【转载】 Spring Security做JWT认证和授权
https://www.jianshu.com/p/d5ce890c67f7
上一篇博客讲了如何使用Shiro和JWT做认证和授权(传送门:https://www.jianshu.com/p/0b1131be7ace),总的来说shiro是一个比较早期和简单的框架,这个从最近已经基本不做版本更新就可以看出来。这篇文章我们讲一下如何使用更加流行和完整的spring security来实现同样的需求。
Spring Security的架构
按照惯例,在使用之前我们先讲一下简单的架构。不知道是因为spring-security后出来还是因为优秀的设计殊途同归,对于核心模块,spring-security和shiro有80%以上的设计相似度。所以下面介绍中会多跟shiro做对比,如果你对shiro不了解也没关系,跟shiro对比的部分跳过就好。
spring-security中核心概念
- AuthenticationManager, 用户认证的管理类,所有的认证请求(比如login)都会通过提交一个token给
AuthenticationManager
的authenticate()
方法来实现。当然事情肯定不是它来做,具体校验动作会由AuthenticationManager
将请求转发给具体的实现类来做。根据实现反馈的结果再调用具体的Handler来给用户以反馈。这个类基本等同于shiro的SecurityManager
。 - AuthenticationProvider, 认证的具体实现类,一个provider是一种认证方式的实现,比如提交的用户名密码我是通过和DB中查出的user记录做比对实现的,那就有一个
DaoProvider
;如果我是通过CAS请求单点登录系统实现,那就有一个CASProvider
。这个是不是和shiro的Realm的定义很像?基本上你可以帮他们当成同一个东西。按照Spring一贯的作风,主流的认证方式它都已经提供了默认实现,比如DAO、LDAP、CAS、OAuth2等。
前面讲了AuthenticationManager
只是一个代理接口,真正的认证就是由AuthenticationProvider
来做的。一个AuthenticationManager
可以包含多个Provider,每个provider通过实现一个support方法来表示自己支持那种Token的认证。AuthenticationManager
默认的实现类是ProviderManager
。 -
UserDetailService, 用户认证通过Provider来做,所以Provider需要拿到系统已经保存的认证信息,获取用户信息的接口spring-security抽象成
UserDetailService
。虽然叫Service,但是我更愿意把它认为是我们系统里经常有的UserDao
。 -
AuthenticationToken, 所有提交给
AuthenticationManager
的认证请求都会被封装成一个Token的实现,比如最容易理解的UsernamePasswordAuthenticationToken
。这个就不多讲了,连名字都跟Shiro中一样。 -
SecurityContext,当用户通过认证之后,就会为这个用户生成一个唯一的
SecurityContext
,里面包含用户的认证信息Authentication
。通过SecurityContext我们可以获取到用户的标识Principle
和授权信息GrantedAuthrity
。在系统的任何地方只要通过SecurityHolder.getSecruityContext()
就可以获取到SecurityContext
。在Shiro中通过SecurityUtils.getSubject()
到达同样的目的。
我们大概通过一个认证流程来认识下上面几个关键的概念
对web系统的支持
毫无疑问,对于spring框架使用最多的还是web系统。对于web系统来说进入认证的最佳入口就是Filter了。spring security不仅实现了认证的逻辑,还通过filter实现了常见的web攻击的防护。
常用Filter
下面按照request进入的顺序列举一下常用的Filter:
- SecurityContextPersistenceFilter,用于将
SecurityContext
放入Session的Filter - UsernamePasswordAuthenticationFilter, 登录认证的Filter,类似的还有CasAuthenticationFilter,BasicAuthenticationFilter等等。在这些Filter中生成用于认证的token,提交到AuthenticationManager,如果认证失败会直接返回。
- RememberMeAuthenticationFilter,通过cookie来实现remember me功能的Filter
- AnonymousAuthenticationFilter,如果一个请求在到达这个filter之前SecurityContext没有初始化,则这个filter会默认生成一个匿名SecurityContext。这在支持匿名用户的系统中非常有用。
- ExceptionTranslationFilter,捕获所有Spring Security抛出的异常,并决定处理方式
- FilterSecurityInterceptor, 权限校验的拦截器,访问的url权限不足时会抛出异常
Filter的顺序
既然用了上面那么多filter,它们在FilterChain中的先后顺序就显得非常重要了。对于每一个系统或者用户自定义的filter,spring security都要求必须指定一个order,用来做排序。对于系统的filter的默认顺序,是在一个FilterComparator
类中定义的,核心实现如下。
FilterComparator() {
int order = 100;
put(ChannelProcessingFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
put(WebAsyncManagerIntegrationFilter.class, order);
order += STEP;
put(SecurityContextPersistenceFilter.class, order);
order += STEP;
put(HeaderWriterFilter.class, order);
order += STEP;
put(CorsFilter.class, order);
order += STEP;
put(CsrfFilter.class, order);
order += STEP;
put(LogoutFilter.class, order);
order += STEP;
filterToOrder.put(
"org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter",
order);
order += STEP;
put(X509AuthenticationFilter.class, order);
order += STEP;
put(AbstractPreAuthenticatedProcessingFilter.class, order);
order += STEP;
filterToOrder.put("org.springframework.security.cas.web.CasAuthenticationFilter",
order);
order += STEP;
filterToOrder.put(
"org.springframework.security.oauth2.client.web.OAuth2LoginAuthenticationFilter",
order);
order += STEP;
put(UsernamePasswordAuthenticationFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
filterToOrder.put(
"org.springframework.security.openid.OpenIDAuthenticationFilter", order);
order += STEP;
put(DefaultLoginPageGeneratingFilter.class, order);
order += STEP;
put(ConcurrentSessionFilter.class, order);
order += STEP;
put(DigestAuthenticationFilter.class, order);
order += STEP;
put(BasicAuthenticationFilter.class, order);
order += STEP;
put(RequestCacheAwareFilter.class, order);
order += STEP;
put(SecurityContextHolderAwareRequestFilter.class, order);
order += STEP;
put(JaasApiIntegrationFilter.class, order);
order += STEP;
put(RememberMeAuthenticationFilter.class, order)