Reputation: 3406
I tried to add CSRF/XSRF protection to my application, but ran into strange behavior. All get requests work fine, but on all post/put/delete I'm getting 403 Unauthorized. And the strangest thing is that when I tried to debug my CSRF filter, requests do not reach it, they are rejected somewhere earlier. They do not even reach my authentication filter, so I can not figure out what the problem may be.
My security config:
@Override
public void configure(HttpSecurity http) throws Exception {
http
...
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf().csrfTokenRepository(csrfTokenRepository());
}
private CsrfTokenRepository csrfTokenRepository() {
HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository();
repository.setHeaderName("X-XSRF-TOKEN");
return repository;
}
I do not add the filters since as I said, the requests do not reach them. But if needed I will complete my question. I hope for your help, thank you in advance!
Upvotes: 0
Views: 8477
Reputation: 3406
Many thanks for the answers, they really helped me to find a solution. And I want to share my solution if in the future someone will face the same issue.
As noted in the answers I used SessionCreationPolicy.STATELESS
and did not have sessions so instead of HttpSessionCsrfTokenRepository
I had to use CookieCsrfTokenRepository
with withHttpOnlyFalse()
to allow AngularJS to read cookies.
As a result, I have a configuration like this:
@Override
public void configure(HttpSecurity http) throws Exception {
http
...
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService()), UsernamePasswordAuthenticationFilter.class)
.addFilterAfter(new CsrfHeaderFilter(), CsrfFilter.class)
.csrf().csrfTokenRepository(csrfTokenRepository());
}
If someone is interested in how the CsrfHeaderFilter looks:
public class CsrfHeaderFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
CsrfToken csrf = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
if (csrf != null) {
Cookie cookie = WebUtils.getCookie(request, "XSRF-TOKEN");
String token = csrf.getToken();
if (cookie==null || token!=null && !token.equals(cookie.getValue())) {
cookie = new Cookie("XSRF-TOKEN", token);
cookie.setPath("/");
response.addCookie(cookie);
}
}
filterChain.doFilter(request, response);
}
}
My second problem was CORS. AngularJS documentation says:
"The header will not be set for cross-domain requests."
To solve this problem, I had to use an HTTP Interceptor:
.factory('XsrfInterceptor', function ($cookies) {
return {
request: function (config) {
var headerName = 'X-XSRF-TOKEN';
var cookieName = 'XSRF-TOKEN';
config.headers[headerName] = $cookies.get(cookieName);
return config;
}
};
});
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('XsrfInterceptor');
}]);
I hope my answer will be useful.
Upvotes: 1
Reputation: 3682
In principle, the CSRF mechanism in Spring stores the CSRF token in a HTTP only cookie. Because JavaScript cannot access a HTTP only cookie, you need to tell spring to disable HTTP only:
.and().csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
Then you can read the cookie from Angular and add it to the XSRF-TOKEN header with each request.
This is the general case. I am not sure if this fits your special case.
Upvotes: 4
Reputation: 2030
Assuming that the rest of your config/filters working properly, you're facing this issue because of this: SessionCreationPolicy.STATELESS
.
You can have a look under the hood of Spring CsrfFilter
. You'll see that it needs to remember the value of each CSRF-token for each user inside a session, and since you are not using sessions it can't be done.
What to do next - is really up to you. Some people saying that if you app is stateless there is actually no need for CSRF protection. Spring docs saying that CSRF attacks are still relevant. I think it really depends on your authentication mechanism.
You might also want to look at this nice article, for example.
Hope it helps.
Upvotes: 1