Spring Boot控制器未被调用
我们正在升级到Spring Boot 3.5.5,因此有些代码是历史遗留的。
我们有一个中心门户应用,用户可以在其中登录。它根据不同用户提供不同的登录方式。Spring Boot框架(3.5.5)的所有通用配置都放在一个公用共享库中,与其他应用保持一致(那些应用运行良好)。
问题:
门户应用启动正常。日志中没有记录任何错误或异常。
当在浏览器中打开应用时按预期会跳转到 /auth/login,但页面并未显示。用于返回登录页的控制器根本没有被调用。
@Configuration
@EnableWebSecurity()
@Profile(value = { Constants.AUTH_STRATEGY_CUSTOM })
public class CustomStrategyConfiguration {
private static final Logger logger = LoggerFactory.getLogger(CustomStrategyConfiguration.class);
public static final String AUTHENTICATION_FAILURE_URL = "/auth/login";
public static final String AUTHENTICATION_SUCCESS_URL = "/dashboard/";
@Autowired
private MultiProviderAuthenticationManager authenticationManager;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
HttpSessionRequestCache requestCache = new HttpSessionRequestCache();
requestCache.setMatchingRequestParameterName(null);
http
//.securityMatcher("/**")
.authenticationManager(authenticationManager)
.authorizeHttpRequests((authorizeHttpRequests)
-> authorizeHttpRequests
.requestMatchers("/resources/**","/static/**", "/img/**", "/bootstrap/**", "/uikit/**", "/**.js", "/**.css").permitAll()
.requestMatchers("/auth/login", "/auth/login/","/auth/login/**", "/login","/auth/**","/auth/login/verify","/auth/login?error", "/services/uiStatus/**").permitAll()
.requestMatchers("/profile/password/forgotPassword","/profile/password/forgotPassword/**","/profile/password/forgotPassword.do","/profile/password/resetForgottenPassword/**","/profile/password/resetForgottenPassword.do").permitAll()
.requestMatchers("/META-INF/MANIFEST.MF","/META-INF/**").denyAll()
.requestMatchers("/oauth/authorize").authenticated()
.anyRequest().authenticated()
)
.sessionManagement((sessionsManagement) -> sessionsManagement.sessionFixation().migrateSession().maximumSessions(1))
.requestCache((rc) -> rc.requestCache(requestCache))
.formLogin((formLogin)
-> formLogin
.loginPage("/auth/login")
.loginProcessingUrl("/auth/login/verify")
.defaultSuccessUrl(AUTHENTICATION_SUCCESS_URL, true)
.failureUrl(AUTHENTICATION_FAILURE_URL).permitAll()
.usernameParameter("usernamme")
.passwordParameter("password"))
.exceptionHandling((e) -> e.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/auth/login")))
.logout((logout)
-> logout
.logoutUrl("/auth/logout")
.logoutSuccessUrl("/loggedOut")
.addLogoutHandler(logoutAuditListener()))
.sessionManagement((sessionManagement)
-> sessionManagement
.sessionFixation((sfc) -> sfc.migrateSession())
.maximumSessions(1)
.sessionRegistry(sessionRegistry()))
.headers((headers)
-> headers
.xssProtection((xss) -> xss.disable())
.frameOptions((frameOptions) -> frameOptions.disable())
.contentSecurityPolicy(csp -> csp.policyDirectives(
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " +
"style-src 'self' 'unsafe-inline'; " +
"object-src 'none'; " +
"img-src 'self'; " +
"frame-ancestors 'self';")
))
.csrf((csrf) -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
//.oauth2ResourceServer(oauth2 -> oauth2.opaqueToken(otc -> otc.introspectionUri("/login").introspectionClientCredentials("test", "test")))
.addFilterAfter(authenticationTokenParsingFilter(), AbstractPreAuthenticatedProcessingFilter.class)
;
return http.build();
}
@Bean
public SessionRegistry sessionRegistry() {
return new SessionRegistryImpl();
}
@Bean
public LogoutAuditListener logoutAuditListener() {
return new LogoutAuditListener();
}
}
@Component
public class MultiProviderAuthenticationManager implements AuthenticationManager{
@Autowired
private AuthenticationMethodRegistry authenticationMethodRegistry;
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
for (AuthenticationMethod method : authenticationMethodRegistry.getAuthenticationMethods()) {
if(method == null || method.getAuthenticationProvider() == null) {
continue;
}
AuthenticationProvider provider = method.getAuthenticationProvider();
if (provider.supports(authentication.getClass())) {
Authentication result = provider.authenticate(authentication);
if (result != null) {
return result;
}
}
}
return null;
}
}
@Component
public class AuthenticationTokenParsingFilter extends AbstractAuthenticationProcessingFilter {
@Autowired
private AuthenticationMethodRegistry methodRegistry;
@Autowired
private AuthenticationEventPublisher authenticationEventPublisher;
public AuthenticationTokenParsingFilter(AuthenticationManager authenticationManager) {
super("/auth/login/verify");
setAuthenticationSuccessHandler(new SimpleUrlAuthenticationSuccessHandler(CustomStrategyConfiguration.AUTHENTICATION_SUCCESS_URL));
setAuthenticationManager(authenticationManager);
SimpleUrlAuthenticationFailureHandler handler = new SimpleUrlAuthenticationFailureHandler();
handler.setDefaultFailureUrl(CustomStrategyConfiguration.AUTHENTICATION_FAILURE_URL);
}
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {
Collection<AuthenticationMethod> methods = methodRegistry.getAuthenticationMethods();
Authentication auth = null;
try {
for (AuthenticationMethod method : methods) {
if (!method.isEnabledGlobal()) {
// skip evaluation if it is disabled per configuration
continue;
}
AbstractAuthenticationProcessingFilter filter = method.getFilter();
if (filter != null) {
auth = filter.attemptAuthentication(request, response);
if (auth != null) {
// publish authentication event
authenticationEventPublisher.publishAuthenticationSuccess(auth);
break;
}
}
}
} catch (AuthenticationException ex) {
authenticationEventPublisher.publishAuthenticationFailure(ex, auth);
throw ex;
}
return auth;
}
}
@SpringBootApplication(exclude = { JerseyServerMetricsAutoConfiguration.class })
@EnableScheduling
@ComponentScan(value = "my.company")
@EnableJpaRepositories(value = "my.company", repositoryBaseClass = BasemRepositoryImpl.class)
@EnableMethodSecurity(proxyTargetClass = true)
@Configuration()
public class PlatformApplication extends SpringBootServletInitializer {
....
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
builder.logStartupInfo(true);
List<String> profiles = getActiveProfiles();
profiles.add(PlatformApplication.SPRING_PROFILE_RUNTIME);
profiles.add(getAuthenticationStrategyProfile());
String[] profilesArray = new String[profiles.size()];
profilesArray = profiles.toArray(profilesArray);
builder.profiles(profilesArray);
builder.listeners(new CustomApplicationListener());
return builder;
}
}
解决方案
// REMOVE THIS
.exceptionHandling(e ->
e.authenticationEntryPoint(
new LoginUrlAuthenticationEntryPoint("/auth/login")
)
)
When a request is unauthenticated or the user provides invalid credentials, Spring Security dispatches the request to the configured AuthenticationEntryPoint (/auth/login). When a user initially accesses the web application, no authentication is present in the security context, so the framework redirects the request to the authentication entry point. However, since authentication is still missing when /auth/login itself is requested, the same condition occurs again, causing the AuthenticationEntryPoint to be invoked repeatedly. This results in an infinite redirect loop, preventing the login controller from ever being reached.