I have been trying to enable spring security in web flux, with my own custom authentication method. So far so good, but I am not able to allow certain URL patterns using permitall.
I have tried to create different beans of SecurityWebFilterChain, also tried with different config altogether, but nothing seems to work for me.
Here is my SecurityWebFilterChain
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.csrf()
.disable()
.httpBasic()
.disable()
.formLogin()
.disable()
.logout()
.disable()
.authenticationManager(this.authenticationManager())
.securityContextRepository(this.securityContextRepository())
.authorizeExchange()
.pathMatchers("**/signal/health").permitAll()
.pathMatchers("**/order").permitAll()
.and()
.authorizeExchange()
.anyExchange()
.authenticated()
.and()
.build();
}
I have an internal health check system, which runs as soon as my application is up, so I want this to be allowed.
Moreover, I also want to allow another couple or URI, but the above config doesn't work for me.
Everything goes for authentication.
What am I doing wrong here?
I see a wrong and and authorizeExchange placed in between. Try using this:
#Bean
public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) {
return http.csrf().disable()
.httpBasic().disable()
.formLogin().disable()
.logout().disable()
.authenticationManager(this.authenticationManager())
.securityContextRepository(this.securityContextRepository())
.authorizeExchange()
.pathMatchers("**/signal/health","**/order").permitAll()
.anyExchange()
.authenticated()
.and().build();
}
Related
I am migrating an application from Spring 5 to Spring 6, including the security configuration.
My original setup involved calling .permitAll() for the loginPage url and the loginProcessingUrl and then calling .anyRequest().authenticated() which ensured every other request was redirected to the login page.
I have migrated the configuration and come up with
#Bean
public SecurityFilterChain defaultSecurityConfigurationFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests()
.requestMatchers("/LoginPage/").permitAll()
.requestMatchers("/ValidateLogin/").permitAll()
.and()
// setup login redirection and logic
.formLogin()
.loginPage("/LoginPage")
.loginProcessingUrl("/ValidateLogin")
.usernameParameter("loginName")
.passwordParameter("password")
.failureHandler(customAuthenticationFailureHandler())
.successHandler(customAuthenticationSuccessHandler())
.and()
// setup logout
.logout()
.logoutUrl("/Logout")
.logoutSuccessUrl("/LoginPage")
.invalidateHttpSession(true)
.permitAll()
.and()
.sessionManagement()
.invalidSessionUrl("/LoginPage")
.sessionFixation()
.migrateSession()
.and()
.csrf()
.and()
// add security headers
.headers()
.frameOptions()
.sameOrigin()
.contentSecurityPolicy(CONTENT_SECURITY_POLICY)
.and()
.and()
.authorizeHttpRequests()
.anyRequest()
.authenticated();
CharacterEncodingFilter filter = new CharacterEncodingFilter();
filter.setEncoding("UTF-8");
filter.setForceEncoding(true);
http.addFilterBefore(filter, CsrfFilter.class);
MultipartFilter multipartFilter = new MultipartFilter();
multipartFilter.setServletContext(SpringContext.getBean(ServletContext.class));
http.addFilterBefore(multipartFilter, CsrfFilter.class);
http.addFilterAfter(new SessionCookieFilter(), CsrfFilter.class);
return http.build();
}
However it seems that the anyRequest().authenticated() rule is used no matter which url I try to access resulting in an infinite redirect loop where I am served a 302 to /LoginPage.
Is there any way of creating the functionality in Spring 5 where anyRequest() would catch any requests that weren't already permitted?
I have a question concerning spring and web flux.
I have a spring project with spring security and MVC as dependencies.
This application accepts requests and check authentication using the session cookie.
For all the requests starting with "/api/" a failed authentication results in a 401 response, so that can be intercepted by the frontend as such.
For all the requests different from "/api/**" a failed authentication results in the server returning a login page so that the user can login.
This is the SecuritConfig class:
#Configuration
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.formLogin()
.and()
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.exceptionHandling()
.defaultAuthenticationEntryPointFor(new
HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new AntPathRequestMatcher("/api/**"))
.and()
.cors();
}
}
Now, I am trying to achieve the same thing using web flux. With web flux the SecurityConfig is different, I can setup almost all the configs that I have in the old class but there is no equivalent for:
defaultAuthenticationEntryPointFor(new
HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED),
new AntPathRequestMatcher("/api/**"))
My new security config look like:
#Configuration
#EnableWebFluxSecurity
public class SecurityConfig {
#Bean
public SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
return http
.authorizeExchange()
.pathMatchers("/login/**")
.permitAll()
.anyExchange()
.authenticated()
.and()
.formLogin()
.and()
.csrf()
.disable()
.exceptionHandling()
.authenticationEntryPoint(new
HttpStatusServerEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.build();
}
}
But in this case I only get 401 for all the requests that fail authentication.
Does anybody know how to achieve the same behavior with web flux?
Thank you
I'm developing a spring-boot application and its spring security configuration is as follows:
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeRequests()
.antMatchers("/actuator/**", "/login*", "/logout*")
.permitAll();
httpSecurity
.cors().and()
.csrf().disable()
.authorizeRequests()
.antMatchers("/").permitAll()
.antMatchers("/taas/v1/**").hasRole("admin")
.antMatchers("/taas/v1/teams", "/taas/v1/profiles", "/taas/v1/tests/summary").hasRole("tester")
.antMatchers( "/taas/v1/teams", "/taas/v1/tests/summary").hasRole("user")
.anyRequest().authenticated()
.and()
.exceptionHandling().accessDeniedHandler(customAccessDeniedHandler)
.and()
.httpBasic()
.and()
.formLogin()
.successHandler(customAuthenticationSuccessHandler)
.failureHandler(customAuthenticationFailureHandler)
.and()
.logout()
.logoutSuccessHandler(customLogoutSuccessHandler())
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
}
}
Even though i have set the url pattern for each roles. All users are able to access all endpoints as mentioned in antMatchers(). A user with role user is not supposed to access /taas/v1/profiles. But when I try to access that endpoint by logging in as user, I'm getting the response but expected response is 403 forbidden.
I request someone to provide a workaround for me.
I got this issue resolved by doing some minor changes in my antMatchers(). Below is the modified code.
The main issue is that antMatcher() pattern must not contain the context path, see Spring security antMatcher does not work
#EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.cors()
.and()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.antMatchers("/profiles").hasAnyRole("TESTER")
.antMatchers( "/teams", "/tests/summary").hasAnyRole("USER", "TESTER", "ADMIN")
.anyRequest().authenticated()
.and().csrf().disable()
.exceptionHandling()
.accessDeniedHandler(customAccessDeniedHandler)
.and()
.httpBasic()
.and()
.formLogin()
.successHandler(customAuthenticationSuccessHandler)
.failureHandler(customAuthenticationFailureHandler)
.and()
.sessionManagement()
.invalidSessionUrl("/invalidSession.html")
.maximumSessions(1).sessionRegistry(sessionRegistry()).and()
.sessionFixation().none()
.and()
.logout()
.logoutSuccessHandler(customLogoutSuccessHandler())
.invalidateHttpSession(true)
.deleteCookies("JSESSIONID");
}
}
Please verify the code that you're sharing because as you've mentioned. A user with role user is not supposed to access /ptaas/v1/profiles. But when I try to access that endpoint by logging in as user.
Where your mapping says you've not configured access to user role as given.
.antMatchers( "/taas/v1/teams", "/taas/v1/tests/summary").hasRole("user")
As per your comments it should have been .antMatchers( "/taas/v1/teams", "/taas/v1/tests/summary", "/ptaas/v1/profiles").hasRole("user")
I have a few different APIs in my spring webflux application that need to respond differently to failed authentication. I am trying to set different ServerAuthenticationEntryPoints for each API to handle these cases.
I found this example configuration that shows how to configure different AuthenticationWebFilter for different resources, which enables you to set ServerAuthenticationSuccessHandler and ServerAuthenticationFailureHandler individually, however I am not sure how to configure different ServerAuthenticationEntryPoints without having completely separate SecurityWebFilterChains.
If I have to configure separate SecurityWebFilterChains, how would I do that?
My SecurityWebFilterChain is currently configured like this - unfortunately you can't set the exceptionHandling individually and the second call to authenticationEntryPoint takes precedent:
#Bean
fun securityWebFilterChain(
http: ServerHttpSecurity,
userServerAuthenticationEntryPoint: ServerAuthenticationEntryPoint,
userAuthenticationWebFilter: AuthenticationWebFilter,
deviceServerAuthenticationEntryPoint: ServerAuthenticationEntryPoint,
deviceAuthenticationWebFilter: AuthenticationWebFilter,
serverSecurityContextRepository: ServerSecurityContextRepository,
authenticationManager: ReactiveAuthenticationManager,
serverAccessDeniedHandler: ServerAccessDeniedHandler
): SecurityWebFilterChain {
http
.addFilterAt(userAuthenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.exceptionHandling()
.authenticationEntryPoint(userServerAuthenticationEntryPoint)
.and()
.authorizeExchange()
.pathMatchers(GET, "/sign-in").permitAll()
.pathMatchers("/authentication/**").permitAll()
.pathMatchers(GET, "/landing").hasAnyAuthority("USER", "ADMIN")
.pathMatchers("/user-api/**").hasAnyAuthority("USER", "ADMIN")
http
.addFilterAt(deviceAuthenticationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
.exceptionHandling()
.authenticationEntryPoint(deviceServerAuthenticationEntryPoint)
.and()
.authorizeExchange()
.pathMatchers("/device-api/**").hasAuthority("DEVICE")
// GLOBAL
http
.httpBasic().disable()
.formLogin().disable()
.csrf().disable()
.cors().disable()
.securityContextRepository(serverSecurityContextRepository)
.authenticationManager(authenticationManager)
.exceptionHandling()
.accessDeniedHandler(serverAccessDeniedHandler)
.and()
.authorizeExchange()
.pathMatchers(GET, "/webjars/**").permitAll()
.pathMatchers(GET, "/assets/**").permitAll()
.anyExchange().authenticated()
return http.build()
}
It turns out the default ServerAuthenticationEntryPoint is a DelegatingServerAuthenticationEntryPoint, which enables you to configure via ServerWebExchangeMatchers which actual entry point is responsible for any given ServerWebExchange. See this comment.
This question already has answers here:
Spring Security : Multiple HTTP Config not working
(2 answers)
Closed 3 years ago.
I have Spring Boot configuration which looks something like this:
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterBefore( new Filter(), UsernamePasswordAuthenticationFilter.class)
.csrf().disable() // Disabled, cause enabling it will cause sessions
.headers()
.frameOptions()
.sameOrigin()
.addHeaderWriter(new XXssProtectionHeaderWriter())
.and()
.authorizeRequests()
.antMatchers("/app/**", "/rest/**").hasAuthority(DefaultPrivileges.ACCESS_TASK)
.anyRequest().permitAll();
My understanding was only the requests which start with /app or /rest will be intercepted by my custom filter but it turns out the requests to the root (http://localhost:8080/context/) are also intercepted.
I have multiple configurations for Spring Security the other configuration looks like this:
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.csrf().disable();
if (taskAppProperties.isRestEnabled()) {
if (restAppProperties.isVerifyRestApiPrivilege()) {
http
.antMatcher("/*-api/**")
.authorizeRequests()
.antMatchers("/*-api/**").hasAuthority(DefaultPrivileges.ACCESS_REST_API)
.and()
.httpBasic();
} else {
http
.antMatcher("/*-api/**")
.authorizeRequests()
.antMatchers("/*-api/**").authenticated()
.and()
.httpBasic();
}
} else {
http
.antMatcher("/*-api/**")
.authorizeRequests()
.antMatchers("/*-api/**").denyAll();
}
Can anyone help?
I realize this is a bit confusing, but there are actually two antMatchers methods, one that branches from authorizedRequests and another that branches from requestMatchers.
Let's look at the following declaration:
http
.requestMatchers()
.antMatchers("/app/**", "/api/**")
.and()
.authorizeRequests()
.antMatchers("...").authenticated()
...
The requestMatchers() DSL is where you describe the endpoints that matter to that instance of the Spring Security filter chain. So, this filter chain will only engage for URIs that start with /app or /api.
Let's take a look at another one:
http
.authorizeRequests()
.antMatchers("/app/**", "/api/**")
.authenticated();
While this may appear to be doing the same thing, it isn't. That's because you are calling the antMatchers method that belongs to authorizeRequests().
This is why indentation is important with the Spring Security DSL. Because there's a hierarchy in the DSL, then you want to indent, just like you want to indent inside your if statements.
In Spring Security 5.2, this is simplified a bit with the new lambda DSL:
http
.requestMatchers(r -> r.antMatchers("/app/**", "/api/**"))
.authorizeRequests(a -> a.antMatchers("...").authenticated());
HttpSecurity.authorizeRequests - returns ExpressionInterceptUrlRegistry where we are setting Matchers and Roles condition, which will be added using method ExpressionInterceptUrlRegistry.getRegistry and if you check other usage of this method only at permitAll stub where actual authentication happens.
The filter we add using HttpSecurity.addFilterBefore will not check any Request matching. If you need, you can do one more check in your custom filter to avoid other URIs
http
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.addFilterAfter( new Filter() {
#Override
public void init(FilterConfig filterConfig) throws ServletException {
}
#Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = ((HttpServletRequest) request);
if(httpServletRequest.getRequestURI().startsWith("/app/") || httpServletRequest.getRequestURI().startsWith("/rest/")) {
// Do you secured filter computations
}
chain.doFilter(request, response);
}
#Override
public void destroy() {
}}, UsernamePasswordAuthenticationFilter.class)
.csrf()
.disable() // Disabled, cause enabling it will cause sessions
.headers()
.frameOptions()
.sameOrigin()
.addHeaderWriter(new XXssProtectionHeaderWriter())
.and()
.authorizeRequests()
.antMatchers("/app/**", "/rest/**")
.hasAuthority(DefaultPrivileges.ACCESS_TASK)
.anyRequest()
.permitAll();