I upgraded my project to Spring Boot 3 and Spring Security 6, but since the upgrade the CSRF protection is no longer working.
I'm using the following configuration:
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated())
.httpBasic(withDefaults())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS))
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
.build();
}
#Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.builder().username("user").password("{noop}test").authorities("user").build();
return new InMemoryUserDetailsManager(user);
}
On my webpage I only have a single button:
<button id="test">Test CSRF</button>
And the following JavaScript code:
document.querySelector("#test").addEventListener('click', async function() {
console.log('Clicked');
// This code reads the cookie from the browser
// Source: https://stackoverflow.com/a/25490531
const csrfToken = document.cookie.match('(^|;)\\s*XSRF-TOKEN\\s*=\\s*([^;]+)')?.pop();
const result = await fetch('./api/foo', {
method: 'POST',
headers: {
'X-XSRF-Token': csrfToken
}
});
console.log(result);
});
In Spring Boot 2.7.x this setup works fine, but if I upgrade my project to Spring Boot 3 and Spring Security 6, I get a 403 error with the following debug logs:
15:10:51.858 D o.s.security.web.csrf.CsrfFilter: Invalid CSRF token found for http://localhost:8080/api/foo
15:10:51.859 D o.s.s.w.access.AccessDeniedHandlerImpl: Responding with 403 status code
My guess is that this is related to the changes for #4001. However I don't understand what I have to change to my code or if I have to XOR something.
I did check if it was due to the new deferred loading of the CSRF token, but even if I click the button a second time (and verifying that the XSRF-TOKEN cookie is set), it still doesn't work.
I have recently added a section to the reference documentation for migrating to 5.8 (in preparation to 6.0) that demonstrates a solution for this issue.
TL;DR See I am using AngularJS or another Javascript framework.
The issue here is that AngularJS (and your example code above) are using the XSRF-TOKEN cookie directly. Prior to Spring Security 6, this was fine. But unfortunately, the cookie is actually used to persist the raw token, and with Spring Security 6, the raw token is not accepted by default. Ideally, front-end frameworks would be able to use another source to get the token, such as an X-XSRF-TOKEN response header.
However, even with Spring Security 6, such a response header is not provided out of the box, though it could be a possible enhancement worth suggesting. I have not yet suggested such an enhancement since Javascript frameworks would not be able to use it by default.
For now, you will need to work around the problem by configuring Spring Security 6 to accept raw tokens, as suggested in the section I linked above. The suggestion allows raw tokens to be submitted, but continues to use the XorCsrfTokenRequestAttributeHandler to make available the hashed version of the request attribute (e.g. request.getAttribute(CsrfToken.class.getName()) or request.getAttribute("_csrf")), in case anything renders the CSRF token to an HTML response which could be vulnerable to BREACH.
I would recommend finding a reputable source for researching BREACH more thoroughly, but unfortunately I cannot claim to be such a source.
I would also recommend keeping an eye on Spring Security issues for now, as things may change quickly once the community begins consuming Spring Security 6. You can use this filter as a possible way to keep track of CSRF-related issues.
We have an angular angular application with spring-boot. We tried to migrate to spring-boot 3 (Spring Security 6). And we faced the same problem.
We tried many methods including some of the solutions from this question's answer but we failed. After spending time we found the solution from the spring security doc.
What we need to do is, set the CsrfRequestAttributeName to null in the configuration.
requestHandler.setCsrfRequestAttributeName(null);
What actually happened:
The CsrfToken will be loaded on each request in Spring Security version 5 by default. This means that in a typical setup, every request—even those that are not necessary—must have the HttpSession read.
The default behavior of Spring Security 6 is to postpone looking up the CsrfToken until it is required.
Our application needs the token every time. So, We need to opt into the 5.8 defaults.
The example code is given below (from doc):
#Bean
DefaultSecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
CsrfTokenRequestAttributeHandler requestHandler = new CsrfTokenRequestAttributeHandler();
// set the name of the attribute the CsrfToken will be populated on
requestHandler.setCsrfRequestAttributeName(null);
http
// ...
.csrf((csrf) -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(requestHandler)
);
return http.build();
}
Thanks for this! I was able to use it to solve a similar project in a JHipster + Spring Boot 3 app. However, it seems the class name might've changed recently. Here's what I had to use:
.csrf(csrf -> csrf
.csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler()))
I currently worked around the problem by disabling the XorCsrfTokenRequestAttributeHandler like this:
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
// Added this:
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()))
However, this means that I'm likely vulnerable against the BREACH attack.
Using the accepted answer breaks tests that require CSRF using Spring Security's SecurityMockMvcRequestPostProcessors.crsf() I can either only use CsrfTokenRequestAttributeHandler, or XorCsrfTokenRequestAttributeHandler in Spring Boot's CSRF configuration, both give positive test results.
Using the accepted answer makes Angular work but breaks tests.
So the only workaround at the moment seems to be using CsrfTokenRequestAttributeHandler and so effectively disabling Spring Security's BREACH-protection.
As of Spring Security 6.0.1 and Spring Boot 3.0.2, following the instructions from the accepted answer fails on the first request but succeeds thereafter. The reason it fails on the first request is because the token's cookie never gets created until a protected method is invoked. This is because the method CookieCsrfTokenRepository.saveToken only gets called when the CsrfFilter calls deferredCsrfToken.get(), which only gets called on POST, PUT, PATCH, and DELETE methods. Unfortunately, under the current implementation, that means the client has to expect a failure on the first request. Under previous versions of Spring Security, we used to be able to count on the token's cookie being included in the response to GET, HEAD, or OPTIONS requests.
We are using spring boot 2.4.5 with webflux and calling a service with client credentials grant type. What we noticed is that webclient is not following redirects.
How can we enable webclient to follow redirects where it can continue passing access token until it get the http 200?
Adding following code snippet does not pass the access token to redirected url and it is returning 401.
WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(
HttpClient.create().followRedirect(true)
))
The sensitive headers like the Authorization are removed from the initialized request when redirecting to a different domain.
You can use the following variant of followRedirect(boolean):
followRedirect(boolean followRedirect, Consumer<HttpClientRequest> redirectRequestConsumer)
In order to re-add the Authorization header using redirectRequestConsumer.
For more details see the Javadoc here and Reactor Netty documentation here.
I am writing an API in Spring Boot that I want to secure using Keycloak.
After doing some setting up, I managed to get the keycloak adapter to work. While I was expecting a 403 on any non-authenticated request, I get an HTTP 302 redirect to login page instead.
As I am working on an API, how can I disable the auto login-redirect and provide a 403 error message, so that I could add some frontend logic to start on the login process?
In your config, you do
http.exceptionHandling().authenticationEntryPoint(new Http403ForbiddenEntryPoint())
It will start returning 403 on every failed call and you can then catch it and whatever logic you need.
You need to add the propery keycloak.bearer-only: true in the application.properties or application.yml to avoid redirect to login page when there is one error
You will get one 401 error
Sorry folks, this may be a newb question. I'm a little lost.
My Spring-boot environment provides me with keycloak for client authorization, it gives me these.
spring.security.oauth2.resourceserver.jwt.issuer-uri
spring.security.oauth2.client.provider.keycloak.issuer-uri
spring.security.oauth2.client.registration.keycloak.* # client-id, secret, provider, grant-type
I noticed on the ClientRegistration that .issuerUri(String uri) is not avaialbe until Spring-Security v5.4.x. I am using 5.3.5, although I could bump up. I am confused what the difference is. As I would expect, I get an error when I do .tokenUri(issuerUri). I believe they are different modes/API, but I am at a loss as to what I should set in the 5.3.5 API.
Caused by: org.springframework.security.oauth2.client.ClientAuthorizationException: [invalid_token_response] An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: 405 Method Not Allowed: [{"error":"RESTEASY003650: No resource method found for POST, return 405 with Allow header"}]
So as a newb, I don't get why I have 4 choices of URI and what they do. Google and javadoc haven't been much help, so I figure I just don't know the right place to look to learn it. The only way I know how to fix this is to manual make my own HTTP call to the URI and get my Authentication token, but that would defeat the purpose of the Oauth2 library.
tokenUri represents the URI for the token endpoint. For example:
https://authz.example.org/auth/realms/myrealms/protocol/openid-connect/token
Whereas issuerUri is the URI that identifies the Authorization Server:
https://authz.example.org/auth
It's quite common for the issuer URI to be the root for more specific URIs like the token URI.
Regarding your specific error, I'd imagine that Keycloak is stating that you cannot POST to https://authz.example.org/auth, which is true. You should be POSTing to the token endpoint.
The issuer-uri Spring Boot property should cause Spring Security to look up the other endpoints and add them to a default ClientRegistration. Because of that, I'm not sure why you are also trying to programmatically configure ClientRegistration. That said, if you do need to programmatically create a ClientRegistration, you can use the issuer URI like so, and Spring Security will do the rest:
#Bean
ClientRegistrationRepository registrations() {
ClientRegistration registration = ClientRegistrations
.forIssuerLocation("https://authz.example.org/auth")
.build();
return new InMemoryClientRegistrationRepository(registration);
}
I configured a service with oauth2Login.
User is redirected to Authorization Endpoint - /oauth2/authorization/{registrationId} when he/she is not authorized.
I would like to customize redirection in a way that it takes into account path prefix, since application is accessible trough prefix /api/myapp/.
From the source code I can see that there is OAuth2AuthorizationRequestRedirectWebFilter during Spring Security setup and this filter is using DefaultServerRedirectStrategy which decides if location is relative or not. Moreover it uses a contextPath which is hard to set when using Spring Boot.
Unfortunately I don't know how to override default behavior to make redirect relative.
I don't need to modify contextPath. Instead I've registered ForwardedHeaderTransformer as a bean.
This transformer is able to retrieve headers set by proxy (X-Forwarded-Prefix) and sets context path for request correctly.