欢迎阅读Spring Security 实战干货系列文章,在集成 Spring Security 安全框架的时候我们最先处理的可能就是根据我们项目的实际需要来定制注册登录了,尤其是 Http 登录认证。根据以前的相关文章介绍, Http 登录认证由过滤器 UsernamePasswordAuthenticationFilter
进行处理。我们只有把这个过滤器搞清楚才能做一些定制化。今天我们就简单分析它的源码和工作流程。
UsernamePasswordAuthenticationFilter
继承于 AbstractAuthenticationProcessingFilter
(另文分析)。它的作用是拦截登录请求并获取账号和密码,然后把账号密码封装到认证凭据 UsernamePasswordAuthenticationToken
中,然后把凭据交给特定配置的 AuthenticationManager
去作认证。源码分析如下:
public class UsernamePasswordAuthenticationFilter extends AbstractAuthenticationProcessingFilter { // 默认取账户名、密码的key public static final String SPRING_SECURITY_FORM_USERNAME_KEY = "username"; public static final String SPRING_SECURITY_FORM_PASSWORD_KEY = "password"; // 可以通过对应的set方法修改 private String usernameParameter = SPRING_SECURITY_FORM_USERNAME_KEY; private String passwordParameter = SPRING_SECURITY_FORM_PASSWORD_KEY; // 默认只支持 POST 请求 private boolean postOnly = true; // 初始化一个用户密码 认证过滤器 默认的登录uri 是 /login 请求方式是POST public UsernamePasswordAuthenticationFilter() { super(new AntPathRequestMatcher("/login", "POST")); } // 实现其父类 AbstractAuthenticationProcessingFilter 提供的钩子方法 用去尝试认证 public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { // 判断请求方式是否是POST if (postOnly && !request.getMethod().equals("POST")) { throw new AuthenticationServiceException( "Authentication method not supported: " + request.getMethod()); } // 先去 HttpServletRequest 对象中获取账号名、密码 String username = obtainUsername(request); String password = obtainPassword(request); if (username == null) { username = ""; } if (password == null) { password = ""; } username = username.trim(); // 然后把账号名、密码封装到 一个认证Token对象中,这是就是一个通行证,但是这时的状态时不可信的,一旦通过认证就变为可信的 UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken( username, password); // 会将 HttpServletRequest 中的一些细节 request.getRemoteAddr() request.getSession 存入的到Token中 setDetails(request, authRequest); // 然后 使用 父类中的 AuthenticationManager 对Token 进行认证 return this.getAuthenticationManager().authenticate(authRequest); } // 获取密码 很重要 如果你想改变获取密码的方式要么在此处重写,要么通过自定义一个前置的过滤器保证能此处能get到 @Nullable protected String obtainPassword(HttpServletRequest request) { return request.getParameter(passwordParameter); } // 获取账户很重要 如果你想改变获取密码的方式要么在此处重写,要么通过自定义一个前置的过滤器保证能此处能get到 @Nullable protected String obtainUsername(HttpServletRequest request) { return request.getParameter(usernameParameter); } // 参见上面对应的说明为凭据设置一些请求细节 protected void setDetails(HttpServletRequest request, UsernamePasswordAuthenticationToken authRequest) { authRequest.setDetails(authenticationDetailsSource.buildDetails(request)); } // 设置账户参数的key public void setUsernameParameter(String usernameParameter) { Assert.hasText(usernameParameter, "Username parameter must not be empty or null"); this.usernameParameter = usernameParameter; } // 设置密码参数的key public void setPasswordParameter(String passwordParameter) { Assert.hasText(passwordParameter, "Password parameter must not be empty or null"); this.passwordParameter = passwordParameter; } // 认证的请求方式是只支持POST请求 public void setPostOnly(boolean postOnly) { this.postOnly = postOnly; } public final String getUsernameParameter() { return usernameParameter; } public final String getPasswordParameter() { return passwordParameter; } }
为了加强对流程的理解,我特意画了一张图来对这个流程进行清晰的说明:
根据上面的流程,我们理解了 UsernamePasswordAuthenticationFilter
工作流程后可以做这些事情:
UsernamePasswordAuthenticationToken
,定制业务场景需要的特殊凭据。 AuthenticationManager
从哪儿来,它又是什么,它是如何对凭据进行认证的,认证成功的后续细节是什么,认证失败的后续细节是什么。不要走开,持续关注: 码农小胖哥 为你揭晓这个答案。
关注公众号:Felordcn 获取更多资讯
个人博客:https://felord.cn