SpringSecurity 权限管理的实现

news2025/1/4 15:37:05

文章目录

  • 前言
  • 权限管理的实现
  • 权限校验的原理
    • FilterSecurityInterceptor
    • AccessDescisionManager
      • AffirmativeBased
      • ConsensusBased
      • UnanimousBased
    • AccessDecisionVoter
      • WebExpressionVoter
      • AuthenticatedVoter
      • PreInvocationAuthorizationAdviceVoter
      • RoleVoter
      • RoleHierarchyVoter

前言

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";
    }
}

SpringSecurity提供的注解

@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中做认证和权限的校验操作。

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来处理。

AccessDescisionManager

AccessDescisionManager字面含义是决策管理器。

image.png

AccessDescisionManager有三个默认的实现

image.png

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

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,即代表允许授予权限和拒绝权限相等,且同时也代表授予访问权限。

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

AccessDecisionVoter

再来看看各种投票器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

常见的几种投票器

WebExpressionVoter

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

image.png

进入authorizeRequests()方法

image.png

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

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;
	}

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;
	}

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;
	}

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/1024306.html

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

相关文章

HEXO 基本使用

1 新建、编辑并预览文章 1. 新建文章 hexo new [layout] title # 或 hexo n [layout] title创建文章前要先选定模板&#xff0c;在hexo中也叫做布局。hexo支持三种布局&#xff08;layout&#xff09;&#xff1a;post(默认)、draft、page。我们先介绍如何使用已有布局…

【Unity 实用工具篇】✨ | 编辑器扩展插件 Odin Inspector,进阶功能学习

前言【Unity 实用工具篇】✨ | 编辑器扩展插件 Odin,进阶功能学习一、Odin Attributes Overview 特性篇1.1 提示信息1.1.1 Title、InfoBox1.1.2 DetailedInfoBox1.2 各类限制1.2.1 限制数值范围1.2.2 限制只读1.2.3 限制属性搜索范围1.4 Required 限制必须赋值1.3 数值变化时触…

我们感知的内容是由大脑最支持的假设决定的吗?

某种意义上讲&#xff0c;我们从未直接体验到感觉信号本身&#xff0c;而是通过对这些信号的解读和处理来感知和理解外界。 感觉信号是由我们的感官系统接收到的来自外部世界的刺激所产生的物理信号。例如&#xff0c;视觉感觉信号由我们的眼睛接收光线并将其转化为神经脉冲&am…

爬虫获取一个网站内所有子页面的内容

上一篇介绍了如何爬取一个页面内的所有指定内容&#xff0c;本篇讲的是爬去这个网站下所有子页面的所有指定的内容。 可能有人会说需要的内容复制粘贴&#xff0c;或者直接f12获取需要的文件下载地址一个一个下载就行了&#xff0c;但是如下图十几个一级几十个二级一百多个疾病…

STM32-无人机-电机-定时器基础知识与PWM输出原理

电机控制基础——定时器基础知识与PWM输出原理 - 掘金单片机开发中&#xff0c;电机的控制与定时器有着密不可分的关系&#xff0c;无论是直流电机&#xff0c;步进电机还是舵机&#xff0c;都会用到定时器&#xff0c;比如最常用的有刷直流电机&#xff0c;会使用定时器产生PW…

「UG/NX」Block UI 指定方位SpecifyOrientation

✨博客主页何曾参静谧的博客📌文章专栏「UG/NX」BlockUI集合📚全部专栏「UG/NX」NX二次开发「UG/NX」BlockUI集合「VS」Visual Studio「QT」QT5程序设计「C/C+&#

怎么打开mysql题库练习系统

在正式考试的时候&#xff0c;大题上方会有一个启动的按钮&#xff0c;非常明显。 我在考试之前买了题库&#xff0c;但是一直没找到怎么进入mysql &#xff08;指的不是平时自己做项目的入口&#xff0c;是考试系统仿真&#xff09; 其实是要下载一个软件&#xff1a; 就在…

大数据与云计算实验一

检查是否开启 sudo service docker status 开启服务 sudo service docker start 运行服务 sudo docker run -itd -p 8080:80 nginx 查询ID docker ps -all 进入容器shell sudo docker exec -it <容器ID或容器名称> /bin/bash 找到/usr/share/nginx/html/index.…

【小沐学CAD】虚拟仿真开发工具:GL Studio

文章目录 1、简介2、软件功能3、应用行业3.1 航空3.2 汽车3.3 防御3.4 工业3.5 电力与能源3.6 医疗3.7 空间3.8 科技 结语 1、简介 https://disti.com/gl-studio/ https://ww2.mathworks.cn/products/connections/product_detail/gl-studio.html DiSTI 是 HMI 软件、虚拟驾驶舱…

zabbix自定义key

用户参数&#xff08;zabbix-agent&#xff09; 介绍 自定义用户参数&#xff0c;也就是自定义key&#xff0c;有时&#xff0c;你可能想要运行一个代理检查&#xff0c;而不是Zabbix的预定义&#xff0c;你可以编写一个命令来检索需要的数据&#xff0c;并将其包含在代理配置…

大模型如何赋能智能客服

2022年&#xff0c;大模型技术的出色表现让人们瞩目。随着深度学习和大数据技术的发展&#xff0c;大模型在很多领域的应用已经成为可能。许多公司开始探索如何将大模型技术应用于自己的业务中&#xff0c;智能客服也不例外。 智能客服是现代企业中非常重要的一部分&#xff0…

OpenCascade插件化三维算法研究平台

基于OpenCascade 7.7.0、QT 6.5.2开发了一个插件化三维算法研究平台。 由于采用插件化技术&#xff0c;平台启动极快&#xff0c;用户用到相关功能时&#xff0c;系统才载入相关模块。插件化平台&#xff0c;不仅可以作为三维建模、展示、格式转换等工具软件&#xff0c;还可以…

从菜鸟到吃鸡高手!教你提高战斗力的顶级游戏干货!

大家好&#xff01;作为专业吃鸡行家&#xff0c;今天我将为大家分享一些与众不同的干货&#xff0c;助你成为吃鸡界的顶级战士&#xff01; 首先&#xff0c;游戏战斗力的提升是每个吃鸡玩家的追求。通过使用绝地求生作图工具&#xff0c;你可以简单快捷地分享你的战斗经验与技…

生活中的光伏

光伏作为可再生能源发电的主力军&#xff0c;逐渐被更多的电力用户所接受。随着光伏发电的普及&#xff0c;人们在日常生活中对太阳能光伏发电的利用率越来越高。 1、太阳能公交站台 太阳能公交站台&#xff0c;是指公交中途站点供电方式由原来的直接接入电源改为太阳能供电。…

InputAction的使用

感觉Unity中InputAction的使用&#xff0c;步步都是坑。 需求点介绍 当用户长按0.5s 键盘X或者VR left controller primaryButton (即X键)时&#xff0c;显示下一个图片。 步骤总览 创建InputAction资产将该InputAction资产绑定到某个GameObject上在对应的script中&#xf…

基于matlab实现的卡尔曼滤波匀加速直线运动仿真

完整程序&#xff1a; clear clc %% 初始化参数 delta_t 0.1; %采样时间 T 8; %总运行时长 t 0:delta_t:T; %时间序列 N length(t); %序列的长度 x0 0; %初始位置 u0 0; %初速度 U 10; %控制量、加速度 F [1 delta_t 0 1]; %状态转移矩阵 B …

《模型结构图绘制 -- Axure 软件使用教程》学习笔记

《模型结构图绘制 – Axure 软件使用教程》 Axure10是订阅制收费软件可以根据鼠标位置放大试图 界面介绍 页面尺寸&#xff1a;Auto&#xff08;右上角&#xff09; 页面可以自动延展尺寸

CentOS 7 安装踩坑

CentOS与Ubuntu并称为Linux最著名的两个发行版&#xff0c;但由于笔者主要从事深度学习图像算法工作&#xff0c;Ubuntu作为谷歌和多数依赖库的亲儿子占据着最高生态位。但最近接手的一个项目里&#xff0c;甲方指定需要在CentOS7上运行项目代码&#xff0c;笔者被迫小小cos了一…

Linux-软件安装/项目部署

软件安装 软件安装方式 在Linux系统中&#xff0c;安装软件的方式主要有四种&#xff0c;这四种安装方式的特点如下&#xff1a; 安装JDK 上述我们介绍了Linux系统软件安装的四种形式&#xff0c;接下来我们就通过第一种(二进制发布包)形式来安装JDK。 JDK具体安装步骤如下&…

nodejs 如何在npm发布自己的包 <记录>

一、包结构 必要结构&#xff1a; 一个包对应一个文件夹&#xff08;文件夹名不是包名&#xff0c;但最好与包名保持一致&#xff0c;包名以package.json中的name为主&#xff09;包的入口文件index.js包的配置文件package.json包的说明文档README.md 二、需要说明的文件 1.配…