自定义LoginFilter认证成功后为何无法进入Controller层?
自定义了LoginFilter继承UsernamePasswordAuthenticationFilter,完成认证后触发了登录成功回调,但请求无法进入/sso/login的Controller方法。Postman测试时仅返回认证成功回调的内容,无法执行Controller里生成Token并返回的逻辑。
Spring Security的UsernamePasswordAuthenticationFilter(你的LoginFilter继承自它)的设计逻辑是:拦截指定路径的请求完成认证后,通过配置的AuthenticationSuccessHandler直接向客户端输出响应,会终止整个请求链的执行。你将LoginFilter的filterProcessesUrl设置为/sso/login,和Controller的登录路径完全一致,导致请求被Filter拦截处理后直接返回,不会流转到Controller层。
方案一:将Token生成逻辑移至登录成功回调(推荐)
既然认证已经在Filter中完成,直接在MyAuthenticationSuccessHandler里调用服务生成Token并返回,无需再走Controller。这种方式更符合Spring Security的认证流程设计。
修改MyAuthenticationSuccessHandler:
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.io.PrintWriter; import java.util.HashMap; import java.util.Map; public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler { @Autowired private UmsAdminService umsAdminService; // 注入你的用户服务 private String tokenHead = "Bearer "; // 根据你的配置调整 @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { String username = authentication.getName(); // 认证已通过,无需再次校验密码,直接生成Token String token = umsAdminService.login(username, null); Map<String, String> tokenMap = new HashMap<>(); tokenMap.put("token", token); tokenMap.put("tokenHead", tokenHead); response.setContentType("application/json;charset=utf-8"); PrintWriter out = response.getWriter(); ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(new JavaTimeModule()); out.write(mapper.writeValueAsString(CommonResult.success(tokenMap))); out.flush(); out.close(); } }
同时可以删除Controller中对应的/sso/login方法,避免路径冲突。
方案二:修改LoginFilter,让请求继续流转到Controller
如果坚持要通过Controller处理返回,需要重写LoginFilter的successfulAuthentication方法,不调用SuccessHandler直接返回,而是继续执行请求链:
public class LoginFilter extends UsernamePasswordAuthenticationFilter { // 保留原有的构造方法和attemptAuthentication方法... @Override protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { // 将认证信息存入SecurityContext SecurityContextHolder.getContext().setAuthentication(authResult); // 继续执行后续Filter链,让请求到达Controller chain.doFilter(request, response); } }
同时,移除MyAuthenticationSuccessHandler的配置:
修改MyCustomDsl中的loginFilter方法:
LoginFilter loginFilter(AuthenticationManager authenticationManager) throws Exception { LoginFilter loginFilter = new LoginFilter(authenticationManager); loginFilter.setFilterProcessesUrl("/sso/login"); loginFilter.setUsernameParameter("username"); loginFilter.setPasswordParameter("password"); // 注释掉SuccessHandler配置 // loginFilter.setAuthenticationSuccessHandler(new MyAuthenticationSuccessHandler()); loginFilter.setAuthenticationFailureHandler(new MyAuthenticationFailureHandler()); return loginFilter; }
调整Controller方法,直接从SecurityContext获取已认证的用户信息:
@ResponseBody @RequestMapping(value = "/sso/login", method = RequestMethod.POST) public CommonResult login(){ Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); String username = authentication.getName(); String token = umsAdminService.login(username, null); if(token == null){ return CommonResult.validateFailed("生成Token失败"); } Map<String, String> tokenMap = new HashMap<>(); tokenMap.put("token", token); tokenMap.put("tokenHead", tokenHead); return CommonResult.success(tokenMap); }
推荐使用方案一,因为Spring Security的Filter设计就是用来处理认证逻辑并返回响应,将Token生成放在回调中更符合认证流程的职责划分,也避免了请求链的冗余流转。
内容的提问来源于stack exchange,提问作者tiger_yam

