I'm trying to implement CSRF token security in my Spring Boot API to learn how to deal with that.
I've followed this tutorial (server side part) and this is my security config:
private static final String[] CSRF_IGNORE = {"/api/login"};
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.ignoringAntMatchers(CSRF_IGNORE)
.csrfTokenRepository(csrfTokenRepository())
.and()
.addFilterAfter(new CustomCsrfFilter(), CsrfFilter.class)
.exceptionHandling()
.authenticationEntryPoint(new Http403ForbiddenEntryPoint() {
})
.and()
.authenticationProvider(getProvider())
.formLogin()
.loginProcessingUrl("/api/login")
.successHandler(new AuthentificationLoginSuccessHandler())
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(new AuthentificationLogoutSuccessHandler())
.invalidateHttpSession(true)
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
Others things are the same as in the tutorial.
I'm testing with Postman.
When i add the endpoint i want in CSRF_IGNORE, i can see with logger/debug that token stocked, and token from cookie are the same, because the security config's part CustomCsrfFilter.java in .addFilterAfter() is used, but when i remove the endpoint from this CSRF_IGNORE, what i get is a 403, and, logger/debug in the CustomCsrfFilter.java isn't used, so i'm thinking that tokens aren't compared.
I think I missed something and I would like to understand.
If you want to use CSRF with a http only false cookie, why not use Spring Security's built in CookieCsrfTokenRepository? Should simplify your config that way. CustomCsrfFilter seems to be adding a XSRF-TOKEN cookie to the HttpServletResponse, which CookieCsrfTokenRepository does for you.
The default CSRF cookie name when using CookieCsrfTokenRepository is X-CSRF-TOKEN, which is conveniently the default name Angular's HttpClientXsrfModule uses. Of course you can customize that if you need.
So your security config becomes:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.exceptionHandling()
.authenticationEntryPoint(new Http403ForbiddenEntryPoint())
.and()
.authenticationProvider(getProvider())
.formLogin()
.loginProcessingUrl("/api/login")
.successHandler(new AuthentificationLoginSuccessHandler())
.failureHandler(new SimpleUrlAuthenticationFailureHandler())
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(new AuthentificationLogoutSuccessHandler())
.invalidateHttpSession(true)
.and()
.authorizeRequests()
.anyRequest().authenticated();
}
And with Angular, your app module has HttpClientXsrfModule as
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
HttpClientModule,
HttpClientXsrfModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Related
We have migrated to Spring Boot 3 and Spring Security 6. The behavior of users who are not authenticated has changed.
Unauthenticated users should have Anonymous Authetification as before. Instead we get an AuthenticationException.
Is this behavior correct in the new version of Spring Security 6 or do we need to adjust our WebSecurityConfig?
Here is our filter chain:
#Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
log.debug("Configuring HTTP Security");
http
.csrf().disable()
.cors()
.and()
.headers()
.frameOptions().disable()
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.exceptionHandling()
.and()
.authorizeHttpRequests()
.requestMatchers("/sf/**").permitAll()
.requestMatchers(HttpMethod.GET, "/health").permitAll()
.requestMatchers(HttpMethod.GET, "/metrics").permitAll()
.requestMatchers(HttpMethod.GET, "/error").permitAll()
.requestMatchers(HttpMethod.GET, "/favicon.ico").permitAll()
.requestMatchers(HttpMethod.GET, "/info").permitAll()
.requestMatchers("/").permitAll()
.requestMatchers("/**").authenticated()
.and()
.addFilterBefore(new RolesToRightsConverterFilter(s3RSpringConfig), BasicAuthenticationFilter.class)
.addFilterAfter(new Slf4jMDCFilter(authService, tracingService), RolesToRightsConverterFilter.class)
.oauth2ResourceServer().jwt().jwtAuthenticationConverter(new AadJwtBearerTokenAuthenticationConverter());
return http.build();
}
In the configuration below I think I have not done anything wrong. The Urls that I have allowed for all are redirecting me to login page. Same problem with users having role USER.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/**").hasRole("ADMIN")
.antMatchers("/new/**", "/edit/**", "/create/**", "/save/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/", "/registration/**", "/view/**",).permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("/")
.and()
.logout().invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/loggingOut").permitAll();
}
If you can provide any resource which can help to understand better. I am new to spring, any help would be much appreciated.
I think the problem is with your Role Hierarchy. Try this.
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/new/**", "/edit/**", "/create/**", "/save/**").hasAnyRole("USER", "ADMIN")
.antMatchers("/", "/registration/**", "/view/**",).permitAll()
.antMatchers("/**").hasRole("ADMIN")
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login").permitAll()
.defaultSuccessUrl("/")
.and()
.logout().invalidateHttpSession(true)
.clearAuthentication(true)
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/loggingOut").permitAll();
}
If this did not work please try with different combinations.
This article explains the Role Hierarchy, It can help you.
I am using spring boot application with spring security using jwt.
login user is having the admin access, and he is trying to delete the user, it is accepting with the following code
angular:-
delete(userId: number) {
debugger;
return this.http.delete(`/api/v1/admin/deleteUser/${userId}`);
}
SpringSecurityConfig.java
#Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.headers()
.frameOptions().sameOrigin()
.and()
.authorizeRequests()
.antMatchers("/api/v1/authenticate", "/api/v1/register","/api/v1/basicauth").permitAll()
.antMatchers("/").permitAll()
.antMatchers("/admin/**").hasRole("ADMIN")//only admin can access this
.anyRequest().authenticated()
.and()
.formLogin()
.loginPage("/login")
.defaultSuccessUrl("/home")
.failureUrl("/login?error")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.deleteCookies("my-remember-me-cookie")
.permitAll()
.and()
.exceptionHandling().authenticationEntryPoint(jwtAuthenticationEntryPoint).and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add a filter to validate the tokens with every request
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
controller.java
#DeleteMapping(path = "/admin/deleteUser/{userId}")
public ResponseEntity<?> deleteUser(HttpServletRequest request,#PathVariable int userId) {
authenticationService.deleteUser(userId);
return ResponseEntity.ok((""));
}
but in my application user login with ROLE_USER, he is also able to access that method, how to restrict access upto ROLE_ADMIN only.
Modify the ant matchers to match the expected URL.
.antMatchers("/api/v1/admin/**").hasRole("ADMIN") //only admin can access this
I want to implement AuthenticationFailureHandler with the following configuration:
// Auth failure handler
#Bean
public AuthenticationFailureHandler appAuthenticationFailureHandler() {
ExceptionMappingAuthenticationFailureHandler failureHandler = new ExceptionMappingAuthenticationFailureHandler();
Map<String, String> failureUrlMap = new HashMap<>();
failureUrlMap.put(BadCredentialsException.class.getName(), "/login?error");
failureUrlMap.put(AccountExpiredException.class.getName(), "/login?expired");
failureUrlMap.put(LockedException.class.getName(), "/login?locked");
failureUrlMap.put(DisabledException.class.getName(), "/login?disabled");
failureHandler.setExceptionMappings(failureUrlMap);
return failureHandler;
}
and in class SecurityConfiguration extends WebSecurityConfigurerAdapter I have:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/register", "/confirm").permitAll()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
// username password
.usernameParameter("username")
.passwordParameter("password")
// success and failure handlers
.successHandler(appAuthenticationSuccessHandler())
.failureHandler(appAuthenticationFailureHandler())
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/login?logout")
.invalidateHttpSession(true)
.clearAuthentication(true)
.permitAll()
.and()
.exceptionHandling().accessDeniedHandler(accessDeniedHandler())
;
}
with this, all mentioned above is not redirecting to relevant failure URL, but if I remove
.anyRequest()
.authenticated()
then it is being redirected to relevant failure URL, but that is not good practice now the question is how I can configure the configure() to ignore /login?request parameter and implement further logic accordingly?
As I understand, the issue is that urls like "/login?.*" are available only after authorization. According to spring examples, you can exclude paths from authorized access with the following code in Config file:
#Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/resources/**");
}
I try to understand how the RequestMatcher, AntMatcher and so on are working. I read some posts and understand the basics. Actually I have this simple basic config:
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers() //1
.antMatchers("/login", "/oauth/authorize") //2
.and() //3
.authorizeRequests() //4
.anyRequest() //5
.authenticated() //6;
I really dont understand the points 1,2 and 3. From my understanding this means requests of /login and /oauth/authorize are mapped and should be authorized requests. All other requests needs a authenticatoin.
Means for an endpoint /user/me I have to be authenticated because its ruled by point 5 and 6?
The call to this endpoint is working for me.
In my ohter config I try a different approach:
#Override
protected void configure(HttpSecurity http) throws Exception { // #formatter:off
http
.authorizeRequests() //1
.antMatchers("/login", "/oauth/authorize", "/img/**").permitAll() //2
.anyRequest() //3
.authenticated() //4
From my point of view, this should be the same logic than the first config. But actually the endpoint /user/me is not accessable any more.
I would really appreciated for a clarification
Update 1:
This is my config now:
#Override
protected void configure(HttpSecurity http) throws Exception { // #formatter:off
http
.requestMatchers()
.antMatchers("/", "/login", "/oauth/authorize",
"/main", "/logout-success", "/single-logout",
"/password_forgotten", "/enter_new_password", "/img/**",
"/logout", "/access_denied")
.and().authorizeRequests()
.antMatchers("/img/**", "/logout-success", "/password_forgotten",
"/enter_new_password", "/access_denied").permitAll()
.requestMatchers(SecurityUtils::isFrameworkInternalRequest).permitAll()
.and()
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login?error")
.defaultSuccessUrl("/main")
.permitAll()
.and()
.logout()
.logoutRequestMatcher(new AntPathRequestMatcher("/logout"))
.logoutSuccessUrl("/logout-success")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
.and()
.exceptionHandling()
.accessDeniedPage("/access_denied")
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))
.and().csrf().disable();
and if I enter URL \user\me as a not authenticated user I get a 401 and this message:
<oauth>
<error_description>
Vollständige Authentifikation wird benötigt um auf diese Resource zuzugreifen
</error_description>
<error>unauthorized</error>
</oauth>
Which is ok, but means any ohter SecurityFilterChain takes place for this URL, right?
requestMatchers() configures if an URL will be processed by that SecurityFilterChain. So if an URL does not match it , the whole SecurityFilterChain will be skipped which means Spring Security will not handle this URL after that. If you do not configure it , the default is to match all URLs.
The authorizeRequests() configures the authorisation stuff for an URL such as things like if it requires to be authenticated or if only certain roles can access it etc. It only has effect for those URLs that are processed by that SecurityFilterChain (i.e. Those URLs that are matched by requestMatchers())
So , back to your 1st example:
http.requestMatchers() //1
.antMatchers("/login", "/oauth/authorize") //2
.and() //3
.authorizeRequests() //4
.anyRequest() //5
.authenticated() //6;
It means this SecurityFilterChain will only has effect on /login and /oauth/authorize. Both URLs are required to be authenticated. All other URLs will not handled by this SecurityFilterChain. So whether /user/me is required to be authenticated or not is nothing to do with Spring Security.
http
.authorizeRequests() //1
.antMatchers("/login", "/oauth/authorize", "/img/**").permitAll() //2
.anyRequest() //3
.authenticated() //4
It means all URLs will be handled by this SecurityFilterChain (Default value of requestMatchers()). /login , /oauth/authorize and /img/** does not need any authorisation. Other URLs are required to be authenticated.
Your first configuration
.requestMtchers() //1
#Override
protected void configure(HttpSecurity http) throws Exception {
http.requestMatchers() //1
.antMatchers("/login", "/oauth/authorize") //2
.and() //3
.authorizeRequests() //4
.anyRequest() //5
.authenticated() //6;
Let me explain your .authorizeRequests() //4
http.authorizeRequests() It's a wild card(/**)(just like filter allows every request)
HttpSecurity configuration will consider only requests with these pattern
here you can say
http.authorizeRequests()
//is nothing but equals to
http.antMatcher("/**").authorizeRequests();
//and also equals to
http.requestMatchers()
.antMatchers("/**")
.and()
.authorizeRequests()
if you configure like below
http.antMatcher("/api/**").authorizeRequests();
Rest of the configuration(.hasRole(), .hasAnyRole, .authenticated() or .authenticated()) will only get consulted if incoming request uri matches the configured antmatcher(/api/**)
Consider you have multiple URL's(different pattern) needs to be configured then you can't use simply one antMatcher. You need multiple then you should use requestMatcher as below
http.requestMatchers()
.antMatchers("/api/**", "employee/**", "/customer/**")
.and()
.authorizeRequests()
.antMatchers() //2
Used for configuring RequestMatcherConfigurer return type of .requestMatchers()
or also
Used for configuring ExpressionInterceptUrlRegistry return type of .authorizeRequests()
.and() //3
Return the HttpSecurity for further customizations
.anyRequest() //5
The object that is chained after creating the RequestMatcher
All request matches configured request matcher pattern
.authenticated() //6
Below configuration is self explainatory
.antMatchers("/app/admin/**").hasRole("ADMIN")
//or
.antMatchers("/app/admin/**").hasAnyRole("ADMIN", "USER")
//or
.antMatchers("/app/admin/**").authenticated()
//or
.antMatchers("/app/admin/**").permitAll()
This is rough idea about antMatchers, authorizeRequests, and authenticated. You can refer my answer in this link for sequence of execution in spring security