Spring Security抛出异常后重定向至formLogin页面问题求助
问题描述
调用注册接口http://localhost:8080/api/v1/registration/register创建用户时,首次调用可成功创建;但重复提交相同用户信息时,服务器日志正常抛出email already taken错误,程序却未返回错误信息,而是重定向至/login端点。
相关代码
WebSecurityConfig 配置
package com.example.venuematchingservice.security.config; import com.example.venuematchingservice.appuser.AppUserService; import lombok.AllArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @AllArgsConstructor @EnableWebSecurity public class WebSecurityConfig { private final AppUserService appUserService; private final BCryptPasswordEncoder bCryptPasswordEncoder; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf(csrf -> csrf .disable()) .authorizeHttpRequests(auth -> auth .requestMatchers("/api/v1/registration/**", "*").permitAll() .anyRequest().authenticated()) .formLogin(Customizer.withDefaults()); return http.build(); } @Bean public WebSecurityCustomizer webSecurityCustomizer() { return (web) -> web.ignoring().requestMatchers("/js/**", "/images/**"); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider(); authProvider.setUserDetailsService(appUserService); authProvider.setPasswordEncoder(bCryptPasswordEncoder); return authProvider; } }
AppUserService 类代码
package com.example.venuematchingservice.appuser; import com.example.venuematchingservice.registration.token.ConfirmationToken; import com.example.venuematchingservice.registration.token.ConfirmationTokenService; import lombok.AllArgsConstructor; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import java.time.LocalDateTime; import java.util.UUID; @Service @AllArgsConstructor public class AppUserService implements UserDetailsService { private final AppUserRepository appUserRepository; private final static String USER_NOT_FOUND = "user with email %s not found"; private final BCryptPasswordEncoder bCryptPasswordEncoder; private ConfirmationTokenService confirmationTokenService; @Override public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException { return appUserRepository.findByEmail(email).orElseThrow(() -> new UsernameNotFoundException(String.format(USER_NOT_FOUND, email))); } public String signUpUser(AppUser appUser){ boolean userExists = appUserRepository.findByEmail(appUser.getEmail()).isPresent(); if (userExists){ throw new IllegalStateException("email already taken"); } String encodedPassword = bCryptPasswordEncoder.encode(appUser.getPassword()); appUser.setPassword(encodedPassword); appUserRepository.save(appUser); String token = UUID.randomUUID().toString(); ConfirmationToken confirmationToken = new ConfirmationToken( token, LocalDateTime.now(), LocalDateTime.now().plusMinutes(15), appUser ); confirmationTokenService.saveConfirmationToken(confirmationToken); return token; } public int enableAppUser(String email) { return appUserRepository.enableAppUser(email); } }
问题原因及解决办法
原因解析
- 异常触发默认错误处理后被拦截:当
AppUserService抛出IllegalStateException未被捕获时,Spring Boot会将请求转发到/error端点。但你的Security配置中anyRequest().authenticated()要求所有未明确放行的请求必须认证,而/error不在放行列表里,所以Spring Security会将请求重定向到登录页面。 - 请求匹配规则错误:配置里的
*只匹配当前路径下的一级资源,无法覆盖/error这类端点,导致异常处理时的请求被拦截。
解决方案
1. 修正Security的请求放行规则
更新WebSecurityConfig中的authorizeHttpRequests配置,明确放行/error端点,同时去掉无效的*匹配:
.authorizeHttpRequests(auth -> auth .requestMatchers("/api/v1/registration/**", "/error").permitAll() .anyRequest().authenticated())
2. 添加全局异常处理器
创建全局异常处理类,直接返回异常信息的HTTP响应,避免触发默认的/error转发:
import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(IllegalStateException.class) public ResponseEntity<String> handleDuplicateEmail(IllegalStateException ex) { return new ResponseEntity<>(ex.getMessage(), HttpStatus.CONFLICT); } }
3. 适配纯API场景(可选)
如果你的应用是纯API服务,不需要表单登录页面,可以修改formLogin配置,禁用默认登录页面,返回JSON格式的认证错误:
.formLogin(form -> form .loginPage("/api/v1/auth/login") // 替换为你的自定义登录API .failureHandler((request, response, exception) -> { response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setContentType("application/json"); response.getWriter().write("{\"error\": \"认证失败\"}"); }) .permitAll())
内容的提问来源于stack exchange,提问作者Ricardo Blohm
相关产品推荐
相关产品推荐

