I recently migrated from SpringBoot 2.7.4 to SpringBoot 3.1.0.
I made some changes in the Security Config as some of the classes were deprecated.
Everything works alright but any error that is raised in the Spring App Controller is not returned to the caller. The response is always 401 Unauthorized.
I'm not even sure which files to share as this is extremely weird.
Any help is appreciated
Even endpoints marked as ignored return with a 401 in case of an exception.
@EnableWebSecurity
@Configuration
public class SecurityConfig {
@Value( "${auth0.audience}" )
private String audience;
@Value("${spring.security.oauth2.resourceserver.jwt.issuer-uri}")
private String issuer;
@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
return (web) -> web.ignoring().requestMatchers("/v1/noauth/**", "/actuator/**");
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests((authz) -> authz
.requestMatchers("/v1/**").authenticated()
)
.oauth2ResourceServer((oauth2) -> oauth2
.jwt((jwt) -> jwt
.decoder(jwtDecoder())
)
)
.cors(Customizer.withDefaults())
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
@Bean
JwtDecoder jwtDecoder() {
/*
By default, Spring Security does not validate the "aud" claim of the token, to ensure that this token is
indeed intended for our app. Adding our own validator is easy to do:
*/
NimbusJwtDecoder jwtDecoder = (NimbusJwtDecoder)
JwtDecoders.fromOidcIssuerLocation(issuer);
OAuth2TokenValidator<Jwt> audienceValidator = new AudienceValidator(audience);
OAuth2TokenValidator<Jwt> withIssuer = JwtValidators.createDefaultWithIssuer(issuer);
OAuth2TokenValidator<Jwt> withAudience = new DelegatingOAuth2TokenValidator<>(withIssuer, audienceValidator);
jwtDecoder.setJwtValidator(withAudience);
return jwtDecoder;
}
}
I also have a Filter for configuring RequestId for each request
@Data
@EqualsAndHashCode(callSuper = false)
public class Slf4jMDCFilter extends OncePerRequestFilter {
private final String responseHeader;
private final String mdcTokenKey;
private final String requestHeader;
public Slf4jMDCFilter(String responseHeader, String mdcTokenKey, String requestHeader){
this.responseHeader = responseHeader;
this.mdcTokenKey = mdcTokenKey;
this.requestHeader = requestHeader;
}
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain)
throws java.io.IOException, ServletException {
try {
final String token;
if (StringUtils.hasText(requestHeader) && StringUtils.hasText(request.getHeader(requestHeader))) {
token = URLEncoder.encode(request.getHeader(requestHeader));
} else {
token = UUID.randomUUID().toString().toUpperCase();
}
MDC.put(mdcTokenKey, token);
if (StringUtils.hasText(responseHeader)) {
response.addHeader(responseHeader, token);
}
chain.doFilter(request, response);
} finally {
MDC.remove(mdcTokenKey);
}
}
}
which is used as follows
@Configuration
public class Slf4jMDCFilterConfiguration {
public static final String DEFAULT_RESPONSE_TOKEN_HEADER = "ResponseToken";
private final String responseHeader = DEFAULT_RESPONSE_TOKEN_HEADER;
private final String mdcTokenKey = Util.DEFAULT_MDC_UUID_TOKEN_KEY();
@Value( "${config.slf4jfilter.requestHeader}" )
private String requestHeader;
@Bean
public FilterRegistrationBean servletRegistrationBean() {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean();
final Slf4jMDCFilter log4jMDCFilterFilter = new Slf4jMDCFilter(responseHeader, mdcTokenKey, requestHeader);
registrationBean.setFilter(log4jMDCFilterFilter);
registrationBean.setOrder(2);
return registrationBean;
}
}
I confirmed that earlier this was not happening and a 500 with INTERNAL_SERVER_ERROR was returned.
Replacing authorizeHttpRequests
with authorizeRequests
didn't fix the issue for me.
In the latest Spring Boot 3.x version, authorizeRequests
is a deprecated one. By default, Spring Boot 3 provides /error
mapping to handle all the exceptions. We need to ask Spring Security to permit /error
mapping. Then Spring Security will allow us to get the actual errors with appropriate HTTP status codes.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.cors().and()
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/error").permitAll()
.requestMatchers("/auth**").permitAll()
.anyRequest().authenticated())
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.userDetailsService(jpaUserDetailsService)
.build();
}
For more details, please refer this stackoverflow answer
I had a similar problem in a slightly different setup, but I've found out that Spring somehow tried to render an error page that needed authentication.
By enabling DEBUG logs in my application.yml:
logging:
level:
org:
springframework:
web: DEBUG
it showed me some logs related to error handling:
...
GET...
Mapped to...
Resolved...
Completed 500 INTERNAL_SERVER_ERROR
Mapped to org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController#error(HttpServletRequest)
"ERROR" dispatch for GET "/error", parameters={}
...
So I fixed it by disabling authentication for /error/** patterns.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With