I've recently upgraded spring boot from 2.x.x version to 3.3.3 and spring security v.6.4.3 and faced an issue when calling my /api/v1/authentication/login
endpoint. When the user is authenticated with oauth2 and should be redirected to the endpoint, a NoResourceFoundException is thrown. For some reason DispatcherServlet is classyfing the request as a request for a static resource, instead of a dynamic endpoint. I'm attaching related logs below.
This endpoint is oauth protected and defined as
@Slf4j
@Validated
@RestController
@Tag(name = "Authentication")
@RequestMapping("/api/v1/authentication")
public class AuthenticationControllerBase {
protected final UserService userService;
private final ObjectMapper objectMapper;
public AuthenticationControllerBase(UserService userService, ObjectMapper objectMapper) {
log.info("Initializing AuthenticationControllerBase");
this.userService = userService;
this.objectMapper = objectMapper;
}
@PostMapping(value = "/login")
public LoginResponse login(@AuthenticationPrincipal OidcUser principal) throws StatusManagementException {
String email = principal.getUserInfo().getEmail();
email = EmailUtil.normalizeEmail(email);
User user = userService.getUser(email);
return user.getRole() == UserRole.APPLICATION_ADMIN
? userService.loginWithLock(email)
: userService.loginWithoutLock(email);
}
}
My Security configuration is
package com.kyc.service.config;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import .springframework.beans.factory.annotation.Value;
import .springframework.context.annotation.Bean;
import .springframework.context.annotation.Configuration;
import .springframework.context.annotation.Profile;
import .springframework.security.config.annotation.web.builders.HttpSecurity;
import .springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import .springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import .springframework.security.web.SecurityFilterChain;
import .springframework.security.web.authentication.session.SessionAuthenticationStrategy;
import .springframework.security.web.session.InvalidSessionStrategy;
import .springframework.security.web.session.SessionInformationExpiredEvent;
import .springframework.security.web.session.SessionInformationExpiredStrategy;
import .springframework.security.web.util.matcher.AntPathRequestMatcher;
import java.io.IOException;
@Slf4j
@Configuration
@Profile({"pl-demo", "pl-stage"})
@RequiredArgsConstructor
@EnableWebSecurity
public class OIDCSecurityConfigurationPl {
@Value("${service.configuration.oidc.auth-redirection-uri}")
private String authorizeRedirectionURI;
@Value("${service.configuration.oidc.logout-redirection-url}")
private String logoutRedirectionURL;
@Value("${service.configuration.oidc.logout-uri}")
private String logoutURI;
private final SessionAuthenticationStrategy sessionAuthenticationStrategy;
@Bean
protected SecurityFilterChain configure(HttpSecurity http) {
try {
http.csrf(AbstractHttpConfigurer::disable);
http.cors(AbstractHttpConfigurer::disable);
http.authorizeHttpRequests(authorizeRequests -> authorizeRequests.requestMatchers("/", "/error", "/actuator/health",
"/oauth2/authorization/**", "/api/v1/identity-verification/webhook",
"/api/v1/engagements/*/export/*/pdf")
.permitAll()
.anyRequest()
.authenticated());
setupOauth(http);
http.sessionManagement(sess -> sess.sessionAuthenticationStrategy(sessionAuthenticationStrategy)
.requireExplicitAuthenticationStrategy(false)
.invalidSessionStrategy(new KYCSessionExpirationStrategy()).maximumSessions(1));
http.logout(logout -> logout.logoutRequestMatcher(new AntPathRequestMatcher(logoutURI))
.invalidateHttpSession(true)
.logoutSuccessUrl(logoutRedirectionURL));
return http.build();
} catch (Exception e) {
log.error("Exception in OIDC configuration", e);
throw new SecurityException(e);
}
}
private void setupOauth(HttpSecurity http) {
try {
http.oauth2Login(oauth -> oauth.redirectionEndpoint(redir -> redir.baseUri(authorizeRedirectionURI)));
} catch (Exception e) {
log.error("oauth2Login:: Exception in oauth2Login configuration", e);
}
}
private static class KYCSessionExpirationStrategy implements SessionInformationExpiredStrategy, InvalidSessionStrategy {
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent sessionInformationExpiredEvent)
throws IOException {
sessionInformationExpiredEvent.getResponse().sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
@Override
public void onInvalidSessionDetected(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse)
throws IOException {
log.info("invalid session...");
httpServletResponse.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}
2025-04-03 15:36:00
.springframework.web.servlet.resource.NoResourceFoundException: No static resource api/v1/authentication/login.
2025-04-03 15:36:00
2025-04-03 13:36:00.546 ERROR 31 --- 34 [ XNIO-1 task-2] c.p.k.s.a.e.GlobalExceptionHandler : Internal server error
2025-04-03 15:36:00
2025-04-03 13:36:00.546 DEBUG 31 --- 34 [ XNIO-1 task-2] .m.m.a.ExceptionHandlerExceptionResolver : Using @ExceptionHandler com.pwc.kyc.service.api.exception.GlobalExceptionHandler#handleAll(Exception)
2025-04-03 15:36:00
2025-04-03 13:36:00.545 DEBUG 31 --- 34 [ XNIO-1 task-2] o.s.w.s.r.ResourceHttpRequestHandler : Resource not found
2025-04-03 15:36:00
2025-04-03 13:36:00.542 DEBUG 31 --- 34 [ XNIO-1 task-2] o.s.w.s.h.SimpleUrlHandlerMapping : Mapped to ResourceHttpRequestHandler [classpath [META-INF/resources/], classpath [resources/], classpath [static/], classpath [public/], ServletContext [/]]
2025-04-03 15:36:00
2025-04-03 13:36:00.541 DEBUG 31 --- 34 [ XNIO-1 task-2] o.s.w.s.DispatcherServlet : POST "/stage/api/v1/authentication/login/", parameters={}
2025-04-03 15:36:00
2025-04-03 13:36:00.540 DEBUG 31 --- 34 [ XNIO-1 task-2] c.p.k.s.c.f.ConcurrentAdminSessionFilter : All sessions det size: 1
2025-04-03 15:36:00
2025-04-03 13:36:00.540 DEBUG 31 --- 34 [ XNIO-1 task-2] c.p.k.s.c.f.ConcurrentAdminSessionFilter : All principals size: 1
2025-04-03 15:36:00
2025-04-03 13:36:00.531 DEBUG 31 --- 34 [ XNIO-1 task-2] tor$SharedEntityManagerInvocationHandler : Creating new EntityManager for shared EntityManager invocation
2025-04-03 15:36:00
2025-04-03 13:36:00.529 DEBUG 31 --- 34 [ XNIO-1 task-2] o.s.s.w.FilterChainProxy : Secured POST /api/v1/authentication/login/
2025-04-03 15:36:00
2025-04-03 13:36:00.528 DEBUG 31 --- 34 [ XNIO-1 task-2] w.c.HttpSessionSecurityContextRepository : Retrieved SecurityContextImpl [Authentication=OAuth2AuthenticationToken [PROTECTED]]