这不是个完整的spring security介绍文章,只是简单的介绍比较流行场景下,几个关键的类。
引言
我们平时遇到到最简单,也是最熟悉的认证过程是这样的
- 用户在登录页面输入用户名密码
- 验证用户密码是否正确
- 验证成功后,把用户的验证信息放到session
- 用户访问页面时先从session拿到用户的验证信息,然后再访问页面
那么以上几个步骤是从哪个类来处理的呢?
过滤器
首先整个spring security是用过滤器来处理的。
用户名密码的获取
类AbstractAuthenticationProcessingFilter(其实现类UsernamePasswordAuthenticationFilter)用来从请求中获取用户名和密码,并把其包装到一个特定的类UsernamePasswordAuthenticationToken,然后调用AuthenticationManager其实现类ProviderManager来循环调用AuthenticationProvider(其实现类为DaoAuthenticationProvider)来处理认证过程。
认证处理
类AbstractUserDetailsAuthenticationProvider负责处理UsernamePasswordAuthenticationToken类型的用户信息。这是个虚拟类,他的实现类是DaoAuthenticationProvider1
2
3public 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
3http.securityContext()
.requireExplicitSave(true)
.securityContextRepository(securityContextRepository);
通过自定义SecurityContextRepository来对jwt进行处理。
另外关于记住我功能,因为对于浏览器,请求的数据无法强绑定,所以如果用户的token被劫持的话,服务器端是没有办法查知的。只有一个IP地址可以简单的验证一下。
浏览器强制使用ssl,并保证自己的电脑没有安装奇怪的程序的证书和程序的话,基本上能防止token被劫持。