Kristof
Kristof

Reputation: 1704

Spring RestController requires CSRF

I have built a RestController, which should help manage tags in a web application. I will be calling all of the methods through AJAX (using JQuery), but Spring-Boot's default Security results in the following (when calling from Postman):

{ "timestamp": 1438800538949, "status": 403, "error": "Forbidden", "message": "Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-CSRF-TOKEN'.", "path": "/tag/add" }

What is the best way to get the CSRF token to do the calls from JQuery? I assume it's better than to disable it. What about when using it from PostMan?

This is the code of the controller:

@RestController
public class TagController {

@Autowired
private TagService tagService;

@RequestMapping(name = "/tag/list", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<TagList> getTagList() {
    TagList result = new TagList(tagService.list());
    return new ResponseEntity<TagList>(result, HttpStatus.OK);
}

@RequestMapping(name = "/tag/add", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> addTag(@RequestBody AlterTagForm form) {
    try {
        tagService.addTag(form.getArticleId(), form.getTagName());
        return new ResponseEntity<>(HttpStatus.ACCEPTED);
    } catch (EntityNotFoundException ex) {
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
}

@RequestMapping(name = "/tag/remove", produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<?> removeTag(@RequestBody AlterTagForm form) {
    try {
        tagService.removeTag(form.getArticleId(), form.getTagName());
        return new ResponseEntity<>(HttpStatus.ACCEPTED);
    } catch (EntityNotFoundException ex) {
        return new ResponseEntity<>(HttpStatus.NOT_FOUND);
    }
}
}

Upvotes: 0

Views: 2495

Answers (1)

Manu Zi
Manu Zi

Reputation: 2370

First, you need to include the csrf token within your html:

<meta name="_csrf" id="_csrf" th:content="${_csrf.token}"/> <meta name="_csrf_header" id="_csrf_header" th:content="${_csrf.headerName}"/>

you can do it in the form too, like this:

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

but i prefer to do that in the meta, because that is in the header and i have this for every template with a form (i use thymeleaf, fragments etc.)

Second, you need to modify your ajax call:

var csrfToken = $('#_csrf').attr("content");
var csrfHeader = $('#_csrf_header').attr("content");
$.ajax({
    url : '/tag/remove',
    type: 'POST',
    data: ({ id: id }),
    beforeSend: function(xhr){
            xhr.setRequestHeader(csrfHeader, csrfToken);
    }
}).done(function(data, textStatus, jqXHR) {
        alert('finish');
});

To set the requestHeader is the most important of this.

PS: Sometimes it can be happen that the csrf header is null for this you can easily replace this:

var csrfHeader = $('#_csrf_header').attr("content")

with that:

var csrfHeader = 'X-CSRF-TOKEN';

Upvotes: 1

Related Questions