Spring Security做JWT登录认证

此篇文章分析了security在登录认证的时候一些核心模块的主要功能,后面也针对security登录进行代码实现

Spring Security 核心架构
  • AuthenticationManager,用户认证的管理类,所有请求都会通过一个token给AuthenticationManager的authenticate()方法来实现。当然事情肯定不是由它来做的,而是AuthenticationManager将具体请求转发给具体的实现类去校验。根据实现类反馈的结果,再调用具体的Handler来给用户反馈。

  • AuthenticationProvider,认证的具体实现类,一个Provider是一种认证方式的实现,比如登录的用户名和密码是通过db查询所得到的结果进行比对,那就有一个DaoProvider;如果是通过cas单点登录系统实现,就有一个CasProvider。前面说到的AuthenticationManager只是一个代理接口,真正的认证是由AuthenticationProvider来实现。一个AuthenticationManager可以包含多个Provider,每个provider通过实现一个support方法来表示自己支持那种Token的认证。AuthenticationManager默认的实现类是ProviderManager。

  • UserDetailService,用户认证通过provider来做,所以Provider需要拿到系统已经保存好了的认证信息,获取用户信息接口spring-security抽象成UserDetailService。虽然叫service,但更像是一个UserDao。
  • AuthenticationToken,所有提交给AuthenticationManager的请求都会被封装 成一个token的实现,比如最容易理解的UsernamePasswordAuthenticationToken。

  • SecurityContext,当用户认证通过后,就会给用户分配一个唯一的SecurityContext,里面包含用户的认证信息Authentication。通过SecurityContext我们可以获取到用户的唯一表示principle和授权信息
    GrantedAuthrity。在系统的任何地方,只要通过SecurityHolder.getSecurityContext()就可以获取到SecurityContext。

  • spring-security核心流程
对Web系统的支持

对于web系统来说进入认证的最佳入口就是Filter了。spring security不仅实现了认证的逻辑,还通过filter实现了常见的web攻击的防护。

常用filter

下面按照request请求顺序列举一下常用的filter:

  • SecurityContextPersistenceFilter,用户将securityContext放入Session的filter
  • UsernamePasswordAuthenticationFilter,登录认证的filter,类似的还有CasAuthenticationfilter,BasicAuthenticationFilter等等。在这些filter中,生成用于认证的token,提交到AuthenticationManager,如果认证失败直接返回。
  • RememberMeAuthenticationFilter,通过cookie实现记住我的功能。
  • AnonymousAuthenticationFilter,如果一个请求在到达这个filter之前SecurityContext没有初始化,则这个filter会默认生成一个匿名SecurityContext。这在支持匿名用户的系统中非常有用。
  • ExceptionTranslationFilter,捕获spring security抛出的异常,并决定处理方式。
  • FirstSecurityInterceptor,权限校验的拦截器,访问的url权限不足会抛出异常。
JWT认证实现
  • 支持用户通过用户名和密码登录
  • 登录后,将token传回给客户端,每次请求,客户端需通过header将token带回,用于权限校验
  • 服务端负责token的定期刷新
登录认证流程

Filter
对于用户登录行为,security通过定义一个filter来拦截/login来实现的。spring security默认支持 form登录,所以用于json形式登录的情况我们自定义一个filter,这个filter直接从AbstractAuthenticationProcessingFilter继承,只需要实现两部分,一个是RequestMatcher,指明拦截的Request类型;另外就是json body中提取出username和password提交给AuthenticationManager。

Provider
前面流程图中说到,封装后的token交由provider来处理。对于登录的provider,spring security提供了默认的provider,DaoAuthenticationProvider,我们可以直接使用,这个类继承了AbstractUserDetailsAuthenticationProvider。

UserDetailService
provider在验证用户信息的时候,用户获取用到了UserDetailService类的loadUserByUsername(String username)方法。这个是一个只有一个方法的接口,所以我们自己要做的,就是将自己的UserDetailsService实现类配置成一个Bean,自定义获取用户方法。

认证结果处理
filter将token交给provider做校验,校验的结果无非就两种,成功或者失败。对于这两种结果,我们只需要实现两个Handler接口,set到filter里面,filter在收到Provider的处理结果后会回调这两个Handler方法。

先来看成功的情况,针对jwt认证的业务场景,登录成功需要返回给客户端一个token。所以成功的handler的实现类中需要包含这个逻辑。

再来看失败的情况,登录失败,将失败异常原因返回给客户端。