springsecurity的执行过程,无非就是拦截器的执行流程,网上有很多资料可供学习。新手学习后实际去做权限控制时,感觉对springsecurity的理解还是雾里看花,如何快速学透权限控制?看完我这篇博客就行了。拦截器里面的实现需要一些组件来实现,在这些组件中有三个最重要接口or方法
1 UserDetailsService 处理用户和用户可以访问的url(可以在数据库中配置用户,角色)
密码验证在 authenticate方法里面 http://blog.csdn.net/d7011800/article/details/8692667
2 FilterInvocationSecurityMetadataSource
(访问的url和访问当前url所需要的角色.例如:用户访问http://www.love.jsp,调用FilterInvocationSecurityMetadataSource
的方法来获取被拦截url所需的全部权限Role角色,
这个接口中要注意两个接口:RequestMatcher访问的url,ConfigAttribute访问的url需要的对应的Role角色)
3 AccessDecisionManager投票器
三种策略,分别对应那个策略类:
UnanimousBased.java 只要有一个Voter不能完全通过权限要求,就禁止访问。
AffirmativeBased.java只要有一个Voter可以通过权限要求,就可以访问。
ConsensusBased.java只要通过的Voter比禁止的Voter数目多就可以访问了。
主要流程:用户访问url,FilterInvocationSecurityMetadataSource的方法获取被拦截url所需的全部权限Role角色,把这些权限Role角色去和数据库中配置的所有权限Role角色比对(查询数据库中配置的所有权限Role角色主要由接口UserDetailsService完成),如果"访问此url需要的权限Role角色"是"用户数据库中配置的权限Role角色"的子集,就允许访问。这个比对过程由AccessDecisionManager去完成。
附:
1 我上面使用的是用角色作为中转站,通过比较角色确定是否有权限。其实也可以用其他的模型作为中转站。
如把权限作为中转表:
http://blog.csdn.net/jaune161/article/details/18353397
http://blog.csdn.net/jaune161/article/details/18354599
http://blog.csdn.net/jaune161/article/details/18446481(什么时候查询用户所有的角色或权限呢?可以是此链接jaune161的filterSecurityInterceptor在SpringSecurity默认的过滤器之前执行。也可以是在用户校验完用户名密码后也可以添加自己定义的AuthenticationFilter(对应的bean是UsernamePasswordAuthenticationFilter或UsernamePasswordAuthenticationFilter的继承类)到FilterChain的FORM_LOGIN_FILTER位置见http://elim.iteye.com/blog/2161648目前再用这种。
elim.iteye.com/blog/2161648链接里也提到了两种方式的关联:1.5.1 FilterSecurityInterceptor---FilterSecurityInterceptor是用于保护Http资源的,它需要一个AccessDecisionManager和一个AuthenticationManager的引用。它会从SecurityContextHolder获取Authentication,然后通过SecurityMetadataSource(对应jaune161里FilterInvocationSecurityMetadataSource,是SecurityMetadataSource的实现类)可以得知当前请求是否在请求受保护的资源。
elim里在UsernamePasswordAuthenticationFilter的return this.getAuthenticationManager().authenticate(authRequest);代码里getAuthenticationManager()就是获得AuthenticationManager)
2腾讯rep 浅谈springSecurity:http://zhaoxijun.iteye.com/blog/2261757
3比较老的springsecurity开发指南,但是内容很好:http://www.mossle.com/docs/auth/html/index.html
好了,有了我上文的铺垫,再看完那两篇文章,你再学习springsecurity就很容易就学了。
-----看完后是不是更迷惑了---囧!!!
小结:整个流程是什么样的呢?介绍三种方式:
方式1 假设用户user--->角色role1---->权限abcUrl。在UserDetailsService 中去数据库中查询用户时,根据用户的角色查询对应的权限,并把权限放在user对象里。用户访问urlBcd时,在投票器中校验,这里要自己写个自定义投票器,投票器里逻辑这样写,根据request获得用户请求的url:urlBcd,取出user对象里用户所有权限,看是否包含urlBcd。发现用户只有一个权限abcUrl,没有urlBcd,拒绝访问。
方式2:假设用户user--->角色role1---->权限abcUrl。在UserDetailsService 中去数据库中查询用户时,根据用户的角色查询对应的权限,并把权限放在user对象里。在用户访问的controller的方法加上注解:PreAuthorize
@PreAuthorize("hasAuthority('query-demo')")
public String getDemo(){
return "good";
}
用户不包含query-demo权限,拒绝访问。博客 代码
方式3: 见下图流程
注意这个表和前面两个有点不同,用户---角色---权限role_admin---资源url 见链接 。在UserDetailsService 中去数据库中查询用户时,根据用户的角色查询对应的权限,并把权限放在user对象里。
链接 : 在投票器中鉴权处理。Authentication中是用户及用户权限信息,attributes是访问资源需要的权限,然后循环判断用户是否有访问资源需要的权限,如果有就返回ACCESS_GRANTED,通俗的说就是有权限。
链接 :查询用户权限role_xx,根据请求的资源url,根据数据库中配置资源url和权限role_xx的关系,去获取访问当前资源url需要哪些权限role_xx
通过查询数据库获得资源与权限的对应列表 RequestMap,。
具体的逻辑见getURLResourceMapping---->
loadResuorce() 里key为resourcePath,vlue为权限, 一个资源对应多个权限,则把所有权限用逗号拼接组成vlaue,如map("resourcePath","aaa,bbb,c")--->
最后在bindRequestMap()里组合为Map
getAllConfigAttributes:获取所有权限集合
getAttributes:根据request请求获取访问资源所需权限
上面两个链接其实就是使用自定义的securityMetadataSource里的代码,在FilterInvocationSecurityMetadataSource实现类里重写。
方式3 流程图
springsecurity默认的提供了三种决策器,AffirmativeBased 一票通过,只要有一个投票器通过就允许访问ConsensusBased 有一半以上投票器通过才允许访问资源
UnanimousBased 。决策器里再调用投票器去投票判断是否有权限,有无权限访问的最终觉得权是由投票器来决定的,最常见的投票器为RoleVoter。例如作者用的是投票器里鉴权。
禅师,决策器和投票器什么关系?非得调用投票器才能鉴权吗?当然不是,其实决策器就是判断用户有无权限,不去调用投票器去判断也可以,自己写逻辑判断也行。打个比方我们项目里controller层会调用ip工具类去获取访问者的ip 地址,非得调用ip工具类才可以获得ip地址吗,没必要,我不用ip工具类直接在controller中写个if判断获取ip地址也行。禅师,我懂了,你的意思是说ip工具类抽出来有利于复用,修改,或者把ip工具类抽象成接口,然后开发地球ip,火星ip多个实现类,方便调用,虽然有这么多好处,但是我可以不用,我就是爱自己写,毕竟葛大爷说过,有小孩这事我还是喜欢自力更生。。。禅师:大郎,注意看好莲莲。
链接 作者写的自定义决策器里就不使用投票器,直接返回有无权限。
xml中:access-decision-manager-ref="accessDecisionManager"
然后类MyAccessDecisionManager extends AbstractAccessDecisionManager 重写decide方法
还有点要注意,前文提到 链接 : 去获取访问当前资源url需要哪些权限role_xx。容器启动时会操作一次,如果后来管理员重新授权,访问当前url对应的需要的权限变了怎么办。即系统会在初始化时一次将所有资源加载到内存中,即使在数据库中修改了资源信息,系统也不会再次去从数据库中读取资源信息。这就造成了每次修改完数据库后,都需要重启系统才能时资源配置生效。我们只要想办法在管理员修改数据后,更新内存中数据map即可。
解决方案是,有网友提出1 如果数据库中的资源出现的变化,需要刷新内存中已加载的资源信息时。
ApplicationContext ctx = WebApplicationContextUtils.
getWebApplicationContext(request.getSession().getServletContext());
xxSecurityMetadataSource cs=(CustomInvocationSecurityMetadataSource)ctx.
getBean("xxxSecurityMetadataSource",com.xx.xxMetadataSource.class);
cs.loadResourceDefine();
loadResourceDefine()重新查数据库,资源url需要哪些权限role_xx。
2 放资源的map 用static。
这个解决的办法也行,就是多个管理员并发修改时会有点问题。我现在想的是用定时器做。
源码:对比xml和springboot配置方式
1 用户名和密码被过滤器获取到,封装成Authentication
,通常情况下是UsernamePasswordAuthenticationToken
这个实现类。
2 AuthenticationManager
身份管理器负责验证这个Authentication
3 认证成功后,AuthenticationManager
身份管理器返回一个被填充满了信息的(包括上面提到的权限信息,身份信息,细节信息,但密码通常会被移除)Authentication
实例。
4 SecurityContextHolder
安全上下文容器将第3步填充了信息的Authentication
,通过SecurityContextHolder.getContext().setAuthentication(…)方法,设置到其中。