spring security 授权方式(自定义)及源码跟踪
spring security 授权方式(自定义)及源码跟踪
这节我们来看看spring security的几种授权方式,及简要的源码跟踪。在初步接触spring security时,为了实现它的授权,特别是它的自定义授权,在网上找了特别多的文章以及例子,觉得好难,但是现在自己尝试结合官方文档及demo来学习,颇有收获。
基于表达式Spel的访问控制
Spring Security 使用 Spring EL 进行表达式支持,不了解Spring EL的童鞋自行学习。根据文档https://docs.spring.io/spring-security/site/docs/5.4.9/reference/html5/#el-access 在IDEA中查看SecurityExpressionRoot类的上下继承关系,SecurityExpressionOperations声明了各个表达式接口,最终由WebSecurityExpressionRoot、MethodSecurityExpressionRoot实现各个具体的表达式逻辑。继承关系如下所示:

在这里我们能够知道,最常用的应该就是基于Web\Method这两种方式来进行授权我们的应用。再来看下 SecurityExpressionRoot 类中定义的最基本的 SpEL 有哪些:

我们简单介绍几个表达式接口:
| Expression | Description |
|---|---|
| hasRole(String role) | 如果当前主体具有指定的角色,则返回 true |
| hasAnyRole(String… roles) | 如果当前主体具有任何提供的角色,则返回 true |
| hasAuthority(String authority) | 如果当前主体具有指定的权限,则返回 true。 |
| hasAnyAuthority(String… authorities) | 如果当前主体具有任何提供的权限,则返回 true |
| authentication | 允许直接访问从 SecurityContext 获得的当前 Authentication 对象 |
| principal | 允许直接访问代表当前用户的主体对象 |
授权方式
基于Web/Url的安全表达式
这种方式可以对单个Url进行安全验证,也可以对批量的Url进行安全验证,比如
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// 针对/admin/api/hello 这个接口要有p1权限
.antMatchers("/admin/api/hello").hasAuthority("p1")
// 对于访问/user/api/** 的接口要有user权限
.antMatchers("/user/api/**").hasRole("USER")
.antMatchers("/app/api/**").permitAll()
.antMatchers("/css/**", "/index").permitAll()
.antMatchers("/user/**").hasRole("USER")
.and()
.formLogin()
.loginPage("/login")
.failureUrl("/login-error")
.permitAll();
}
基于Method的安全表达式
Method Security Expressions
Method security is a bit more complicated than a simple allow or deny rule. Spring Security 3.0 introduced some new annotations in order to allow comprehensive support for the use of expressions.
Spring Security 3.0 引入了一些新的注解,以便全面支持表达式的使用,分别是@PreAuthorize, @PreFilter, @PostAuthorize and @PostFilter相信大家有web开发的基础,不难知道这几个注解的意思。
@PreAuthorize:在访问方法前进行鉴权
@PreFilter:同上
@PostAuthorize:在访问方法后进行鉴权
@PostFiltert:同上
public class AdminController { @GetMapping("hello")
@PostAuthorize("hasRole('User')")
public String hello() {
return "hello, admin";
} @GetMapping("p1")
@PreAuthorize("hasAuthority('p1')")
public String p1() {
return "hello, p1";
}
}
但是基于方法的需要事先在配置类添加注解,表示开启方法验证。
@EnableGlobalMethodSecurity(securedEnabled = true,prePostEnabled = true)
根据官网介绍还有其他2中方式(基于AOP 、spring security原生的注释@Secure),有兴趣的小伙伴可以自行参阅https://docs.spring.io/spring-security/site/docs/5.4.9/reference/html5/#secure-object-impls
授权原理
根据文档https://docs.spring.io/spring-security/site/docs/5.4.9/reference/html5/#secure-object-impls指出,spring security提供了拦截器来控制安全对象的访问,例如方法调用、web请求。AccessDecisionManager 做出关于是否允许调用继续进行的调用前决定。
查看AccessDecisionManager 接口:
decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
参数说明:
- authentication:当前登录对象主体
- object: 当前安全保护对象
- configAttributes:访问当前对象必须要有的权限属性
再看看看它的三个实现类,默认的实现是根据各个实现类的投票机制来决定是否能够访问当前安全保护对象的:
AffirmativeBased:只要configAttributes中有一个权限满足就可以访问当前保护对象
ConsensusBased:满足超过一半的权限就能够访问当前保护对象
UnanimousBased:configAttributes中所有的权限都满足才能访问当前保护对象
由于我们不知道默认是哪个实现类,所以我们在三个类上的decide方法都打上断点,这样我们就能知道默认是哪个实现类了,

内部的投票实现有兴趣的小伙伴自行探索,到这样我们大概就明白spring security默认的授权实现机制了。接着我们根据该机制去实现我们的自定义授权方式。

给出官网的一张原理图
- 首先,FilterSecurityInterceptor 从 SecurityContextHolder 获得一个 Authentication
- 其次,FilterSecurityInterceptor 从传入 FilterSecurityInterceptor 的 HttpServletRequest、HttpServletResponse 和 FilterChain 创建一个 FilterInvocation
- 接下来,它将 FilterInvocation 传递给 SecurityMetadataSource 以获取 ConfigAttributes
- 最后,它将 Authentication、FilterInvocation 和 ConfigAttributes 传递给 AccessDecisionManager。
- 如果授权被拒绝,则抛出 AccessDeniedException。在这种情况下,ExceptionTranslationFilter 处理 AccessDeniedException
- 如果访问被授予,FilterSecurityInterceptor 继续使用允许应用程序正常处理的 FilterChain。
自定义授权方式
根据葫芦画瓢,我们首先需要
1、自定义一个AccessDecisionManager实现类,让它确定到底是否能够鉴权通过,能够访问保护对象;
@Component
public class CustomUrlDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute configAttribute : configAttributes) {
String needRole = configAttribute.getAttribute();
if ("ROLE_LOGIN".equals(needRole)) {
if (authentication instanceof AnonymousAuthenticationToken) {
throw new AccessDeniedException("尚未登录,请登录!");
}else {
return;
}
}
Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (GrantedAuthority authority : authorities) {
if (authority.getAuthority().equals(needRole)) {
return;
}
}
}
throw new AccessDeniedException("权限不足,请联系管理员!");
}
@Override
public boolean supports(ConfigAttribute attribute) {
return true;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
2、接着实现一个FilterInvocationSecurityMetadataSource实现类,这个类给出访问保护对象具体需要的哪些权限。
/**
* 这个类的作用,主要是根据用户传来的请求地址,分析出请求需要的角色
*/
@Component
public class CustomFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
@Autowired
MenuService menuService;
AntPathMatcher antPathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
String requestUrl = ((FilterInvocation) object).getRequestUrl();
List<Menu> menus = menuService.getAllMenusWithRole();
for (Menu menu : menus) {
if (antPathMatcher.match(menu.getUrl(), requestUrl)) {
List<Role> roles = menu.getRoles();
String[] str = new String[roles.size()];
for (int i = 0; i < roles.size(); i++) {
str[i] = roles.get(i).getName();
}
return SecurityConfig.createList(str);
}
}
return SecurityConfig.createList("ROLE_LOGIN");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> clazz) {
return true;
}
}
3、将上面2个对象添加到拦截器中,给FilterSecurityInterceptor重新设置它的这2个属性
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O object) {
object.setAccessDecisionManager(customUrlDecisionManager);
object.setSecurityMetadataSource(customFilterInvocationSecurityMetadataSource);
return object;
}
})
相信到这里,小伙伴也能根据自己实际项目需要怎样的授权方式去进行实现了,如果是AOP/@secure方式的则需要再看一下文档说明。好了,spring security的章节就到这里,后面继续学习spring security oauth2的章节。
spring security 授权方式(自定义)及源码跟踪的更多相关文章
- spring security之 默认登录页源码跟踪
spring security之 默认登录页源码跟踪 2021年的最后2个月,立个flag,要把Spring Security和Spring Security OAuth2的应用及主流程源码研究透 ...
- spring security 之自定义表单登录源码跟踪
上一节我们跟踪了security的默认登录页的源码,可以参考这里:https://www.cnblogs.com/process-h/p/15522267.html 这节我们来看看如何自定义单表认 ...
- spring security 认证源码跟踪
spring security 认证源码跟踪 在跟踪认证源码之前,我们先根据官网说明一下security的内部原理,主要是依据一系列的filter来实现,大家可以根据https://docs.sp ...
- 涨姿势:Spring Boot 2.x 启动全过程源码分析
目录 SpringApplication 实例 run 方法运行过程 总结 上篇<Spring Boot 2.x 启动全过程源码分析(一)入口类剖析>我们分析了 Spring Boot 入 ...
- Spring Boot 2.x 启动全过程源码分析
Spring Boot 2.x 启动全过程源码分析 SpringApplication 实例 run 方法运行过程 上面分析了 SpringApplication 实例对象构造方法初始化过程,下面继续 ...
- spring MVC cors跨域实现源码解析
# spring MVC cors跨域实现源码解析 > 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议.IP.http方法任意一个不同就 ...
- HTTP严格安全传输(HTTP Strict Transport Security, HSTS)chromuim实现源码分析(一)
// HTTP strict transport security (HSTS) is defined in// http://tools.ietf.org/html/ietf-websec-stri ...
- Spring Boot REST(二)源码分析
Spring Boot REST(二)源码分析 Spring 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) SpringBoot RE ...
- spring MVC cors跨域实现源码解析 CorsConfiguration UrlBasedCorsConfigurationSource
spring MVC cors跨域实现源码解析 spring MVC cors跨域实现源码解析 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议 ...
随机推荐
- Java:并发笔记-06
Java:并发笔记-06 说明:这是看了 bilibili 上 黑马程序员 的课程 java并发编程 后做的笔记 5. 共享模型之无锁 本章内容 CAS 与 volatile 原子整数 原子引用 原子 ...
- 【二食堂】Beta - Scrum Meeting 3
Scrum Meeting 3 例会时间:5.15 18:30~18:50 进度情况 组员 当前进度 今日任务 李健 1. 继续完成文本区域划词添加的功能 issue 1. 划词功能已经实现,继续开发 ...
- Vue项目搭建常用的配置文件,request.js和vue.config.js
request.js用来请求数据,封装的代码如下: import axios from 'axios' const request = axios.create({ timeout: 5000 }) ...
- 2021.10.10考试总结[NOIP模拟73]
T1 小L的疑惑 对于\(P_i\),如果所有比\(P_i\)小的数加起来也达不到\(P_i-1\),那么值域肯定不连续.否则设原来值域最大值为\(mx\),则\(P_i\)会让值域最大值增致\(mx ...
- [调试笔记] 晚测5 T1 容易题
众所周知,sbwzx在考试一结束就嚷嚷T1是个sb题.那他为什么调了2小时才调出来呢?快和小编一起看看吧. Sb题:指除了sbwzx别人都能做出来的题 1.CE:震惊!sbwzx竟然连map都不会用, ...
- [BZOJ3307] 雨天的尾巴-----------------线段树进阶
虽然是个板子,但用到了差分思想. Description N个点,形成一个树状结构.有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最 ...
- RocketMQ源码详解 | Broker篇 · 其一:线程模型与接收链路
概述 在上一节 RocketMQ源码详解 | Producer篇 · 其二:消息组成.发送链路 中,我们终于将消息发送出了 Producer,在短暂的 tcp 握手后,很快它就会进入目的 Broker ...
- 一步一步学ROP之gadgets和2free篇(蒸米spark)
目录 一步一步学ROP之gadgets和2free篇(蒸米spark) 0x00序 0x01 通用 gadgets part2 0x02 利用mmap执行任意shellcode 0x03 堆漏洞利用之 ...
- Spring Cloud Alibaba 使用 feign 和 rebion 进行服务消费
微服务的服务消费,一般是使用 feign 和 rebion 调用服务提供,进行服务的消费,本文将实战使用代码讲解服务的消费. 微服务环境的搭建 创建一个 springboot 项目,springboo ...
- k8s入坑之路(14)scheduler调度 kubelet管理及健康检查 更新策略
kubelet 主要功能 Pod 管理 在 kubernetes 的设计中,最基本的管理单位是 pod,而不是 container.pod 是 kubernetes 在容器上的一层封装,由一组运行在同 ...