Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Springboot App returns 401 instead of 500

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.

like image 723
asds_asds Avatar asked Oct 16 '25 09:10

asds_asds


2 Answers

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

like image 110
vemahendran Avatar answered Oct 17 '25 22:10

vemahendran


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.

like image 25
Lodewijck Avatar answered Oct 17 '25 21:10

Lodewijck



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!