专栏首页码农小胖哥的码农生涯Spring Security 实战干货:UsernamePasswordAuthenticationFilter 源码分析

Spring Security 实战干货:UsernamePasswordAuthenticationFilter 源码分析

1. 前言

欢迎阅读 Spring Security 实战干货系列文章,在集成Spring Security安全框架的时候我们最先处理的可能就是根据我们项目的实际需要来定制注册登录了,尤其是Http登录认证。根据以前的相关文章介绍,Http登录认证由过滤器UsernamePasswordAuthenticationFilter 进行处理。我们只有把这个过滤器搞清楚才能做一些定制化。今天我们就简单分析它的源码和工作流程。

2. 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工作流程

3. 我们可以定制什么

根据上面的流程,我们理解了UsernamePasswordAuthenticationFilter工作流程后可以做这些事情:

  • 定制我们的登录请求 URI 和请求方式。
  • 登录请求参数的格式定制化,比如可以使用JSON格式提交甚至几种并存。
  • 如何将用户名和密码封装入凭据UsernamePasswordAuthenticationToken,定制业务场景需要的特殊凭据。

4. 我们会有什么疑问

AuthenticationManager从哪儿来,它又是什么,它是如何对凭据进行认证的,认证成功的后续细节是什么,认证失败的后续细节是什么。

本文分享自微信公众号 - 码农小胖哥(Felordcn),作者:码农小胖哥

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-07-16

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Springboot与Elasticsearch完美结合

    在前面一篇已经写了elasticsearch的环境的搭建,那么这一篇就写下springboot与elasticsearch环境的整合。如果没有搭建环境,请参考:...

    码农小胖哥
  • 使用Docker构建企业级自定义镜像

    临下班前,楼主接到了一个需求,由于基础镜像标准发生变更,需要按照最新的Docker 镜像标准构建自己应用的自定义镜像。目前的标准是这样的:基础架构组只提供所有项...

    码农小胖哥
  • Spring Security 实战干货: 401和403状态

    最近几篇我对Spring Security中用户认证流程进行了分析,同时在分析的基础上我们实现了一个验证码登录认证的实战功能。当认证失败后交给了Authenti...

    码农小胖哥
  • ​Java爬取同花顺股票数据(附源码)

    最近有小伙伴问我能不能抓取同花顺的数据,最近股票行情还不错,想把数据抓下来自己分析分析。我大A股,大家都知道的,一个概念火了,相应的股票就都大涨。

    java之旅
  • Redis 事务(8)

    Redis的单个命令是原子性的(比如get set mget mset),如果涉及到多个命令的时候,需要把多个命令作为一个不可分割的处理序列,就需要用到事务。

    兜兜毛毛
  • 设计模式之四(抽象工厂模式第三回合)

    抽象工厂模式:提供一个创建一系列相关或相互依赖对象的接口,而无需指定它们具体的类。

    aehyok
  • Redis教程02(五大数据类型简介)

    本文给大家介绍下Redis中的五大数据类型 Redis中的数据都是key/value对,这里的数据类型指的是value的值的类型

    用户4919348
  • Redis教程03(String介绍)

    返回 key 的值,如果 key 不存在时,返回 nil。 如果 key 不是字符串类型,那么返回一个错误。

    用户4919348
  • Github速度太慢全网最全方案

    近日,我在Github上下载源码,真的鸡肋,慢的一匹,通过以下方式,让我下载Github速度飞快,因为刚好有代理,就用的第一种方式,而后面几种方式参考自网上的一...

    公众号guangcity
  • 代码又出错了?很简单,Fuck 一下就好了!

    说起 Python 强大的地方,你可能想到是它的优雅、简洁、开发速度快,社区活跃度高。但真正使得这门语言经久不衰的一个重要原因是它的无所不能,因为社区有各种各样...

    崔庆才

扫码关注云+社区

领取腾讯云代金券