在原公司有专门的登录验证和权限管理服务,换公司后在最近项目中需要使用Spring Security自主实现分布式系统的用户验证授权及权限验证功能,因此花了两天时间研究并实现了该方案:
功能点细分:
1. 基于REST请求的登录
2. 用户名密码验证及验证成功后给用户授权
3. http请求的权限配置和验证
4. 方法级别的权限配置和验证
5. 分布式环境中用户权限共享
Spring Security默认是通过表单提交用户登录请求,可通过修改配置实现自定义登录请求
在spring-security.xml中配置如下为自定义的登录入口,并在entry-point-ref中配置登录url(只需关注红框标注部分)
注:中别的配置将在下文中分别介绍
Spring Security的底层是通过一系列Filter管理的,当我们使用NameSpace时,Spring Security会自动创建FilterChain及默认包含的Filter。其中几个关键Filter的位置和作用如下,完整信息可参考(http://wiki.jikexueyuan.com/project/spring-security/filter.html)
过滤器 | 位置 | 作用 |
---|---|---|
SecurityContextPersistenceFilter | SECURITY_CONTEXT_FILTER | 进行 request 时在 SecurityContextHolder 中建立一个 SecurityContext |
UsernamePasswordAuthenticationFilter | FORM_LOGIN_FILTER | 认证及授权处理, 为SecurityContextHolder添加一个有效的 Authentication |
FilterSecurityInterceptor | SECURITY_CONTEXT_FILTER | 保护 Http 资源,判断用户是否有权限访问相应的资源 |
由此可见该功能点是通过UsernamePasswordAuthenticationFilter实现的。UsernamePasswordAuthenticationFilter会调用AuthenticationManager处理认证请求的接口。AuthenticationManager又会进一步把该请求委托给AuthenticationProvider处理。AuthenticationProvider会根据请求的用户名调用UserDetailsService获取用户详情UserDetails。Spring Security有内置UserDetailsService实现,如:
这些均不能满足当前系统的需求,我们需要调用系统中用户服务的接口来加载用户详情,所以这里需要定义自己的UserDetailsService实现。
(1) 自定义UserInfoService实现UserDetailsService接口,重写其loadUserByUsername()方法实现从用户服务的接口来加载用户详情
(2) 在spring-security.xml中配置UsernamePasswordAuthenticationFilter实例authenticationFilter,并为其逐级注入自定义的UserDetailsService实例userInfoService。
(3) 用authenticationFilter替换FilterChain中默认的认证过滤器UsernamePasswordAuthenticationFilter
之前介绍过FilterChain中的默认的Filter都有自己的位置,UsernamePasswordAuthenticationFilter 对应的位置为FORM_LOGIN_FILTER。Spring Security支持通过 position、before 或者 after 指定自定义 Filter放置的位置。红框中语句的含义就是将authenticationFilter放置到FORM_LOGIN_FILTER位置,也就实现了替换默认UsernamePasswordAuthenticationFilter的功能
(4) 整个用户认证及授权的流程如下:
注:红框部分“分布式环境中用户权限共享”功能的部分实现,后面介绍
HTTP请求的权限配置就是自定义url和所需权限的对应关系。在配置文件中实现
HTTP请求的权限验证就是检查用户登录授权阶段获取到的用户权限是否可以访问该url。Spring Security中FilterSecurityInterceptor通过调用AccessDecisonManager的相应接口完成权限认证,其中AccessDecisonManager或配置一个或多个AccessDecisionVoter的vote()方法来进行投票(成功返回1,失败返回-1),并根据每个AccessDecisionVoter的投票结果及一定的策略决定该用户是否有权限访问该url。Spring Security默认使用的AccessDecisonManager实现类AffirmativeBased的策略是只要有一个AccessDecisionVoter投了反对票(即vote方法返回-1),则拒绝访问url。Spring Security中也有多个AccessDecisionVoter实现类,这里介绍两个常用的AccessDecisionVoter:
(1) 在spring-security.xml中配置url及其权限的对应关系
红框语句的含义就是为能匹配“/dicts**”表达式的url请求配置ROLE_DICT权限
(2) 权限检查的过滤器FilterSecurityInterceptor及其配置均使用Spring Security默认配置。
(3) 一次非登录的Http请求的完整流程如下:
注:红框部分“分布式环境中用户权限共享”功能的部分实现,后面介绍
可基于注解实现,参考http://blog.csdn.net/w605283073/article/details/51327182
(1) 在spring-security.xml中开启@PreAuthorize的注解支持
(2) 为需要权限验证的方法添加@PreAuthorize注解
当前分布式系统已经实现了基于Redis的分布式Session,也即同一次访问过程中经过不同主机上不同服务之间是共享Session的。因此当我们需要实现用户权限共享时,只需要在登录验证阶段获取到的用户详情信息(包含用户权限)写入到Session中即可。通过上一节的“用户认证及授权的流程”中可知用户完成验证后的用户详情信息会封装为Authentication实例并写入SecurityContext中,因此我们只需将SecurityContext写入共享Session即可。在需要使用用户权限的地方(如访问特定权限的url或方法的验证阶段)只需要取出Session中的SecurityContext
(1) 自定义AuthenticationSuccessHandler实现类SessionAuthenticationSuccessHandler,重写onAuthenticationSuccess()方法,将SecurityContext保存到session中
(2) 在spring-security.xml中为登录验证授权过滤器authenticationFilter配置授权成功后的处理Handler为SessionAuthenticationSuccessHandler
(3) 自定义过滤器ValidationFilter,在doFilter()方法中实现取出session中的SecurityContext保存到SecurityContextHolder中
(4) 在spring-security.xml为默认FilterChian添加ValidationFilter,位于FilterSecurityInterceptor (位置为FILTER_SECURITY_INTERCEPTOR)前面,这样就可以实现在权限验证之前先经过ValidationFilter将session中的SecurityContext保存到SecurityContextHolder中
此处使用了before关键字定义了validationFilter的插入位置