I have a web application that is experiencing weird behavior. When you try to bring up the app, it requests that you log in as expected, and takes you to the welcome page (/
) you can then select either the profile (/profile
) page or the search page (/search
). If you try to access any of these pages without logging in, it redirects you to the login page as expected. When you try to submit search criteria or submit a password change, however, a 403 Forbidden is returned.
<security:http use-expressions="true">
<security:intercept-url pattern="/resources/css/*" access="permitAll" />
<security:intercept-url pattern="/resources/images/*" access="permitAll" />
<security:intercept-url pattern="/login" access="permitAll" />
<security:intercept-url pattern="/logout" access="permitAll" />
<security:intercept-url pattern="/accessdenied" access="permitAll" />
<security:intercept-url pattern="/**" access="hasRole('ROLE_USER')" />
<security:form-login
login-page="/login"
default-target-url="/"
authentication-success-handler-ref="loginSuccessHandler"
authentication-failure-url="/accessdenied"
/>
<security:logout
logout-success-url="/"
logout-url="/perform_logout"
delete-cookies="JSESSIONID"
/>
</security:http>
urls:
/ (Welcome Page [GET])
/search (Search Page [GET])
/search/data (Search Query [POST])
/profile (Profile Page [GET])
/profile/updatePassword (Profile Update [POST])
Profile Controller
@Controller
@RequestMapping({ "/profile" })
public class ProfileController {
@Autowired
UserService userService = null;
@Autowired
ProfileService profileService = null;
@RequestMapping(value = { "/", "" }, method = RequestMethod.GET)
public String getProfile(Model model) {
Profile profile = profileService.getProfile();
model.addAttribute("profile", profile);
return "profile";
}
@RequestMapping(value = { "/updatePassword" }, method = RequestMethod.POST)
public @ResponseBody AjaxResponse updatePassword(@RequestBody Profile profile) {
// do stuff
return new AjaxResponse(response, null, errors);
}
}
Search Controller
@Controller
@RequestMapping({ "/search" })
public class StockKeepingUnitController {
@Autowired(required = true)
private SkuService skuService;
@Autowired(required = true)
private UserService userService;
@RequestMapping(value = {"", "/"}, method = RequestMethod.GET)
public String search() {
return "search";
}
@RequestMapping(value = "/data", method = RequestMethod.POST)
public @ResponseBody AjaxResponse data(@RequestBody SearchCriteria searchCriteria) {
List<StockKeepingUnit> skus = null;
try {
String criteria = searchCriteria.getCriteria();
skus = skuService.listSkusBySearch(criteria);
} catch (Exception ex) {
ex.printStackTrace();
List<String> errors = new ArrayList<>();
errors.add("Error saving ALOT.");
return new AjaxResponse("ERROR", null, errors);
}
return new AjaxResponse("OK", skus, null);
}
}
Search ajax
$.ajax({url: "${pageContext.request.contextPath}/search/data"
, method: "POST"
, contentType: "application/json; charset=utf-8"
, dataType: "json"
, data: JSON.stringify(searchCriteria)
, success: function(ajaxResponse) { /* ... */ }
, error: function(xhr, status, error) { /* ... */ }
});
Profile ajax
$.ajax({
url: "${pageContext.request.contextPath}/profile/updatePassword",
, method: "POST"
, contentType: "application/json; charset=utf-8"
, dataType: "json"
, data: JSON.stringify(profile)
, success : function(ajaxResponse) { /* ... */ }
, error : function(xhr, status, error) { /* ... */ }
});
---EDIT--- jQuery for csrf
$(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);
});
});
Also I just found out that if I reload each page, the POST submission works. Is there some way the CSRF token changes on each page? I am using jQuery Mobile btw.