سؤال

I use the following JavaScript/jQuery function to make a remote procedure call through JSON.

<s:url var="testJsonUrl" action="testJsonAction"/>
<script src="../js/jquery-1.8.0.min.js" type="text/javascript"></script>

var timeout;
var request;

$(document).ready(function(){
    $("#countryComboBox").change(function(){
        if(!request)
        {
            request = $.ajax({
                datatype:"json",
                type: "POST",
                data: JSON.stringify({jsonrpc:'2.0', method:'getStateTables', id:'jsonrpc', params:[$("#countryComboBox").val()]}),
                contentType: "application/json-rpc; charset=utf-8",
                url: "<s:property value='#testJsonUrl'/>",
                success: function(response)
                {
                    alert(JSON.stringify(response.result));
                },
                complete: function()
                {
                    timeout = request = null;
                },
                error: function(request, status, error)
                {
                    if(status!=="timeout"&&status!=="abort")
                    {
                        alert(status+" : "+error);
                    }
                }
            });
            timeout = setTimeout(function() {
                if(request)
                {
                    request.abort();
                    alert("The request has been timed out.");
                }
            }, 30000);
        }
    });
});

This function invokes the method getStateTables() in an action class which is as follows.

@Namespace("/admin_side")
@ResultPath("/WEB-INF/content")
@ParentPackage(value = "json-default")
public final class TestAction extends ActionSupport
{
    @Autowired
    private final transient SharableService sharableService = null;
    private static final long serialVersionUID = 1L;
    List<StateTable> stateTables;

    public TestAction() {}

    @SMDMethod
    public List<StateTable> getStateTables(@SMDMethodParameter(name="countryId")Long countryId) {
        stateTables = sharableService.findStatesByCountryId(countryId);
        return stateTables;
    }

    @Action(value = "testJsonAction",
    results = {
        @Result(name = ActionSupport.SUCCESS, type = "json", params = {"enableSMD", "true"})},
    interceptorRefs = {
        @InterceptorRef(value = "json", params = {"enableSMD", "true", "includeProperties", "result\\[\\d+\\]\\.stateId, result\\[\\d+\\]\\.stateName", "excludeNullProperties", "true"})})
    public String executeAction() throws Exception {
        return SUCCESS;
    }

    @Action(value = "Test",
    results = {
        @Result(name = ActionSupport.SUCCESS, location = "Test.jsp"),
        @Result(name = ActionSupport.INPUT, location = "Test.jsp")},
    interceptorRefs = {
        @InterceptorRef(value = "defaultStack", params = {"params.acceptParamNames", "", "params.excludeMethods", "load", "validation.validateAnnotatedMethodOnly", "true"})})
    public String load() throws Exception {
        //This method is just used to return an initial view on page load.
        return ActionSupport.SUCCESS;
    }
}

This method causes a list of states corresponding to the countryId supplied by the jQuery function as a parameter to this method to be returned which is delegated to the jQuery function as a JSON response.

This works fine unless Spring Security comes into picture. While using Spring Security, the request is forbidden.

The header information can be viewed as follows.

Request URL:http://localhost:8080/TestStruts/admin_side/testJsonAction.action
Request Method:POST
Status Code:403 Forbidden
Request Headers
    Accept:*/*
    Accept-Encoding:gzip,deflate,sdch
    Accept-Language:en-US,en;q=0.8
    Cache-Control:max-age=0
    Connection:keep-alive
    Content-Length:73
    Content-Type:application/json-rpc; charset=UTF-8
    Cookie:JSESSIONID=0C4D7DFD269F1D5F7315A39971A75961; sbOverlayID=42887237
    Host:localhost:8080
    Origin:http://localhost:8080
    Referer:http://localhost:8080/TestStruts/admin_side/Test.action
    User-Agent:Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1700.76 Safari/537.36
    X-Requested-With:XMLHttpRequest
Request Payload
    {jsonrpc:2.0, method:getStateTables, id:jsonrpc, params:[2]}
    id: "jsonrpc"
    jsonrpc: "2.0"
    method: "getStateTables"
    params: [2]
Response Headers
    Content-Length:1149
    Content-Type:text/html;charset=utf-8
    Date:Tue, 28 Jan 2014 22:12:55 GMT
    Server:Apache-Coyote/1.1
    X-Content-Type-Options:nosniff
    X-Frame-Options:DENY
    X-XSS-Protection:1; mode=block

My Spring Security configurations are exactly the same as mentioned in this question.

Where to look into Spring Security?

هل كانت مفيدة؟

المحلول

Found something. It is because I'm using a CSRF token (configured in the spring-security.xml file as <csrf/>) which is required to be sent along with the AJAX request. it worked, when I used a jQuery function along with <meta> tags as mentioned here.

If you using JSON, then it is not possible to submit the CSRF token within an HTTP parameter. Instead you can submit the token within a HTTP header. A typical pattern would be to include the CSRF token within your meta tags. An example with a JSP is shown below:

<html>
  <head>
    <meta name="_csrf" content="${_csrf.token}"/>
    <!-- default header name is X-CSRF-TOKEN -->
    <meta name="_csrf_header" content="${_csrf.headerName}"/>
    <!-- ... -->
  </head>
  <!-- ... -->

You can then include the token within all your Ajax requests. If you were using jQuery, this could be done with the following:

$(function () {
  var token = $("meta[name='_csrf']").attr("content");
  var header = $("meta[name='_csrf_header']").attr("content");
  $(document).ajaxSend(function(e, xhr, options) {
    xhr.setRequestHeader(header, token);
  });
});
مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top