【深入浅出 Spring Security(十)】权限管理的概述和使用详情

news2024/10/6 8:31:00

权限管理

  • 一、授权的核心概念
  • 二、权限管理策略
    • 权限表达式(SpEL Spring EL)
    • 1. 基于 URL 的权限管理(过滤器)
      • 基本用法
    • 2. 基于 方法 的权限管理(AOP)
      • @EnableGlobalMethodSecurity
      • 基本用法
  • 三、权限管理之版本问题

一、授权的核心概念

在专栏内前面的博客中,详细地述说了认证过程,我们得知认证成功之后会将当前登录的用户信息保存到 Authentication 对象中,Authentication 对象中有一个 getAuthorities() 方法,用来返回当前登录用户具备的权限信息,也就是当前用户具有权限信息。该方法的返回值为 Collection<? extends GrantedAuthority>,当需要进行权限判断时,就会根据集合返回权限信息调用相应方法进行判断。(而认证后上下文是如何去得到这个 Authentication 的,在前期博客中也详细解释了——通过 SecurityContextHolder 类进行获取)

在这里插入图片描述看返回值的占位符,针对于返回值为 GrantedAuthority 应该如何理解?是角色还是权限?

针对于授权管理,一般使用的是咱熟悉的 RBAC 模型,其中 R 可称为 Resources,也可称为 Roles,也就是我们针对授权可以是基于角色权限管理基于资源权限管理。从设计层面来说,角色和权限是两个完全不同的东西:权限是一些具体操作,角色则是某些权限集合。如:READ_BOOK 和 ROLE_ADMIN 是完全不同的。因此至于返回值是什么取决于业务设计情况:

  • 基于角色权限设计就是:用户<=>角色<=>资源 三者关系 返回就是用户的角色
  • 基于资源权限设计就是:用户<=>权限<=>资源 三者关系 返回就是用户的 权限
  • 基于角色和资源权限设计就是:用户<=>角色<=>权限<=>资源 返回统称为用户的权限

为什么可以统称为权限 ?因为从代码层面角色和权限没有太大不同,都是权限。特别是在 Spring Security 中,角色和权限处理方式基本上都是一样的(都是字符串)。唯一区别是 Spring Security 在很多时候会自动给角色添加一个 ROLE_ 前缀,而权限则不会自动添加(这是源于 Spring Security 的内部实现)。

二、权限管理策略

实际开发中,认证在项目进行中总是固定的,而权限管理一般是可变的,更需要开发人员去了解的。

Spring Security 中提供的权限管理策略主要有两种类型:

可以访问系统中的哪些资源?<=> (http、url、method

  • 基于过滤器的权限管理(FilterSecurityInterceptor

    • 基于过滤器的权限管理主要是用来拦截 HTTP 请求,拦截下来之后,根据 HTTP 请求地址进行权限校验。
  • 基于 AOP 的权限管理(MethodSecurityInterceptor

    • 基于 AOP 权限管理主要是用来处理方法级别的权限问题。当需要调用一个方法时,通过 AOP 将操作拦截下来,然后判断用户是否具备相关的权限。

权限表达式(SpEL Spring EL)

在解释俩权限管理策略之前,先阐述一下 Spring Security 内置的权限表达式(SpEL 表达式)。它主要是用来进行权限配置的,我们可以在请求的 URL 或者 访问的方法上(注解),通过权限表达式来配置需要的权限。以下是 Spring Security 内置的权限表达式:

在这里插入图片描述

表达式作用
hasRole(String role)当前用户是否具备指定角色
hasAnyRole(String… roles)当前用户是否具备指定角色中的任意一个
hasAuthority(String authority)当前用户是否具备指定的权限
hasAnyAuthority(String… authorities)当前用户是否具备指定权限中的任意一个
permitAll()允许所有的请求/调用
denyAll()拒绝所有的请求/调用
isAnonymous()当前用户是否是一个匿名用户
isAuthenticated()当前用户是否已经认证成功
isRememberMe()当前用户是否哦是通过 RememberMe 自动登录的
isFullyAuthenticated()当前用户是否既不是匿名用户又不是通过RememberMe自动登录的
hasPermission(Object target,Object permission)当前用户是否具备指定目标的指定权限
hasPermission(Object targetId,String targetType,Object permission)当前用户是否具备指定目标的指定权限
getAuthentication()获取 Authentication 对象
authentication这个是从 SecurityContext 中获取的 Authentication 对象,即已经认证了的
principal代表当前登录主体 Principal

一般来说,Spring Security 提供的这些内置权限表达式,就已经足够使用了。

1. 基于 URL 的权限管理(过滤器)

基于 URL 地址的权限管理主要是通过过滤器 FilterSecurityInterceptor 来实现的。如果开发者配置了基于 URL 地址的权限管理,那么 FilterSecurityInterceptor 就会被自动添加到 Spring Security 过滤器链中,在过滤器链红拦截下请求,然后分析当前用户是否具备请求所需要的权限,如果不具备,则抛出异常。

基本用法

(在创建 SpringBoot 项目的时候,不小心创建成了 6.1 版本的了,不想改了,下面测试就是按最新版来的,有些方法都被启用了,下面是最新版的代码,还有 6.1 版本的SpringSecurity 在@EnableWebSecurity 注解的元注解中,已经没有 @Configuration 了,所以记得在外面加上,这是一个坑。)

配置类

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public UserDetailsService userDetailsService(){
        return new InMemoryUserDetailsManager(
                User.withUsername("root").password("{noop}123").roles("ADMIN","USER").authorities("READ_INFO").build(),
                User.withUsername("admin").password("{noop}123").roles("USER").build(),
                User.withUsername("myz").password("{noop}123").authorities("READ_INFO").build()
        );
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http,@Autowired UserDetailsService userDetailsService) throws Exception {
        AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class);
        builder.userDetailsService(userDetailsService);
        return builder.build();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeRequests()
                .requestMatchers("/user").hasRole("USER")
                .requestMatchers("/getInfo").hasAuthority("READ_INFO")
                .requestMatchers("/admin").hasRole("ADMIN")
                .anyRequest().authenticated()
                .and()
                .formLogin(configurer->{})
                .csrf(configurer->configurer.disable())
                .build();
    }

}

TestController

@RestController
public class TestController {

    @GetMapping("/user")
    public String user(){
        return "user ok!";
    }

    @GetMapping("/admin")
    public String admin(){
        return "admin ok!";
    }

    @GetMapping("/getInfo")
    public String getInfo(){
        return "info ok!";
    }

}

测试效果:myz/123 登录的用户只能访问 /getInfo;admin/123 登录的用户只能访问 /user;root/123 登录的用户三个测试资源路径都能访问。

2. 基于 方法 的权限管理(AOP)

基于方法的权限管理主要是通过 AOP 来实现的,Spring Security 中通过 MethodSecurityInterceptor 来提供相关的实现。不同在于 URL 中的 FilterSecurityInterceptor 只是这种请求之前进行前置处理,而 MethodSecurityInterceptor 除了前置处理之外还可以进行后置处理(相当于是环绕的切面)。前置处理就是在请求之前判断是否具备相应的权限,后置处理则是对方法的执行结果进行二次过滤。前置处理和后置处理分别对应了不同的实现类。

@EnableGlobalMethodSecurity

EnableGlobalMethodSecurity 注解是用来开启权限注解的,注解被开启了才能在项目代码中使用。用法如下:

@EnableWebSecurity
@EnableGlobalMethodSecurity(
        prePostEnabled = true,
        securedEnabled = true,
        jsr250Enabled = true
)
public class SecurityConfig {
}
  • perPostEnabled:开启 Spring Security 提供的四个权限注解,@PostAuthorize、@PostFilter、@PreAuthorize 以及 @PreFilter。
  • securedEnabled:开启 Spring Security 提供的 @Secured 注解,该注解不支持权限表达式
  • jsr250Enabled:开启 JSR-250 提供的注解,主要是 @DenyAll、@PermitAll、@RolesAll 。同样这些注解也不支持权限表达式

以上所提到的注解含义如下表所示:

注解含义
@PostAuthorize在目标方法执行之后进行权限校验
@PostFilter在目标方法执行在之后对返回结果进行过滤
@PreAuthorize在目标方法执行之前进行权限校验
@PreFilter在目标方法执行之前对方法参数进行过滤
@Secured访问目标方法必须具各相应的角色
@DenyAll拒绝所有访问
@PermitAll允许所有访问
@RolesAllowed访问目标方法必须具备相应的角色

这些基于方法的权限管理相关的注解,一般来说只要设置 prePostEnabled=true 就够用了,也就是解放表中前四个注解。

基本用法

SecurityConfig

@EnableWebSecurity
@EnableGlobalMethodSecurity(
        prePostEnabled = true,
        securedEnabled = true,
        jsr250Enabled = true
)
public class SecurityConfig {

    @Bean
    public UserDetailsService userDetailsService(){
        return new InMemoryUserDetailsManager(
                User.withUsername("root")
                        .password("{noop}123")
                        // .authorities("READ_INFO")
                        .roles("ADMIN","USER")
                        .build(),
                User.withUsername("admin").password("{noop}123").roles("USER").build(),
                User.withUsername("myz").password("{noop}123").authorities("READ_INFO").build()
        );
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
        AuthenticationManagerBuilder builder = http.getSharedObject(AuthenticationManagerBuilder.class);
        builder.userDetailsService(userDetailsService());
        return builder.build();
    }

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        return http.authorizeRequests()
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .and()
                .csrf(configurer->configurer.disable())
                .build();
    }


}

AuthorizeMethodController

@RestController
@RequestMapping("/hello")
public class AuthorizeMethodController {

    @GetMapping("/getInfo")
    public Object getInfo(){
        System.out.println(SecurityContextHolder.getContext().getAuthentication().getName());
        return SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    }


    @PreAuthorize("hasRole('ADMIN') and authentication.name=='root'")
    @GetMapping
    public String hello(){
        return "hello";
    }

    @PreAuthorize("authentication.name==#name")
    @GetMapping("/name")
    public String hello(String name){
        return "hello:" + name;
    }

    @PreFilter(value="filterObject.id%2!=0",filterTarget = "users")// filterTarget 必须是 数组 集合类型
    @PostMapping("/users")
    public void addUsers(@RequestBody List<User> users){
        System.out.println("users = " + users);
    }

    @PostAuthorize("returnObject.id==1")
    @GetMapping("/userId")
    public User getUserById(Integer id){
        return new User("myz",id);
    }

    @PostFilter("filterObject.id%2==0")// 用来对方法的返回值进行过滤,filterObject也是需要是集合或者是数组才行
    @GetMapping("/lists")
    public List<User> getAll(){
        List<User> users = new ArrayList<>();
        for(int i=0;i<10;++i){
            users.add(new User("myz:" + i, i));
        }
        return users;
    }



    @Secured("ROLE_USER")// 只能判断角色
    @GetMapping("/secured")
    public User getUserByUsername(){
        return new User("secured",999);
    }

    @Secured({"ROLE_USER","ROLE_ADMIN"})
    @GetMapping("/username")
    public User getUsername(String username){
        return new User(username,11111);
    }

}

自定义 User

@NoArgsConstructor
@AllArgsConstructor
@Data
public class User {
    private String name;
    private Integer id;

}

测试效果:略~

三、权限管理之版本问题

注意:在 Spring Security 5.7.8 版本中,Spring Security 中内部提供的 UserDetails 实现类 User 的权限添加是取决于后者,即使用 rolesauthorities 方法进行用户对象权限添加时,User 对象实际的权限取决于后使用的方法,这源于如下的内部实现:

在这里插入图片描述
在这里插入图片描述
通过源码可以看见,内部属性 authorities 集合对象,每次调用 roles 或者 authorities 方法都重新实例化赋值了。

而在最新版本 6.1.0 中,其内部属性 authorities 本就是一个空的集合对象,然后使用 roles 和 authorities 方法时,是往内部 authorities 集合中进行添加元素。即可同时使用。

但小编认为在实际中我们会自己搭配自己的 UserDetails 实现类,自定义方法实现,所以这些问题在一定程度上也不会遇到。但是在使用 InMemoryUserDetailsManager 进行某测试的时候,需要注意这一点。毕竟持久化的用户信息较有意义些。

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

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

相关文章

游戏中用脚开飞机实现方案

看看这个图片&#xff0c;有人用脚开飞机&#xff0c;用几个踏板去控制&#xff0c;在游戏中&#xff0c;开飞机的操作比较简单&#xff0c;上升&#xff0c;下降&#xff0c;加减油门&#xff0c;方向左&#xff0c;方向右。 android设备中&#xff0c;使用模拟点击就可以实现…

好用的电容笔有哪些推荐?apple pencil的平替笔测评

随着平板电脑在校园、办公室中的应用越来越广泛&#xff0c;需要一种具有良好性能的电容笔。苹果品牌原装的这支电容笔&#xff0c;虽然功能很强&#xff0c;但因为其的价格实在是太贵了&#xff0c;所以只是用来学习记笔记&#xff0c;实在是太浪费了。所以&#xff0c;哪个电…

python接口自动化(一)--什么是接口、接口优势、类型(详解)

简介 经常听别人说接口测试&#xff0c;接口测试自动化&#xff0c;但是你对接口&#xff0c;有多少了解和认识&#xff0c;知道什么是接口吗&#xff1f;它是用来做什么的&#xff0c;测试时候要注意什么&#xff1f;坦白的说&#xff0c;笔者之前也不是很清楚。接下来先看一下…

计算机网络——自顶向下方法(第一章学习记录)

什么是Internet? 可以从两个不同的方面来理解Internet。&#xff08;它的构成。它的服务&#xff09; 1.因特网的主要构成 处在因特网的边缘部分就是在因特网上的所有主机&#xff0c;这些主机又称为端系统&#xff08;end system&#xff09;&#xff0c;端系统通过因特网服…

openEuler+Linaro合作成果展示|2023开放原子全球开源峰会

2023年6月11&#xff5e;13日&#xff0c;2023年开放原子全球开源峰会&#xff08;OpenAtom&#xff09;在北京经济开发区北人亦创国际会展中心召开&#xff0c;本届峰会旨在搭建全球开源生态发展合作交流平台&#xff0c;聚焦开源生态建设发展&#xff0c;并组织了openEuler、…

STL容器——unordered_set的用法

0、概述 unordered_set容器&#xff0c;可直译为无序 set 容器。即 unordered_set容器和 set 容器很像&#xff0c;唯一的区别就在于 set容器会自行对存储的数据进行排序&#xff0c;而 unordered_set 容器不会。下面是set、multiset和unordered_set之间的差别。 注意这三种集…

计算机网络管理 SNMP协议实用工具MIB Browser的安装和使用

⬜⬜⬜ &#x1f430;&#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;(*^▽^*)欢迎光临 &#x1f7e7;&#x1f7e8;&#x1f7e9;&#x1f7e6;&#x1f7ea;&#x1f430;⬜⬜⬜ ✏️write in front✏️ &#x1f4dd;个人主页&#xff1a;陈丹宇jmu &am…

qt常用基本类

文章目录 点直线时间相关的类 qt里面打印log或者打印东西&#xff0c;自带打印函数qDebug 里面<<插入操作符可以写任意类型 qDebug()<<"im ssss"<<520; //默认给你带换行//也能加endl&#xff0c;那就多换一行并不是说printf或者cout就不能用了…

[进阶]Java:阶段项目02——首页、登录、客户操作页

代码演示&#xff1a; public class MovieSystem {/*** 定义系统的数据容器用户存储数据* 1.存储很多用户&#xff08;客户对象&#xff0c;商家对象&#xff09;*/public static final List<User> ALL_USERS new ArrayList<>();/*** 2.存储系统全部商家和其排片…

Zebec Protocol 与 PGP 深度合作,将流支付更广泛的应用于薪资支付领域

Zebec Protocol 与 PGP 深度合作&#xff0c;将流支付更广泛的应用于薪资支付领域 随着传统机构的入局&#xff0c;以及相关加密合规法规的落地&#xff0c;加密支付正在成为一种备受欢迎的全新支付方式。加密支付基于区块链底层&#xff0c;不受地域、时间等的限制&#xff0c…

网络安全 log4j漏洞复现

前言&#xff1a; log4j被爆出“史诗级”漏洞。其危害非常大&#xff0c;影响非常广。该漏洞非常容易利用&#xff0c;可以执行任意代码。这个漏洞的影响可谓是重量级的。 漏洞描述&#xff1a; 由于Apache Log4j存在递归解析功能&#xff0c;未取得身份认证的用户&#xff…

Gof23设计模式之简单工厂/静态工厂模式

在java中&#xff0c;万物皆对象&#xff0c;这些对象都需要创建&#xff0c;如果创建的时候直接new该对象&#xff0c;就会对该对象耦合严重&#xff0c;假如我们要更换对象&#xff0c;所有new对象的地方都需要修改一遍&#xff0c;这显然违背了软件设计的开闭原则。如果我们…

工地临边防护缺失识别检测算法 opencv

工地临边防护缺失识别检测系统通过opencvpython网络模型技术&#xff0c;工地临边防护缺失识别检测算法检测到没有按照要求放置临边防护设备时&#xff0c;将自动发出警报提示现场管理人员及时采取措施。Python是一门解释性脚本语言&#xff0c;是在运行的时候将程序翻译成机器…

PaddleOCR #使用PaddleOCR进行光学字符识别 - OCR飞桨实验

引言&#xff1a; PaddleOCR 是一个 OCR 框架或工具包&#xff0c;它提供多语言实用的 OCR 工具&#xff0c;帮助用户在几行代码中应用和训练不同的模型。PaddleOCR 提供了一系列高质量的预训练模型。这包含三种类型的模型&#xff0c;使 OCR 高度准确并接近商业产品。它提供文…

SPI(Serial Perripheral Interface)

SPI全称是Serial Perripheral Interface&#xff0c;也就是串行外围设备接口。SPI是Motorola公司推出的一种同步串行接口技术&#xff0c;是一种高速、全双工的同步通信总线&#xff0c;SPI时钟频率相比I2C要高很多&#xff0c;最高可以工作在上百MHz。SPI以主从方式工作&#…

Fiddler抓包工具之Fiddler+willow插件应用

安装 Fiddler的安装包地址&#xff1a;fillderwillow 解压后安装fiddler4和willow1.4.*版本。 安装成功后&#xff0c;启动fiddler后会出现willow插件按钮&#xff1a; 说明安装成功。 重定向 willow重定向 进入willow界面后&#xff0c;通过右键->Add Project ->Add R…

Compose 嵌套滑动冲突的解决办法

前言 在最近我利用业余时间使用 Compose 写的 Gihub APP 中&#xff0c;它的首页结构是这样的&#xff1a; 采用了 Drawer 嵌套 Pager 的结构。 这就会出现一个问题&#xff0c;那就是 Drawer 和 Pager 都需要监听横向滑动手势&#xff0c;从而实现展开 Drawer 和 切换 Pager…

C#TryCatch用法

前几天一个学员在学习C#与TryCatch用法时,也不知道TryCatch用法装可以用来做什么 。下面我们就详细讲讲C# 和封TryCatch用法相关知识。 C# 是一种通用、类型安全且面向对象的编程语言&#xff0c;由微软开发并在 .NET 平台上运行。TryCatch 是 C# 语言中的一个结构&#xff0c…

PYTHON在数据保存csv时文件内容乱码了怎么解决?

目录标题 前言问题1. 在打开 CSV 文件时指定编码方式2. 将数据转换成 Unicode 编码尾语 前言 嗨喽&#xff0c;大家好呀~这里是爱看美女的茜茜呐 问题 如果你的 Python 程序采集到的数据在保存成 CSV 格式的文件时出现了乱码。 那么可尝试以下解决方法&#xff1a; 1. 在打…

MySQL数据库——索引、事务、存储引擎

MySQL 索引 索引的概念 索引是一个排序的列表&#xff0c;在这个列表中存储着索引的值和包含这个值的数据所在行的物理地址&#xff08;类似于C语言的链表通过指针指向数据记录的内存地址&#xff09;。使用索引后可以不用扫描全表来定位某行的数据&#xff0c;而是先通过索引…