Spring Security in Spring Boot 3 - spring-boot

I'm currently in the process of migrating our REST application from Spring Boot 2.7.5 to 3.0.0-RC2. I want everything to be secure apart from the Open API URL. In Spring Boot 2.7.5, we used to do this:
#Named
#EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/openapi/openapi.yml").permitAll()
.anyRequest().authenticated()
.and()
.httpBasic();
}
}
and it worked fine. In Spring Boot 3, I had to change it to
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.authorizeHttpRequests((requests) -> requests
.requestMatchers("/openapi/openapi.yml").permitAll()
.anyRequest()
.authenticated())
.httpBasic();
return http.build();
}
}
since WebSecurityConfigurerAdapter has been removed. It's not working though. The Open API URL is also secured via basic authentication. Have I made a mistake when upgrading the code or is that possibly an issue in Spring Boot 3 RC 2?
Update
Since most of the new API was already available in 2.7.5, I've updated our code in our 2.7.5 code base to the following:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests((requests) -> requests
.antMatchers(OPTIONS).permitAll() // allow CORS option calls for Swagger UI
.antMatchers("/openapi/openapi.yml").permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
}
In our branch for 3.0.0-RC2, the code is now as follows:
#Configuration
#EnableWebSecurity
public class WebSecurityConfig {
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests((requests) -> requests
.requestMatchers(OPTIONS).permitAll() // allow CORS option calls for Swagger UI
.requestMatchers("/openapi/openapi.yml").permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
}
As you can see, the only difference is that I call requestMatchers instead of antMatchers. This method seems to have been renamed. The method antMatchers is no longer available. The end effect is still the same though. On our branch for 3.0.0-RC2, Spring Boot asks for basic authentication for the OpenAPI URL. Still works fine on 2.7.5.

Author: https://github.com/wilkinsona
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((requests) -> requests
.requestMatchers(new AntPathRequestMatcher("/openapi/openapi.yml")).permitAll()
.anyRequest().authenticated())
.httpBasic();
return http.build();
}
Source: https://github.com/spring-projects/spring-boot/issues/33357#issuecomment-1327301183
I recommend you use Spring Boot 3.0.0 (GA) right now, not RC version.

Inside my WebSecurityConfig, I did this:
private static final String[] AUTH_WHITELIST = {
// -- Swagger UI v2
"/v2/api-docs",
"v2/api-docs",
"/swagger-resources",
"swagger-resources",
"/swagger-resources/**",
"swagger-resources/**",
"/configuration/ui",
"configuration/ui",
"/configuration/security",
"configuration/security",
"/swagger-ui.html",
"swagger-ui.html",
"webjars/**",
// -- Swagger UI v3
"/v3/api-docs/**",
"v3/api-docs/**",
"/swagger-ui/**",
"swagger-ui/**",
// CSA Controllers
"/csa/api/token",
// Actuators
"/actuator/**",
"/health/**"
};
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests( auth -> auth
.requestMatchers(AUTH_WHITELIST).permitAll()
.anyRequest().authenticated()
)
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.httpBasic(withDefaults())
.addFilterBefore(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
//.addFilterAfter(authenticationJwtTokenFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
#Bean
public SecurityFilterChain configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.authorizeHttpRequests((requests) -> requests
.requestMatchers( new AntPathRequestMatcher("swagger-ui/**")).permitAll()
.requestMatchers( new AntPathRequestMatcher("/swagger-ui/**")).permitAll()
.requestMatchers( new AntPathRequestMatcher("v3/api-docs/**")).permitAll()
.requestMatchers( new AntPathRequestMatcher("/v3/api-docs/**")).permitAll()
.anyRequest().authenticated())
.httpBasic();
return httpSecurity.build();
}
This and using Dockerfile (doing mvn clean package and running .jar from Docker) made me had no issues with authentication inside swagger ui.
Hope this can help you :)

Use
http.securityMatcher("<patterns>")...
to specify authentication for endpoints.
authorizeHttpRequests((requests) -> requests
.requestMatchers("<pattern>")
only works for authorization, if you don't set securityMatcher , SecurityFilterChain by default gets any request for authentication. And any request will be authenticated by an authentication provider.
In your case, you can define two security filter, chains: one for public endpoitns, another for secured. And give them proper order:
#Bean
#Order(1)
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.securityMatcher(OPTIONS,"/openapi/openapi.yml").csrf().disable()
.authorizeHttpRequests((requests) -> requests
.anyRequest().permitAll() // allow CORS option calls for Swagger UI
);
return http.build();
}
#Bean
Order(2)
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http.securityMatcher("/**")
.csrf().disable()
.authorizeHttpRequests((requests) -> requests.anyRequest().authenticated())
.httpBasic();
return http.build();
}

The official documentation suggests an example which I have abridged here with your config:
http
.authorizeExchange((exchanges) ->
exchanges
.pathMatchers("/openapi/openapi.yml").permitAll()
.anyExchange().authenticated())
.httpBasic();
return http.build();
You could try this, since it changes the "request" for the "exchange" wording, in line with the migration to declarative clients (#PostExchange vs. #PostMapping) I suppose. Hope it helps.

My security cfg looks like:
Spring 3.0.0
#Bean
public SecurityFilterChain configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(requests -> requests
.requestMatchers(HttpMethod.GET, "/", "/static/**", "/index.html", "/api/users/me").permitAll()
.requestMatchers(HttpMethod.POST, "/api/users").permitAll()
.requestMatchers(HttpMethod.GET, "/api/users/login", "/api/users/{username}", "/api/users/logout", "/api/costumers", "/api/storages").authenticated()
.requestMatchers(HttpMethod.POST, "/api/costumers", "/api/storages").authenticated()
.requestMatchers(HttpMethod.PUT, "/api/costumers/{id}", "/api/storages/{id}").authenticated()
.requestMatchers(HttpMethod.DELETE, "/api/users/{id}", "/api/storages/{id}", "/api/costumers/{id}").authenticated()
.anyRequest().denyAll())
.httpBasic();
return http.build();
}
it works

This seems to be a bug in Spring Boot 3. I've raised an issue.

Related

Spring Security Context Authentication is null

i am trying to add couple of filters in my request processing in spring boot security config.
Below is my code
#EnableWebSecurity
#Configuration
public class JwtSecurityConfiguration {
#Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(this::configureEndpoints)
return http.build();
}
private void configureEndpoints(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry authorizationManagerRequestMatcherRegistry){
authorizationManagerRequestMatcherRegistry.mvcMatchers("/permit")
.permitAll()
.mvcMatchers("/block")
.denyAll()
.and()
.mvcMatcher("/api")
.addFilterBefore(new Filter1(), SecurityContextHolderAwareRequestFilter.class)
// register TenantFilter in the chain after the SecurityContext is made available by the respective filter
.mvcMatcher("/api")
.addFilterAfter(new Filter2(), SecurityContextHolderAwareRequestFilter.class)
.authorizeHttpRequests()
.mvcMatchers("/api")
.authenticated()
.and();
}
}
It seems the authentication does not happen and filters are never hit.
If i try to access the authentication in my runtime code i get SecurityContextHolder.getContext().getAuthentication() as null.
Seems to some problem in the security configuration only.

why is the AuthenticationManagerResolver called on a permitAll() (open endpoint)

Our spring boot 2.5.12 app is secured w/ a security configuration like this:
protected void configure(HttpSecurity http) throws Exception {
http
.cors().configurationSource(corsConfigurationSource)
.and()
.csrf()
.ignoringAntMatchers("/")
.and()
.authorizeRequests(authorizeRequests -> authorizeRequests
.mvcMatchers(GET, "/endpoint").hasAuthority("SCOPE_" + (Scope.READ))
.mvcMatchers(GET, "/endpoint/{reference}").permitAll()
.mvcMatchers(GET, "/error").permitAll()
.mvcMatchers(GET, "/info").permitAll()
.mvcMatchers(GET, "/health").permitAll()
.anyRequest().denyAll())
.oauth2ResourceServer()
.authenticationManagerResolver(authenticationManager())
.accessDeniedHandler(accessDeniedHandler)
.authenticationEntryPoint(authenticationEntryPoint);
}
w/ an AuthenticationManagerResolverBean:
public AuthenticationManagerResolver<HttpServletRequest> authenticationManager() {
return request -> {
...
...
...
};
}
it looks as if there's a bug as when i access the endpoint: /endpoint/ref123 it calls the AuthenticationManagerResolver even though this endpoint is open with a .permitAll(). So in the case the user accidentally provides an invalid token on this .permitAll() endpoint they aren't rejected.
if an endpoint is a .permitAll() then shouldn't spring not try to validate the token?
I didn't quite find why this is the behavior but we did find a workaround of sorts.
public void configure(WebSecurity web) {
web
.ignoring()
.mvcMatchers(GET, "/endpoint/{reference}");
}
It gets spring security to ignore tokens all together... valid or otherwise (which is what i thought permitAll did).

Spring Security: don't redirect to login page in case unauthorised

I have Spring Security with oAuth2 authorisation.
I use it for REST API.
My configuration:
#Configuration
#EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
.csrf().disable()
.authorizeRequests()
.antMatchers("/health").permitAll()
.antMatchers("/**").authenticated()
.and()
.oauth2Login()
.and()
.httpBasic();
}
}
I need to make all requests return me 401 when I didn't authorise.
But now when I'm not authorised I got redirect to /login page.
I need to use it like usual REST API: if I did authorise then get content, otherwise get 401 Unauthorised.
How I can make it?
Thanks in addition for help.
Basically you need to configure an AuthenticationEntryPoint which is invoked when Spring Security detects a non-authenticated request. Spring also gives you a handy implementation which enables you to return whatever HttpStatus you need:
#Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
httpSecurity
//rest of your config...
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
}

Oauth2 security configuration antmatchers request filtering not working as expected

I am working on a simple spring boot project along with spring security oauth2 to use google authentication for a specified endpoint which is /google/login.
With following security configurations everything is working perfectly.
#Configuration
public class SecurityConfigure extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/**")
.and()
.authorizeRequests().antMatchers("/ldap/login").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.oauth2Login();
}
}
But I need to specify only /google/login endpoint to authenticate with oauth2. Therefore I specified it like this.
#Configuration
public class SecurityConfigure extends WebSecurityConfigurerAdapter {
#Override
public void configure(HttpSecurity http) throws Exception {
http
.requestMatchers().antMatchers("/google/**")
.and()
.authorizeRequests().antMatchers("/ldap/**").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.oauth2Login();
}
}
with this security configuration http://localhost:8080/google/login endpoint call redirects to another endpoint called http://localhost:8081/oauth2/authorization/google which is I haven't defined.
Please help me to overcome this problem. Thank you.
This configuration works for me. I had to allow all endpoints that were redirecting while Google's authentication process was running. 
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.requestMatchers().antMatchers("/google/**","/oauth2/authorization/google","/login/oauth2/code/google")
.and()
.authorizeRequests().antMatchers("/ldap/**").permitAll()
.anyRequest().fullyAuthenticated()
.and()
.oauth2Login();
}

Providing custom OAuth2AccessTokenResponseClient to spring

I have a demo spring boot app and I would like to configure oauth login as well my own custom token response client.
This is my configuration:
#Configuration
#EnableWebSecurity
class SecurityConfig extends WebSecurityConfigurerAdapter {
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
.anyRequest()
.authenticated()
.and()
.oauth2Login()
.and()
.oauth2Client()
.authorizationCodeGrant()
.accessTokenResponseClient(customAccessTokenResponseClient());
}
private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> customAccessTokenResponseClient() {
DefaultAuthorizationCodeTokenResponseClient client = new DefaultAuthorizationCodeTokenResponseClient();
client.setRequestEntityConverter(new CustomOAuth2AuthorizationCodeGrantRequestEntityConverter());
return client;
}
}
The login flow is working find, but it looks like my custom token response client is not getting registered. I put a break point in there, but the flow doesn't go through it. It looks like it is still using the defaults.
Am I missing something here or is the order wrong?
Figured out the issue.
The right way to configure is the following:
#Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests().anyRequest().authenticated()
.and()
.oauth2Login()
.tokenEndpoint()
.accessTokenResponseClient(customAccessTokenResponseClient());
}

Resources