Java 8 - spring 4.3.x
While configuring spring security and enable csrf feature i came across two implementations of CsrfTokenRepository one is Lazy another is Cookie based
I know CookieCsrfTokenRepository works using writing a csrf token into cookie and accepts a cookie value in header to verify the valid request
Can some one help me to understand how does LazyCsrfTokenRepository works ?
From the javadoc:
A CsrfTokenRepository that delays saving new CsrfToken until the
attributes of the CsrfToken that were generated are accessed.
So why this? In earlier versions of Spring Security the HttpSessionCsrfTokenRepository was default. The drawback with this was that it always creates a token, triggering session creation, regardless of whether the token was used or not, which could be wasteful in some applications.
The LazyCsrfTokenRepository on the other hand only creates a wrapper, and creates the actual token only if getToken() is invoked (like when for example generating a form). This avoids unnecessary session creation.
A gotcha with LazyCsrfTokenRepository is that the actual token generation must still happen before HTTP response is committed, otherwise you get an exception. If you get problem with this, it is easiest to use (only) one of the other two implementations.
Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 days ago.
Improve this question
Spring Boot - Angular Application:
Why does my method return "AnonymousUser" when called from one controller, but the correct user id when called from another controller?
I have a method that returns the logged-in user:
#Override
public String getLoggedUserName() {
return SecurityContextHolder.getContext().getAuthentication().getName();
}
When I call it from a controller, it returns the name of the logged-in user, but when I call it from another controller, it returns "AnonymousUser". Why is this happening?
I don't know if it can be useful as additional information but authentication is via Keycloak
I'll answer first to what I guess your actual question is: "how to be sure to have a JwtAuthenticationToken in the security-context of a #ResController?"
Configure your app as resource-server with a JWT decoder
Protect entry-points with isAuthenticated() either form security filter-chain configuration or #PreAuthorize (or equivalent) on controller class or method (with #EnableMethodSecurity somewhere in the config)
Off course, do not explicitly replace the default Authentication implementation for successful authorizations... (when providing an authentication converter for instance)
Now a complete answer to what you formulated and is way wider as the type of Authentication implementation in the security context of controller method invocation varies depending on quite a few factors.
Runtime
Let's start with "real" invocations (spring app up and running, nothing mocked):
request is not authorized, then an AnonymousAuthenticationToken is put in the security context. Depending on the security config, this could be because of missing or invalid access token (expired, wrong issuer or audience, bad signature, ...), missing basic header, etc., or because the type of authorization is not the right one (for instance a Bearer token on a route intercepted by a security filter-chain expecting basic authentication)
request is successfully authorized => default authentication depends on the security conf
resource-server with JWT decoder, then JwtAuthenticationToken is used (can be accessed verridden by configuring http.oauth2ResourceServer().jwt().jwtAuthenticationConver(...)
resource-server with access token introspection, then BearerTokenAuthentication is used ( override with http.oauth2ResourceServer().opaqueToken().authenticationConver(...))
client with oauth2Login, then OAuth2AuthenticationToken is used
etc., the list continues for non OAuth2 authentications (just have a look at Authentication type hierarchy)
Tests
Test security context can be set for tests in plain JUnit using annotations like #WithMockUser or using MockMvc (respectively WebTestClient for reactive apps) and either annotations or request post processors (respectively WebTestClient mutators).
The type of Authentication injected depends on the annotation (or mutator / post-processor) used. For instance, #WithMockUser builds a UsernamePasswordAuthenticationToken which is rarely adapted to an OAuth2 context. Mutators from SecurityMockMvcRequestPostProcessors from spring-security-test or annotations like
#WithMockJwtAuth, #WithMockBearerTokenAuthentication or #WithOAuth2Login from spring-addons are generally better suited.
Tutorials for both runtime config and tests
I wrote some available from there: https://github.com/ch4mpy/spring-addons/tree/master/samples/tutorials
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.
I've recently upgraded a project from using spring-security 6.0.0-M6 to 6.0.0, gradle config if you want to see it.
This project does not use spring-boot.
Context
My securityFilterChain is configured via code and looks approximately like this:
http.
authenticationManager(authnManager).
securityContext().securityContextRepository(securityRepo).
and().
authorizeRequests(). // <-- DEPRECATED
requestMatchers(RAID_V2_API + "/**").fullyAuthenticated().
The full codebase, starting with the FilterChain config, is publicly available.
Note that usage of WebSecurityConfigurerAdapter is deprecated, and I have not been using it since the original usage of 6.0.0-M6. So calling stuff like WebSecurityConfigurerAdapter.authenticationManagerBean() won't work.
This code works fine, but the call to authorizeRequests() causes a deprecation warning that I want to get rid of.
Problem
The deprecation tag says that I should use authorizeHttpRequests() instead, but when I do that - requests that require authorization (via the fullyAuthenticated() specification above) will be denied with a 403 error.
Analysis
It seems this happens because my AuthenticationProvider instances aren't being called,
because the ProviderManager isn't being called. Since the AuthnProviders don't get called, the security context still contains the pre-auth token instead of a verified post-auth token, so the eventual call to AuthorizationStrategy.isGranted() ends up calling isAuthenticated() on the pre-auth token, which (correctly) returns false and the request is denied.
Question
How do I use the authorizeHttpRequests() method but still have the ProviderManager be called so that my security config works?
My workaround is just to ignore the deprecation warning.
First, your security configuration does not specify any kind of authentication, like httpBasic, formLogin, etc. The AuthenticationManager is invoked by the filters created by those authentication mechanisms in order to authenticate credentials.
Second, the application is probably unwittingly relying on FilterSecurityInterceptor (authorizeRequests) to authenticate the user, which is not supported with authorizeHttpRequests. You need to declare an auth mechanism that collects credentials from the request and authenticates the user.
Because you are using JWT, you might want to consider Spring Security's OAuth2 Resource Server support. You can also refer to our samples repository in order to help you with sample configurations.
Here's a rough outline of what I did to to implement the "just use the resource server" suggestion from the answer.
include the oauth2-resource-server libraries in the build.
create an AuthenticationManagerResolver that replaces what the SecuritycontextRepository and the FilterSecurityInterceptor used to do:
#Bean
public AuthenticationManagerResolver<HttpServletRequest>
tokenAuthenticationManagerResolver(
AuthenticationProvider authProvider
) {
return (request)-> {
return authProvider::authenticate;
};
}
change AuthenticationProvider implementations to use the BearerTokenAuthenticationToken class as the pre-auth token, it still works basically the same way it used to: verifying the pre-auth token and returning a post-auth token.
hook up the new resolver class in the securityFilterChain config by replacing the old securityContextRepository() config with the new authenticationManagerResolver() config, which passes in the resolver created in step 2:
http.oauth2ResourceServer(oauth2 ->
oauth2.authenticationManagerResolver(tokenAuthenticationManagerResolver) );
I like this new approach because it makes it more obvious how the security chain works.
It's nice to replace the custom pre-auth token implementation with the built-in class too.
Note that it's likely this config can be simplified, but I needed the custom resolver since the project uses different types of bearer token depending on the endpoint called. Pretty sure the auth providers don't need to be AuthenticationProvider any more; the lambda function returned from the resolver serves that purpose - they can probably just be random spring components and as long as the method is SAM-type compatible.
The spring-security multi-tenancy doco was helpful for this.
How does spring security maintain authentication info between requests?
Does it use any thing similar to jSessionId or uses an entirely different mechanism.
Further, I see that the AbstractSecurityInterceptor (I mean, any of it's implementations) is responsible for intercepting the incoming request and verify if a request is already authorized using Authentication.isAuthenticated() and then depending on the condition either validate the request or send the Authentication request to an AuthenticationManager Implementation. So, in other words, how does AbstractSecurityInterceptor differentiate between first request and subsequent request.
Spring Security uses a SecurityContextRepository to store and retrieve the SecurityContext for the current security session.
The default implementation is the HttpSessionSecurityContextRepository which utilizes the javax.servlet.http.HttpSession to store/retrieve the SecurityContext.
The underlying servlet container will obtain the correct HttpSession for the incoming request, generally due to a session identifier being passed in a cookie or request parameter. For Spring Security it doesn't matter as that is thus loaded of to the underlying servlet container.
So we're using spring-security-redirect as a parameter in the form that is sent to j_spring_security_check, in order to send the user to the correct page after a successful login. Migrating from Spring security 3.0 to 3.1, this stopped working. We use a subclass of SavedRequestAwareAuthenticationSuccessHandler, overriding onAuthenticationSuccess(), and debugging that method I see that getTargetUrlParameter() returns null. isAlwaysUseDefaultTargetUrl() returns false.
Browsing around I can't find anyone having similar problems... I find some references to AbstractAuthenticationTargetUrlRequestHandler.DEFAULT_TARGET_PARAMETER, which seems to have disappeared in 3.1.
Any ideas?
As per Spring security 3.1 xsd,
Attribute : authentication-success-handler-ref
Reference to an AuthenticationSuccessHandler bean which should be used to handle a successful authentication
request. Should not be used in combination with default-target-url (or always-use-default-target-url) as the
implementation should always deal with navigation to the subsequent destination.
So, in your subclass, you have to perform the redirection.