Preface
本篇总结分别基于 Spring Security 与 Spring MVC HandlerInterceptor 实现认证鉴权.
Spring Security
Spring Security 是基于嵌套 Filter
(委派 Filter) 实现的, 在 DispatcherServlet
之前触发. 普通的 Filter 称之为 Web Filter, 而 Spring Security 的 Filter 称之为 Security Filter:
默认有哪些 Filter 可以看 FilterComparator
中的源码:
认证流程
登录拦截
在 FilterComparator
中有一个 UsernamePasswordAuthenticationFilter
, 继承了 AbstractAuthenticationProcessingFilter
, 它就是我们登录时用到的 Filter:
- 可以看到, 默认情况下拦截
/login
端点的 POST 请求, 当然, 可以通过配置改变这个 url. - 这里还有一个关键, 在
attempAuthentication
中, 用户名以及密码的参数是username
以及password
, 并且是从 http parameter 中获取的, 如果要支持 Json 格式的登录, 那就要重写这里. - 将登录请求信息封装成
Authentication
的实现类, 这里是UsernamePasswordAuthenticationToken
, 然后交给AuthenticationManager
进行下一步的认证.
这一步相当与登录信息的提取以及封装.
认证
认证通过 AuthenticationManager
进行的, 这是一个接口, 默认的实现类为 ProviderManager
:
可以看到实现类 ProviderManager
中维护了一个 List<AuthenticationProvider>
的列表, 存放多种认证方式, 实际上这是委托者模式的应用(Delegate)
核心的认证入口始终只有一个:
AuthenticationManager
, 不同的认证方式: 用户名 + 密码(UsernamePasswordAuthenticationToken
), 邮箱 + 密码, 手机号码 + 密码登录则对应了三个AuthenticationProvider
. 在默认策略下, 只需要通过一个AuthenticationProvider
的认证, 即可被认为是登录成功.
一个最常用到的 AuthenticationProvider
实现类就是 DaoAuthenticationProvider
, 里面比较重要的一个环节就是 additionalAuthenticationChecks
(密码校验):
- 通过
UserDetailsService
的实现类(需要用户自己实现)拿到UserDetails
- 将其中的
password
与UsernamePasswordAuthenticationToken
中的credentials
进行对比
登录成功后会执行 AbstractAuthenticationProcessingFilter#successfulAuthentication
将 Authentication
存到 SecurityContextHolder
中.
到此, 认证的核心就是这样了.
权限校验流程
FilterSecurityInterceptor
是整个Security filter链中的最后一个, 也是最重要的一个, 它的主要功能就是判断认证成功的用户是否有权限访问接口, 其最主要的处理方法就是 调用父类(AbstractSecurityInterceptor
)的 super.beforeInvocation(fi)
, 我们来梳理下这个方法的处理流程:
- 通过
obtainSecurityMetadataSource().getAttributes()
获取 当前访问地址所需权限信息- 通过
authenticateIfRequired()
获取当前访问用户的权限信息- 通过
accessDecisionManager.decide()
使用 投票机制判权, 判权失败直接抛出AccessDeniedException
异常
1 | protected InterceptorStatusToken beforeInvocation(Object object) { |
因此如果要动态鉴权, 可以从两方面入手:
- 自定义
SecurityMetadataSource
, 实现从数据库加载ConfigAttribute
- 另外就是可以自定义
accessDecisionManager
, 官方的UnanimousBased
其实足够使用, 并且他是基于AccessDecisionVoter
来实现权限认证的, 因此我们只需要自定义一个AccessDecisionVoter
就可以了
1 |
|
核心配置
下面贴一个核心配置
1 |
|
更多源码查看: https://github.com/masteranthoneyd/spring-boot-learning/tree/master/spring-boot-security
其他配置说明
session
上面的配置是基于 jwt 无状态的, 所以不需要 session, 如果使用, 可以通过下面配置实现一些额外的功能
1 | http.sessionManagement() |
如果是自定义的用户, 需要重写
equals
以及hashcode
方法, 因为底层是通过一个 Map 存放 session 相关信息, 而 key 则是 principal 对象.如果是覆盖了
UsernamePasswordAuthenticationFilter
, 这些 session 配置需要在自定义的 Filter 重新配置.
同时启用 session 提供一个 bean(因为 Spring security 的通过监听事件实现 session 销毁的):
1 |
|
session 集群共享:
第一步, 引入 redis:
1 | <dependency> |
第二部, 配置 SessionRegistry:
1 |
|
总结
上面提到的只是一个大致的核心流程, 但大概可以看出来, Spring Security 功能不仅齐全, 而且留了很多的扩展点, 可以很灵活的定制自己的权限业务.
但正是因为其极其丰富的扩展, 使得这框架变得”很重”, 对新手来说可能不太友好, 需要一定的学习成本.
Spring MVC HandlerInterceptor
对于一般简单的登录校验而言, 使用 Spring Security 可能稍显笨重, 这时候可以基于 Spring MVC 的 HandlerInterceptor 实现简单的校验逻辑:
1 | public class AuthorizationInterceptor extends HandlerInterceptorAdapter { |
将拦截器添加到 MVC 中:
1 | .class) (AuthorizationPreHandler |
authorizationPreHandler
中简单校验是否存在 token 即可.
完整代码请看: https://github.com/masteranthoneyd/alchemist/tree/master/auth
RBAC 权限设计
主要核心逻辑还是 用户-角色-权限
.
在这基础上拓展出 用户-用户组-角色
以及 权限-类型-具体权限
.