spring security的几个关键类

这不是个完整的spring security介绍文章,只是简单的介绍比较流行场景下,几个关键的类。

引言

我们平时遇到到最简单,也是最熟悉的认证过程是这样的

  • 用户在登录页面输入用户名密码
  • 验证用户密码是否正确
  • 验证成功后,把用户的验证信息放到session
  • 用户访问页面时先从session拿到用户的验证信息,然后再访问页面
    那么以上几个步骤是从哪个类来处理的呢?

过滤器

首先整个spring security是用过滤器来处理的。

用户名密码的获取

AbstractAuthenticationProcessingFilter(其实现类UsernamePasswordAuthenticationFilter)用来从请求中获取用户名和密码,并把其包装到一个特定的类UsernamePasswordAuthenticationToken,然后调用AuthenticationManager其实现类ProviderManager来循环调用AuthenticationProvider(其实现类为DaoAuthenticationProvider)来处理认证过程。

认证处理

AbstractUserDetailsAuthenticationProvider负责处理UsernamePasswordAuthenticationToken类型的用户信息。这是个虚拟类,他的实现类是DaoAuthenticationProvider

1
2
3
public boolean supports(Class<?> authentication) {
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}

并且需要在方法authenticate中完成整个认证过程,包括用户信息的获取(通过retrieveUser方法来实现)
以及密码匹配的验证(通过方法additionalAuthenticationChecks来实现)

保存到session

认证成功后,会通过调用SessionAuthenticationStrategy(实现类ChangeSessionIdAuthenticationStrategy)的方法onAuthentication来保存认证信息。

用户访问

过滤器SecurityContextPersistenceFilter里,通过RequestAttributeSecurityContextRepository来获取session里的信息,并把它放到SecurityContextHolder里进行管理。

1
SecurityContextHolder.setContext(contextBeforeChainExecution);

如果session里没有这个信息,那么后面的过滤器AnonymousAuthenticationFilter会放置一个AnonymousAuthenticationToken进去。
最后一个过滤器FilterSecurityInterceptor通过获取到的用户信息来验证用户是否有权限访问该页面

Remember me

如果登陆设置里开启了记住我这个功能,那么就相当于注入了RememberMeAuthenticationFilter这个过滤器。
根据注入的RememberMeServices实例的不同,这里有两种处理方式

  • TokenBasedRememberMeServices
    这个比较简单,只是简单的把用户名和密码通过md5加密的方式,把用户信息通过cookie的方式保存在前端,用来验证用户的合法性
  • PersistentTokenBasedRememberMeServices
    这种模式,会把token放到数据库里一份儿,再次登录会替换为新的。

这两种之间的区别是,如果token被人劫持后,第一种方式,两个人都可以同时登录成功。而第二种方式,只要用户再登录一次,数据库里的token会重新生成,这样即使被劫持,也不可再用了。

结论

所以,如果用户想自己实现一个从页面获取信息并认证的处理的话,可以实现AbstractAuthenticationProcessingFilter类,然后注入一个实现AbstractUserDetailsAuthenticationProvider的类来执行认证过程。但是这套体系是通过session来对认证信息进行保管的,所以如果认证过程是通过jwt来完成的话,就不能沿用这套体系了。

jwt

如果想使用jwt的话,可以自定义一个filter处理认证,并直接返回生成的jwt token,然后再用spring-boot-starter-oauth2-resource-server中的OAuth2ResourceServerConfigurer::jwt对每个请求提交的jwt token进行认证

1
http.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt);

如果JwtAuthenticationProvider所生成的JwtAuthenticationToken不大符合需求的话,也可以自定义.

题外话

关于jwt,说个题外话,那就是关于jwt,他算是登录信息(类似于用户名密码),还是用户标识(类似于SessionId)

  • 如果你把它看成是登录信息,比如在其他网站上登录后,又跳转到本系统,那么其实就算是用jwt来登录我们的系统,这个时候用上面的OAuth2ResourceServerConfigurer::jwt比较合适,通过BearerTokenAuthenticationFilter来获取BearerTokenAuthenticationToken,然后通过JwtAuthenticationProvider来进行验证。
  • 但是你把它当成是用户标识,比如在本系统登录,生成了jwt,然后再访问本系统,那么这个jwt就有点儿类似于SessionId这样的标识了,那么获取SecurityContext就是通过过滤器SecurityContextHolderFilter来实现的
    1
    2
    3
    http.securityContext()
    .requireExplicitSave(true)
    .securityContextRepository(securityContextRepository);

通过自定义SecurityContextRepository来对jwt进行处理。

另外关于记住我功能,因为对于浏览器,请求的数据无法强绑定,所以如果用户的token被劫持的话,服务器端是没有办法查知的。只有一个IP地址可以简单的验证一下。
浏览器强制使用ssl,并保证自己的电脑没有安装奇怪的程序的证书和程序的话,基本上能防止token被劫持。