SpringSecurity中的权限管理

  SpringSecurity是一个权限管理框架,核心是认证和授权,前面已经系统的给大家介绍过了认证的实现和源码分析,本文重点来介绍下权限管理这块的原理。

一、权限管理的实现

  服务端的各种资源要被SpringSecurity的权限管理控制我们可以通过注解和标签两种方式来处理。

放开了相关的注解后我们在Controller中就可以使用相关的注解来控制了

/*** JSR250*/
@Controller
@RequestMapping("/user")
public class UserController {@RolesAllowed(value = {"ROLE_ADMIN"})@RequestMapping("/query")public String query(){System.out.println("用户查询....");return "/home.jsp";}@RolesAllowed(value = {"ROLE_USER"})@RequestMapping("/save")public String save(){System.out.println("用户添加....");return "/home.jsp";}@RequestMapping("/update")public String update(){System.out.println("用户更新....");return "/home.jsp";}
}
/*** Spring表达式*/
@Controller
@RequestMapping("/order")
public class OrderController {@PreAuthorize(value = "hasAnyRole('ROLE_USER')")@RequestMapping("/query")public String query(){System.out.println("用户查询....");return "/home.jsp";}@PreAuthorize(value = "hasAnyRole('ROLE_ADMIN')")@RequestMapping("/save")public String save(){System.out.println("用户添加....");return "/home.jsp";}@RequestMapping("/update")public String update(){System.out.println("用户更新....");return "/home.jsp";}
}
@Controller
@RequestMapping("/role")
public class RoleController {@Secured(value = "ROLE_USER")@RequestMapping("/query")public String query(){System.out.println("用户查询....");return "/home.jsp";}@Secured("ROLE_ADMIN")@RequestMapping("/save")public String save(){System.out.println("用户添加....");return "/home.jsp";}@RequestMapping("/update")public String update(){System.out.println("用户更新....");return "/home.jsp";}
}

然后在页面模板文件中我们可以通过taglib来实现权限更细粒度的控制

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="security" uri="http://www.springframework.org/security/tags" %>
<html>
<head><title>Title</title>
</head>
<body><h1>HOME页面</h1>
<security:authentication property="principal.username" />
<security:authorize access="hasAnyRole('ROLE_USER')" ><a href="#">用户查询</a><br>
</security:authorize><security:authorize access="hasAnyRole('ROLE_ADMIN')" ><a href="#">用户添加</a><br></security:authorize>
</body>
</html>

然后我们在做用户认证的时候会绑定当前用户的角色和权限数据

二、权限校验的原理

  接下来我们看看在用户提交请求后SpringSecurity是如何对用户的请求资源做出权限校验的。首先我们要回顾下SpringSecurity处理请求的过滤器链。如下:

  通过前面介绍我们请求,当一个请求到来的时候会经过上面的过滤器来一个个来处理对应的请求,最后在FilterSecurityInterceptor中做认证和权限的校验操作,

1.FilterSecurityInterceptor

  我们进入FilterSecurityInterceptor中找到对应的doFilter方法

 public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {// 把 request response 以及对应的 FilterChain 封装为了一个FilterInvocation对象FilterInvocation fi = new FilterInvocation(request, response, chain);invoke(fi); // 然后执行invoke方法}

  首先看看FilterInvocation的构造方法,我们可以看到FilterInvocation其实就是对Request,Response和FilterChain做了一个非空的校验。

 public FilterInvocation(ServletRequest request, ServletResponse response,FilterChain chain) {// 如果有一个为空就抛出异常if ((request == null) || (response == null) || (chain == null)) {throw new IllegalArgumentException("Cannot pass null values to constructor");}this.request = (HttpServletRequest) request;this.response = (HttpServletResponse) response;this.chain = chain;}

  然后进入到invoke方法中。

  所以关键我们需要进入到beforeInvocation方法中

  首先是obtainSecurityMetadataSource()方法,该方法的作用是根据当前的请求获取对应的需要具备的权限信息,比如访问/login.jsp需要的信息是 permitAll 也就是可以匿名访问。

  然后就是decide()方法,该方法中会完成权限的校验。这里会通过AccessDecisionManager来处理。

2.AccessDescisionManager

  AccessDescisionManager字面含义是决策管理器。源码中的描述是

  AccessDescisionManager有三个默认的实现

2.1 AffirmativeBased

  在SpringSecurity中默认的权限决策对象就是AffirmativeBased。AffirmativeBased的作用是在众多的投票者中只要有一个返回肯定的结果,就会授予访问权限。具体的决策逻辑如下:

public void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {int deny = 0; // 否决的票数// getDecisionVoters() 获取所有的投票器for (AccessDecisionVoter voter : getDecisionVoters()) {// 投票处理int result = voter.vote(authentication, object, configAttributes);if (logger.isDebugEnabled()) {logger.debug("Voter: " + voter + ", returned: " + result);}switch (result) {case AccessDecisionVoter.ACCESS_GRANTED:return; // 如果投票器做出了 同意的操作,那么整个方法就结束了case AccessDecisionVoter.ACCESS_DENIED:deny++;break;default:break;}}if (deny > 0) { // 如果deny > 0 说明没有投票器投赞成的,有投了否决的 则抛出异常throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}// 执行到这儿说明 deny = 0 说明都投了弃权 票   然后检查是否支持都弃权// To get this far, every AccessDecisionVoter abstainedcheckAllowIfAllAbstainDecisions();}

2.2 ConsensusBased

  ConsensusBased则是基于少数服从多数的方案来实现授权的决策方案。具体看看代码就非常清楚了

 public void decide(Authentication authentication, Object object,Collection<ConfigAttribute> configAttributes) throws AccessDeniedException {int grant = 0; // 同意int deny = 0;  // 否决for (AccessDecisionVoter voter : getDecisionVoters()) {int result = voter.vote(authentication, object, configAttributes);if (logger.isDebugEnabled()) {logger.debug("Voter: " + voter + ", returned: " + result);}switch (result) {case AccessDecisionVoter.ACCESS_GRANTED:grant++; // 同意的 grant + 1break;case AccessDecisionVoter.ACCESS_DENIED:deny++; // 否决的 deny + 1break;default:break;}}if (grant > deny) {return; // 如果 同意的多与 否决的就放过}if (deny > grant) { // 如果否决的占多数 就拒绝访问throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}if ((grant == deny) && (grant != 0)) { // 如果同意的和拒绝的票数一样 继续判断if (this.allowIfEqualGrantedDeniedDecisions) {return; // 如果支持票数相同就放过}else { // 否则就抛出异常 拒绝throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied"));}}// 所有都投了弃权票的情况// To get this far, every AccessDecisionVoter abstainedcheckAllowIfAllAbstainDecisions();}

  上面代码的逻辑还是非常简单的,只需要注意下授予权限和否决权限相等时的逻辑就可以了。决策器也考虑到了这一点,所以提供了 allowIfEqualGrantedDeniedDecisions 参数,用于给用户提供自定义的机会,其默认值为 true,即代表允许授予权限和拒绝权限相等,且同时也代表授予访问权限。

2.3 UnanimousBased

  UnanimousBased是最严格的决策器,要求所有的AccessDecisionVoter都授权,才代表授予资源权限,否则就拒绝。具体来看下逻辑代码:

 public void decide(Authentication authentication, Object object,Collection<ConfigAttribute> attributes) throws AccessDeniedException {int grant = 0; // 赞成的计票器List<ConfigAttribute> singleAttributeList = new ArrayList<>(1);singleAttributeList.add(null);for (ConfigAttribute attribute : attributes) {singleAttributeList.set(0, attribute);for (AccessDecisionVoter voter : getDecisionVoters()) {int result = voter.vote(authentication, object, singleAttributeList);if (logger.isDebugEnabled()) {logger.debug("Voter: " + voter + ", returned: " + result);}switch (result) {case AccessDecisionVoter.ACCESS_GRANTED:grant++;break;case AccessDecisionVoter.ACCESS_DENIED: // 只要有一个拒绝 就 否决授权 抛出异常throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied","Access is denied"));default:break;}}}// 执行到这儿说明没有投 否决的, grant>0 说明有投 同意的// To get this far, there were no deny votesif (grant > 0) {return;}// 说明都投了 弃权票// To get this far, every AccessDecisionVoter abstainedcheckAllowIfAllAbstainDecisions();}

  上面看了在SpringSecurity中的各种决策器外我们可以再来看看各种投票器AccessDecisionVoter

3.AccessDecisionVoter

  AccessDecisionVoter是一个投票器,负责对授权决策进行表决。表决的结构最终由AccessDecisionManager统计,并做出最终的决策。

public interface AccessDecisionVoter<S> {int ACCESS_GRANTED = 1; // 赞成int ACCESS_ABSTAIN = 0; // 弃权int ACCESS_DENIED = -1;  // 否决boolean supports(ConfigAttribute attribute);boolean supports(Class<?> clazz);int vote(Authentication authentication, S object, Collection<ConfigAttribute> attributes);}

  AccessDecisionVoter的具体实现有

  然后我们来看看常见的几种投票器

3.1 WebExpressionVoter

  最常用的,也是SpringSecurity中默认的 FilterSecurityInterceptor实例中 AccessDecisionManager默认的投票器,它其实就是 http.authorizeRequests()基于 Spring-EL进行控制权限的授权决策类。

进入authorizeRequests()方法

而对应的ExpressionHandler其实就是对SPEL表达式做相关的解析处理

3.2 AuthenticatedVoter

  AuthenticatedVoter针对的是ConfigAttribute#getAttribute() 中配置为 IS_AUTHENTICATED_FULLY 、IS_AUTHENTICATED_REMEMBERED、IS_AUTHENTICATED_ANONYMOUSLY 权限标识时的授权决策。因此,其投票策略比较简单:

 @Overridepublic int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {int result = ACCESS_ABSTAIN; // 默认 弃权 0for (ConfigAttribute attribute : attributes) {if (this.supports(attribute)) {result = ACCESS_DENIED; // 拒绝if (IS_AUTHENTICATED_FULLY.equals(attribute.getAttribute())) {if (isFullyAuthenticated(authentication)) {return ACCESS_GRANTED; // 认证状态直接放过}}if (IS_AUTHENTICATED_REMEMBERED.equals(attribute.getAttribute())) {if (this.authenticationTrustResolver.isRememberMe(authentication)|| isFullyAuthenticated(authentication)) {return ACCESS_GRANTED; // 记住我的状态 放过}}if (IS_AUTHENTICATED_ANONYMOUSLY.equals(attribute.getAttribute())) {if (this.authenticationTrustResolver.isAnonymous(authentication)|| isFullyAuthenticated(authentication)|| this.authenticationTrustResolver.isRememberMe(authentication)) {return ACCESS_GRANTED; // 可匿名访问 放过}}}}return result;}

3.3 PreInvocationAuthorizationAdviceVoter

  用于处理基于注解 @PreFilter 和 @PreAuthorize 生成的 PreInvocationAuthorizationAdvice,来处理授权决策的实现.

具体是投票逻辑

 @Overridepublic int vote(Authentication authentication, MethodInvocation method, Collection<ConfigAttribute> attributes) {// Find prefilter and preauth (or combined) attributes// if both null, abstain else call advice with themPreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);if (preAttr == null) {// No expression based metadata, so abstainreturn ACCESS_ABSTAIN;}return this.preAdvice.before(authentication, method, preAttr) ? ACCESS_GRANTED : ACCESS_DENIED;}

3.4 RoleVoter

  角色投票器。用于 ConfigAttribute#getAttribute() 中配置为角色的授权决策。其默认前缀为 ROLE_,可以自定义,也可以设置为空,直接使用角色标识进行判断。这就意味着,任何属性都可以使用该投票器投票,也就偏离了该投票器的本意,是不可取的。

 @Overridepublic int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {if (authentication == null) {return ACCESS_DENIED;}int result = ACCESS_ABSTAIN;Collection<? extends GrantedAuthority> authorities = extractAuthorities(authentication);for (ConfigAttribute attribute : attributes) {if (this.supports(attribute)) {result = ACCESS_DENIED;// Attempt to find a matching granted authorityfor (GrantedAuthority authority : authorities) {if (attribute.getAttribute().equals(authority.getAuthority())) {return ACCESS_GRANTED;}}}}return result;}

注意,决策策略比较简单,用户只需拥有任一当前请求需要的角色即可,不必全部拥有

3.5 RoleHierarchyVoter

  基于 RoleVoter,唯一的不同就是该投票器中的角色是附带上下级关系的。也就是说,角色A包含角色B,角色B包含 角色C,此时,如果用户拥有角色A,那么理论上可以同时拥有角色B、角色C的全部资源访问权限.

 @OverrideCollection<? extends GrantedAuthority> extractAuthorities(Authentication authentication) {return this.roleHierarchy.getReachableGrantedAuthorities(authentication.getAuthorities());}

7.SpringSecurity中的权限管理相关推荐

  1. java linux 权限管理_权限管理java实现(源于Linux中的权限管理算法)

    这个帖子由来已久吧,我也是到处搜到的,然后仔细学习,果然博大精深,然后加强点弄点自己的东西 我已声明 部分转载!! 向大家介绍一种很不错,也是Linux中的权限管理算法. 定义a^b为:a的b次方 假 ...

  2. 前后端分离中的权限管理思路

    在传统的前后端不分的开发中,权限管理主要通过过滤器或者拦截器来进行(权限管理框架本身也是通过过滤器来实现功能),如果用户不具备某一个角色或者某一个权限,则无法访问某一个页面. 但是在前后端分离中,页面 ...

  3. spring-security登录和权限管理

    spring security spring security 主要的两个功能是认证和授权 认证的大概流程: Username password AuthenticationFilter(自定义use ...

  4. linux中的权限管理,Linux中的用户和权限管理

    Linux是多用户,多任务操作系统:多用户是指多个用户可以同时使用系统资源,而多任务指同时运行多个进程. 用户是能够获取系统资源的权限的集合,Linux通过用户实现资源分隔. 用户组是具有相同特征用户 ...

  5. 超级账本Fabric中的权限管理和策略

    权限管理是区块链网络十分重要的功能,负责控制某个身份在某个场景下是否允许采取某个操作(如读写某个资源). 超级账本 Fabric 项目通过策略(Policy)来灵活指定各场景下的操作权限. 策略应用场 ...

  6. Linux系统中的权限管理

    一.权限查看与读取 1.权限管理 ls -l file ##文件 ls -ld dir ##目录权限查看 matadata 文件原数据 2.权限的读取 文件/目录权限信息 二.普通权限的类型及作用 1 ...

  7. IRIS中的权限管理

    下一篇:案例: 建立只能使用SQL的用户 IRIS通过认证(Authentication)与授权(Authorization)两项机制控制外部用户对系统及应用.数据资源的可访问性.因此.如需要进行权限 ...

  8. Linux 系统中的权限管理

    ### 一.权限查看及读取  ### # 1.权限查看 #         ls -l     file        ##查看文件权限 ls -ld    dir        ##查看目录权限 # ...

  9. SpringSecurity +Jwt 实现权限管理

    目录标题 原理架构图 demo的项目结构 JwtTokenUtil RestAuthenticationEntryPoint 和 RestfulAccessDeniedHandler MyUserDe ...

最新文章

  1. python协程异步原理_简单介绍Python的Tornado框架中的协程异步实现原理
  2. 查看oracle当前消耗,查找Oracle高消耗语句的方法
  3. centos 更改mysql数据库目录位置_centos更改MySQL数据库目录位置
  4. 技术系列课|音视频测试实战——记音视频测试那些事
  5. 【oracle】to_date
  6. linux安全检测及防护,Linux安全检测及防护-单选题.doc
  7. 找出一个字符串中出现次数最多的字_487,重构字符串
  8. 前端学习(2534)vue源码解析
  9. 使用key 发smtp.sendgrid.net_手把手教你使用 iOS 13 效率神器 「快捷指令」
  10. hibernate id生成策略 mysql_Hibernate中ID生成策略
  11. 【Spring】白话IoC及容器的初始化
  12. Attention Please
  13. 用grub4dos制作U盘启动盘winpe+红叶dos+maxdos+veket+linuxmint
  14. ScreenFlow for mac(最强大的屏幕录像软件)
  15. 开发环境- 配置虚拟主机域名/hosts文件 - 学习/实践
  16. evolution ubuntu邮箱_Ubuntu evolution 邮件客户端配置详解(图)
  17. 上传声音 微信小程序_微信小程序录音文件保存,播放
  18. 你不得不知道的上架app
  19. Python 切片
  20. java 调用博思得条码打印机

热门文章

  1. 活动宣传片制作的注意事项
  2. C语言中头文件和源文件的关系
  3. 写给想成为前端工程师的同学们 ―前端工程师是做什么的?
  4. 产品经理学习笔记20210214
  5. 经典名著html,经典名著读后感
  6. 基于JavaGUI的校园卡自助服务系统
  7. 全流程重构京东服务市场系统
  8. 广州大学计算机学院刘文斌,​吴文波
  9. 平面广告公司电脑应用配置需求分析
  10. 注释php一段代码,php-注释一段PHP代码,能翻译成C#最好