7.SpringSecurity中的权限管理

news2024/12/28 3:12:44

SpringSecurity中的权限管理

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

一、权限管理的实现

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

image.png

放开了相关的注解后我们在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>

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

image.png

二、权限校验的原理

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

image.png

  通过前面介绍我们请求,当一个请求到来的时候会经过上面的过滤器来一个个来处理对应的请求,最后在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方法中。

image.png

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

image.png

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

image.png

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

2.AccessDescisionManager

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

image.png

  AccessDescisionManager有三个默认的实现

image.png

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 abstained
		checkAllowIfAllAbstainDecisions();
	}

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 + 1

				break;

			case AccessDecisionVoter.ACCESS_DENIED:
				deny++; // 否决的 deny + 1

				break;

			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 abstained
		checkAllowIfAllAbstainDecisions();
	}

  上面代码的逻辑还是非常简单的,只需要注意下授予权限和否决权限相等时的逻辑就可以了。决策器也考虑到了这一点,所以提供了 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 votes
		if (grant > 0) {
			return;
		}
		// 说明都投了 弃权票
		// To get this far, every AccessDecisionVoter abstained
		checkAllowIfAllAbstainDecisions();
	}

  上面看了在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的具体实现有

image.png

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

3.1 WebExpressionVoter

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

image.png

进入authorizeRequests()方法

image.png

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

3.2 AuthenticatedVoter

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

	@Override
	public int vote(Authentication authentication, Object object, Collection<ConfigAttribute> attributes) {
		int result = ACCESS_ABSTAIN; // 默认 弃权 0
		for (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,来处理授权决策的实现.

image.png

具体是投票逻辑

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

3.4 RoleVoter

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

	@Override
	public 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 authority
				for (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的全部资源访问权限.

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/392715.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ccc-pytorch-卷积神经网络介绍(5)

文章目录一、卷积二、池化三、Batch Norm四、经典卷积网络简单介绍一、卷积 卷积连续函数形式&#xff1a; F(x)∫f(t)g(x−t)dtF(x)\int f(t)g(x-t)dtF(x)∫f(t)g(x−t)dt 物理意义是一个函数在另一个函数上的加权叠加。在2D卷积中指卷积核在数据矩阵中分割出的矩阵和卷积核相…

PMP和软考高项集成,更应该考哪个呢?

要看你自己的偏向&#xff0c;要说考的话&#xff0c;我是觉得都值得考的&#xff0c;一个证一份技术嘛。 我给你稍微介绍一下&#xff0c;PMP都是美国PMI发起的考试&#xff0c;软考高项是国内的考试。PMP是项目管理证书&#xff0c;学习的内容是项目管理&#xff0c;包含大约…

研报精选230306

目录 【行业230306东亚前海证券】食品饮料行业2023年年度投资策略&#xff1a;复苏在途&#xff0c;蓄势待发【行业230306国金证券】基础化工行业研究&#xff1a;MDI价格上行&#xff0c;新一轮国企改革在即【行业230306中银证券】华为汽车产业链深度报告&#xff1a;三种合作…

k8s控制器

目录 一、控制器简介 二、控制器类型 1、RC和RS 2、Deployment 3、DaemonSet 4、Job 5、CronJob 6、StateFulSet 7、HPA 一、控制器简介 在kubernetes中&#xff0c;按照Pod的创建方式可以将其分为两类&#xff1a; 自主式:kubernetes直接创建出来的Pod&#xff0c;…

【Alamofire】【Swift】属性包装器注解@propertyWrapper

Alamofire 中的源码例子 import Foundationprivate protocol Lock {func lock()func unlock() }extension Lock {/// Executes a closure returning a value while acquiring the lock.////// - Parameter closure: The closure to run.////// - Returns: The value…

9.SpringSecurity核心过滤器-SecurityContextPersistenceFilter

SpringSecurity核心过滤器-SecurityContextPersistenceFilter 一、SpringSecurity中的核心组件 在SpringSecurity中的jar分为4个&#xff0c;作用分别为 jar作用spring-security-coreSpringSecurity的核心jar包&#xff0c;认证和授权的核心代码都在这里面spring-security-co…

Promise入门

Promise入门 Promise的基本概念 男女相爱了&#xff0c;女方向男方许下一个承诺怀孕new Promise&#xff0c;这是会产生两种结果怀上(resolve)和没怀上(reject)&#xff0c;resolve对应then&#xff0c;reject对应catch&#xff0c;无论是否怀上都会执行finally。 <script&…

【论文速递】CASE 2022 - EventGraph: 将事件抽取当作语义图解析任务

【论文速递】CASE 2022 - EventGraph: 将事件抽取当作语义图解析任务 【论文原文】&#xff1a;https://aclanthology.org/2022.case-1.2.pdf 【作者信息】&#xff1a;Huiling You, David Samuel, Samia Touileb, and Lilja vrelid 论文&#xff1a;https://aclanthology.o…

sql server 对比两个查询性能 ,理解Elapsed Time、CPU Time、Wait Time

分析 SET STATISTICS TIME ONyour sqlSET STATISTICS TIME OFF由上图分析: cpu time 是查询执行时占用的 cpu 时间。如果了解系统的多任务机制&#xff0c;就会知道系统会将整个 cpu 时间分为一个一个时间片&#xff0c;平均分配给运行的线程——一个线程在 cpu 上运行一段时间…

《PyTorch深度学习实践9》——卷积神经网络-高级篇(Advanced-Convolution Neural Network)

一、1∗11*11∗1卷积 由下面两张图&#xff0c;可以看出1∗11*11∗1卷积可以显著降低计算量。 通常1∗11*11∗1卷积还有以下功能&#xff1a; 一是用于信息聚合&#xff0c;同时增加非线性&#xff0c;1∗11*11∗1卷积可以看作是对所有通道的信息进行线性加权&…

Air101|Air103|Air105|Air780E|ESP32C3|ESP32S3|Air32F103开发板:概述及PinOut

1、合宙Air101&#xff08;芯片及开发板&#xff09; 合宙Air101是一款QFN32 封装&#xff0c;4mm x 4mm 大小的mcu。通用串口波特率&#xff0c;设置波特率为921600。 ​ 管脚映射表 GPIO编号 命名 默认功能及扩展功能 0 PA0 BOOT 1 PA1 I2C_SCL/ADC0 4 PA4 I2C_S…

前端必备技术之——AJAX

简介 AJAX 全称为 Asynchronous JavaScript And XML&#xff0c;就是异步的 JS 和 XML(现在已经基本被json取代)。通过 AJAX 可以在浏览器中向服务器发送异步请求&#xff0c;最大的优势&#xff1a;无刷新获取数据。AJAX 不是新的编程语言&#xff0c;而是一种将现有的标准组…

揭秘关键一环!数据安全服务大盘点

数据安全服务&#xff0c;数据安全体系建设的关键一环。通过数据安全服务解决数据安全建设难题&#xff0c;得到越来越多的重视。不久前&#xff0c;《工业和信息化部等十六部门关于促进数据安全产业发展的指导意见》发布&#xff0c;明确“壮大数据安全服务”&#xff0c;推进…

VScode 插件【配置】

写这篇博客的原因&#xff1a; vscode 很久以前的插件&#xff0c;忘记是干什么的了记录 vscode 好用的插件 插件介绍&#xff08;正文开始&#xff09; Auto Rename tag 开始/关闭标签内容 同步 Chinese (Simplified) VScode 中文化 CSS Peek 通过 html 代码查找到引用的样式…

Linux - 磁盘I/O性能评估

文章目录概述RAID文件系统与裸设备的对比磁盘I/O性能评判标准常用命令“sar –d”命令组合“iostat –d”命令组合“iostat –x”单独统计某个磁盘的I/O“vmstat –d”命令组合小结概述 RAID 可以根据应用的不同&#xff0c;选择不同的RAID方式 如果一个应用经常有大量的读操…

Flink(Java版)学习

一、Flink流处理简介 1.Flink 是什么 2.为什么要用 Flink 3.流处理的发展和演变 4.Flink 的主要特点 5.Flink vs Spark Streaming 二、快速上手 1.搭建maven工程 2.批处理WordCount 3.流处理WordCount 三、Flink部署 1.Standalone 模式 2.Yarn 模式 3.Kubernetes 部署 四、F…

PyTorch深度学习:60分钟入门

PyTorch深度学习&#xff1a;60分钟入门 本教程的目的: 更高层级地理解PyTorch的Tensor库以及神经网络。训练一个小的神经网络来对图像进行分类。 本教程以您拥有一定的numpy基础的前提下展开 Note: 务必确认您已经安装了 torch 和 torchvision 两个包。 这是一个基于Pytho…

Cannot resolve symbol ‘String‘或Cannot resolve symbol ‘System‘ ——IDEA

IDEA中运行报错&#xff0c;“Cannot resolve symbol ‘String‘”解决方案 ‘System‘解决 参考一&#xff1a;(31条消息) IDEA2021 提示“Cannot resolve symbol ‘String‘”解决方案_idea无法解析符号string_YT20233的博客-CSDN博客https://blog.csdn.net/CNMBZY/article…

docker 的安装与卸载

一 卸载旧版本的 Docker 1.列出系统中已安装的docker包 yum list installed | grep docker 示例 2.卸载docker yum -y remove docker-ce-cli.x86_64 yum -y remove docker-ce.x86_64 yum -y remove containerd.io 示例 二 Docker的安装 1.安装 Docker 所需的依赖&#…

虹科方案| 助力高性能视频存储解决方案-1

虹科电子科技有限公司是ATTO技术公司在中国的官方合作伙伴。依附于我们十多年的客户积累和方案提供经验&#xff0c;虹科与 ATTO共同致力于为数据密集型计算环境提供网络和存储连接以及基础架构解决方案&#xff0c;为客户提供更高性能的产品与服务。无论您的工作流程面临何种挑…