Ajax POST 不适用于 spring security csrf,但 GET 方法有效

Posted

技术标签:

【中文标题】Ajax POST 不适用于 spring security csrf,但 GET 方法有效【英文标题】:Ajax POST wont work with spring security _csrf but GET method works 【发布时间】:2018-10-17 00:40:26 【问题描述】:

我正在尝试使用 jquery ajax post 方法提交数据,但到目前为止我没有这样做。我正在使用 spring security 4.0.4.RELEASE 和 Spring framework 4.2.5.RELEASE。经过大量谷歌搜索为什么 GET 有效而 POST 无效。我发现 _csrf 令牌与为什么不允许使用 POST 方法有关。我尝试在 URL 中添加令牌,然后它现在允许使用 POST 方法,但它为传递给 data 参数的所有数据检索空值。这是我的示例代码。

控制器:

@RequestMapping(value = "/ajaxAddDeliveryType", method = RequestMethod.GET, produces = "application/json")
    @ResponseBody
    public JsonResponse addDeliveryType(
            @ModelAttribute(value = "delivery") DeliveryType deliveryType,
            BindingResult result) 

        System.out.println("deliveryType "+deliveryType.toString());

        JsonResponse res = new JsonResponse();

        /* Validate all the input. it return "SUCCESS" or "FAIL" status */
        jsonResponse(res, result, deliveryType);

        if (res.getStatus().equalsIgnoreCase("success")) 
            /*
             * If result status is Success insert the data into database
             */
            deliveryTypeService.saveDeliveryType(deliveryType);
        

        return res;
    


/*
     * This method will validate all input field in form and returning response.
     * If result has error detected it will set the status to "FAIL". If no
     * error occured in result it will set the status to "SUCCESS".
     */
    public JsonResponse jsonResponse(JsonResponse res, BindingResult result,
            DeliveryType deliveryType) 

        /* Set error message if text field is empty */

        ValidationUtils.rejectIfEmptyOrWhitespace(result,
                "mainte_delivery_type", "Delivery type can not be empty");

        ValidationUtils.rejectIfEmptyOrWhitespace(result, "delivery_weight",
                "Delivery weight not be empty");

        ValidationUtils.rejectIfEmptyOrWhitespace(result, "delivery_price",
                "Delivery price can not be empty.");

        if (!deliveryTypeService.isDeliveryTypeUnique(deliveryType.getId(),
                deliveryType.getMainte_delivery_type())) 

            /* Set status to fail */
            res.setStatus("FAIL");

            /* Set error message if delivery type already exist */
            result.rejectValue("mainte_delivery_type",
                    "Delivery type already exists. Please fill in different value");

            res.setResult(result.getAllErrors());

        

        if (result.hasErrors()) 

            /* Set status to fail */
            res.setStatus("FAIL");

            /*
             * Collect all error messages for text field that not properly
             * assign value
             */
            res.setResult(result.getAllErrors());

        

        /* Validate if the weight or price is more than zero / 0 */
        else if (deliveryType.getDelivery_price().toString()
                .equalsIgnoreCase("0")
                || deliveryType.getDelivery_weight().toString()
                        .equalsIgnoreCase("0")) 

            /* Set status to fail */
            res.setStatus("FAIL");

            /* Set error message if delivery type already exist */
            result.rejectValue("delivery_price",
                    "Delivery weight/price value should be more than '0'");

            res.setResult(result.getAllErrors());

        

        else 

            deliveryTypes.clear(); /* Clear array list */
            deliveryTypes.add(deliveryType);
            /*
             * Add employee model object into list
             */
            res.setStatus("SUCCESS"); /* Set status to success */
            res.setResult(deliveryTypes); /* Return object into list */

        

        return res;
    

提交ajax POST方法时的控制台输出:

> deliveryType DeliveryType [id=0, mainte_delivery_type=null, delivery_price=null, delivery_weight=null]

提交ajax GET方法时的控制台输出:

deliveryType DeliveryType [id=0, mainte_delivery_type=test, delivery_price=123, delivery_weight=12]

Javacript 代码:

/*
 * This function will validate all the input field inside the form and return a
     * response if result got an error. otherwise if no error in result is found it
     * will insert the data into database
     */
    function validateAndInsertUsingAjax(action, message) 

        /* Disable button to prevent redundant ajax request */
        $("#btnDeliveryType").prop('disabled', true);


        var datastring = $("#myform").serialize();

        $.ajax(

            type : "GET",
            url : myContext + '/' + action+ '?_csrf=' + $("#token").val(),
            data : datastring,
            contentType : "application/json; charset=utf-8",
            datatype : "json",
            crossDomain : "TRUE",
            success : function(response) 
                var stringResponse = JSON.stringify(response)
                // we have the response

                console.log("response " + stringResponse);

                var obj = JSON.parse(stringResponse);

                if (obj.status == "SUCCESS") 
                    /*
                     * Enable button to make ajax request again after response
                     * return
                     */
                    $("#btnDeliveryType").prop('disabled', false);

                    var userInfo = "<ol>";

                    for (i = 0; i < obj.result.length; i++) 

                        /* Create html elements */

                        userInfo += "<br><li><b>Delivery Type</b> : "
                                + obj.result[i].mainte_delivery_type;

                        userInfo += "<br><li><b>Delivery weight (kg)</b> : "
                                + obj.result[i].delivery_weight;

                        userInfo += "<br><li><b>Delivery Price</b> : "
                                + obj.result[i].delivery_price;

                    

                    userInfo += "</ol>";

                    /* Draw message in #info div */
                    $('#info').html(message + userInfo);

                    /* Show and hide div */
                    $('#error').hide('slow');
                    $('#info').show('slow');

                    /* Populate DataTable */
                    populateDataTable();

                    /* Hide modal */
                    $('#modalAddDeliveryType').modal('hide');

                 else 
                    /*
                     * Enable button to make ajax request again after response
                     * return
                     */
                    $("#btnDeliveryType").prop('disabled', false);

                    var errorInfo = "";

                    for (i = 0; i < response.result.length; i++) 

                        errorInfo += "<br>" + (i + 1) + ". "
                                + response.result[i].code;

                    

                    /* Show error message from response */
                    $('#error').html(
                            "Please correct following errors: " + errorInfo);

                    /* Show and hide div */
                    $('#info').hide('slow');
                    $('#error').show('slow');

                

            ,

            /* xhr.status shows server respond */
            error : function(xhr, desc, err) 
                /*
                 * Enable button to make ajax request again after response return
                 */

                $("#btnDeliveryType").prop('disabled', false);
                if (xhr.status == 500) 
                    alert('Error: ' + "Server not respond ");
                
                if (xhr.status == 403) 
                    alert('Error: ' + "Access Denied");
                

            

        );

    

JSP 页面:

<div class="modal fade" id="modalAddDeliveryType" tabindex="-1"
        role="dialog" aria-labelledby="exampleModalCenterTitle"
        aria-hidden="true">
        <div class="modal-dialog modal-dialog-centered" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <h3 class="modal-title" id="exampleModalLongTitle"></h3>
                    <button type="button" class="close" data-dismiss="modal"
                        aria-label="Close">
                        <span aria-hidden="true">&times;</span>
                    </button>
                </div>

                <input type="hidden" id="token" name="$_csrf.parameterName"
                            value="$_csrf.token" />

                <!-- Form Text field -->
                <form:form method="GET" modelAttribute="delivery" name="myform"
                    id="myform">
                    <form:input type="hidden" path="id" id="id" />
                    <div class="modal-body">

                        <!-- Input Delivery type -->
                        <div>
                            <label for="mainte_delivery_type">Delivery type: </label>
                            <form:input path="mainte_delivery_type" id="mainte_delivery_type"
                                class="form-control" placeholder="Delivery type" />
                            <form:errors path="mainte_delivery_type" cssClass="error" />
                        </div>

                        <!-- Input Delivery weight -->
                        <div>
                            <label for="delivery_weight">Delivery weight (kg): </label>
                            <form:input type="number" min="0" path="delivery_weight"
                                id="delivery_weight" class="form-control"
                                placeholder="Delivery weight(kg)" />
                            <form:errors path="delivery_weight" cssClass="error" />
                        </div>

                        <!-- Input Delivery price -->
                        <div>
                            <label for="delivery_price">Delivery price: </label>
                            <form:input type="number" min="0" path="delivery_price"
                                id="delivery_price" class="form-control"
                                placeholder="Delivery price" />
                            <form:errors path="delivery_price" cssClass="error" />
                        </div>




                        <div id="error" class="error"></div>

                    </div>

                    <div class="modal-footer">

                        <!-- Close button -->
                        <button type="button" class="btn btn-secondary"
                            data-dismiss="modal">Close</button>

                        <!-- Register button -->
                        <input type="submit" class="btn btn-primary" value="Save"
                            id="btnDeliveryType" onClick="insertOrUpdateDeliveryType()" />

                    </div>
                </form:form>
            </div>
        </div>
    </div>

我希望有人能提供一种更好的方法来实现这个 POST 方法。我是 Spring Security 的新手。

【问题讨论】:

【参考方案1】:

CSRF 令牌用于防止远程 3rd 方伪造请求。基本上,攻击者会按原样复制表单,然后强迫攻击者控制的网站上毫无戒心的用户代表用户向通常合法的网站发送请求,该请求将不可避免地包含用户的 cookie。

因此,CSRF 令牌至少可以说是随机的,并且锁定在会话中。这就像在您提交的每个表单中都包含一个 cookie,GET 请求也应该包含这些,只要操作正在完成。

假设您在这里没有尝试做任何恶意行为,并且完全控制了您的机器人,您可以轻松地向网站发送初始 GET 请求,建立会话,并从 HTML 中获取您的会话唯一的 CSRF 令牌(将作为值嵌入到 &lt;input&gt; 标记中)。

编辑:顺便说一句,如果您控制目标站点(生成此 CSRF 令牌的站点!),那么显然您可以将自己列入白名单,或通过 AJAX 专门将该信息发送给用户。如果是这样的话,有办法解决这个问题。

【讨论】:

是的,它阻止了 ajax 使用 post 方法,这就是为什么我在 URL 中包含令牌以便能够使用 POST 方法。我在这里想知道的是如何正确检索传递给参数的数据,因为即使有值,它也会检索空值。 您需要向页面发送GET请求并保存CSRF令牌,然后才能发送带有令牌的POST请求。

以上是关于Ajax POST 不适用于 spring security csrf,但 GET 方法有效的主要内容,如果未能解决你的问题,请参考以下文章

Ajax POST 调用不适用于 WCF

jQuery Ajax POST 不适用于 MailChimp

ASP.NET C# SuppressFormsAuthenticationRedirect 不适用于 Ajax POST?

Spring Security CSRF 令牌不适用于 AJAX

Spring Security CSRF 令牌不适用于 AJAX 调用和表单提交在同一个 JSP 中

$http.POST 不适用于 DATETIME 数据类型(AngularJS、Spring 4.2、MySQL、Hibernate)