SpringSecurity-SpirngBoot-方法级授权(SpringSecurity6.3新特性)(四)

news2024/9/22 9:43:00

SpringSecurity-SpirngBoot-方法级授权(SpringSecurity6.3新特性)(四)

本章使用SpringSecurity6.3新特性实现数据级别的鉴权,主要的目的是实现不同权限的用户查询同一个方法,限制一些内容只能拥有特定权限的用户才能看到,其他没有该权限的用户显示为空。

在上一节的基础上,新建spring-security-authorization-data分支。

  1. 修改SecurityConfiguration类,添加rob用户,权限为"message:read", “user:read”;新建luke用户,权限为"message:read"

    @Bean
    CustomUserRepository customUserRepository() {
        String password = new BCryptPasswordEncoder().encode("password");
    
        CustomUser customUser1 = new CustomUser(1L, "rob", password, "message:read", "user:read");
        CustomUser customUser2 = new CustomUser(2L, "luke", password, "message:read");
        Map<String, CustomUser> emailToCustomUser = new HashMap<>();
        emailToCustomUser.put(customUser1.getEmail(), customUser1);
        emailToCustomUser.put(customUser2.getEmail(), customUser2);
        return new MapCustomUserRepository(emailToCustomUser);
    }
    

    修改CustomUser、CustomUserRepositoryUserDetailsService类,以适配修改后的SecurityConfiguration:

    public class CustomUser {
        private final long id;
    
        private final String email;
    
        @JsonIgnore
        private final String password;
    	
        // 用户权限
        private final String[] authoritie;
    
        @JsonCreator
        public CustomUser(long id, String email, String password, String ...authoritie) {
            this.id = id;
            this.email = email;
            this.password = password;
            this.authoritie = authoritie;
        }
        public long getId() {
            return this.id;
        }
    
        public String getEmail() {
            return this.email;
        }
    
        public String getPassword() {
            return this.password;
        }
    
        public String[] getAuthoritie() {
            return authoritie;
        }
    
        @Override
        public String toString() {
            return email;
        }
    
        @Override
        public int hashCode() {
            return email.hashCode();
        }
    
        @Override
        public boolean equals(Object obj) {
            return this.toString().equals(obj.toString());
        }
    }
    
    @Service
    public class CustomUserRepositoryUserDetailsService implements UserDetailsService {
        private final CustomUserRepository userRepository;
    
    
        public CustomUserRepositoryUserDetailsService(CustomUserRepository userRepository) {
            this.userRepository = userRepository;
        }
        @Override
        public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
            // 查询用户名对应的用户
            CustomUser customUser = this.userRepository.findCustomUserByEmail(username);
            if (customUser == null) {
                // 用户不存在 抛出异常
                throw new UsernameNotFoundException("username " + username + " is not found");
            }
            return new CustomUserDetails(customUser);
        }
    
        static final class CustomUserDetails extends CustomUser implements UserDetails {
    
            private final List<GrantedAuthority> ROLE_USER;
    
            CustomUserDetails(CustomUser customUser) {
                super(customUser.getId(), customUser.getEmail(), customUser.getPassword(), customUser.getAuthoritie());
                ROLE_USER = Collections
                        .unmodifiableList(AuthorityUtils.createAuthorityList(customUser.getAuthoritie()));
            }
    
            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return ROLE_USER;
            }
    
            @Override
            public String getUsername() {
                return getEmail();
            }
    
            @Override
            public boolean isAccountNonExpired() {
                return true;
            }
    
            @Override
            public boolean isAccountNonLocked() {
                return true;
            }
    
            @Override
            public boolean isCredentialsNonExpired() {
                return true;
            }
    
            @Override
            public boolean isEnabled() {
                return true;
            }
    
        }
    }
    
  2. 因为使用的是SpringSecurity6.3,把pom文件的SpringBoot版本修改为3.3.1以使用SpringSecurity6.3。导入h2和spring-boot-starter-data-jpa包实现简单的数据库查询

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    	<modelVersion>4.0.0</modelVersion>
    	<parent>
    		<groupId>org.springframework.boot</groupId>
    		<artifactId>spring-boot-starter-parent</artifactId>
    		<version>3.3.1</version>
    		<relativePath/> <!-- lookup parent from repository -->
    	</parent>
    	<groupId>com.jackmouse</groupId>
    	<artifactId>jackmouse-spring-boot-security-hello</artifactId>
    	<version>0.0.1-SNAPSHOT</version>
    	<name>jackmouse-spring-boot-security-hello</name>
    	<description>jackmouse-spring-boot-security-hello</description>
    	<url/>
    	<licenses>
    		<license/>
    	</licenses>
    	<developers>
    		<developer/>
    	</developers>
    	<scm>
    		<connection/>
    		<developerConnection/>
    		<tag/>
    		<url/>
    	</scm>
    	<properties>
    		<java.version>17</java.version>
    	</properties>
    	<dependencies>
    		<!--security依赖-->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-security</artifactId>
    		</dependency>
    
    		<dependency>
    			<groupId>org.springframework.security</groupId>
    			<artifactId>spring-security-data</artifactId>
    		</dependency>
    		<!--spring-boot Web支持-->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-web</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-data-jpa</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>com.h2database</groupId>
    			<artifactId>h2</artifactId>
    		</dependency>
    		<!--thymeleaf模板引擎-->
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-thymeleaf</artifactId>
    		</dependency>
    		<dependency>
    			<groupId>org.springframework.boot</groupId>
    			<artifactId>spring-boot-starter-test</artifactId>
    			<scope>test</scope>
    		</dependency>
    		<!--security测试模块-->
    		<dependency>
    			<groupId>org.springframework.security</groupId>
    			<artifactId>spring-security-test</artifactId>
    			<scope>test</scope>
    		</dependency>
    		<!-- Selenium Web驱动 -->
    		<dependency>
    			<groupId>org.seleniumhq.selenium</groupId>
    			<artifactId>htmlunit-driver</artifactId>
    		</dependency>
    
    
    	</dependencies>
    
    	<build>
    		<plugins>
    			<plugin>
    				<groupId>org.springframework.boot</groupId>
    				<artifactId>spring-boot-maven-plugin</artifactId>
    			</plugin>
    		</plugins>
    	</build>
    
    </project>
    
  3. 创建MessageController类和MessageRepository接口,实现对message的查询

    @RestController
    public class MessageController {
        private final MessageRepository messages;
    
        public MessageController(MessageRepository messages) {
            this.messages = messages;
        }
    
        @GetMapping("/message")
        List<Message> getMessages() {
            List<Message> all = this.messages.findAll();
            return all;
        }
    
        @GetMapping("/message/{id}")
        Optional<Message> getMessages(@PathVariable Long id) {
            return this.messages.findById(id);
        }
    }
    
    @Repository
    @AuthorizeReturnObject
    public interface MessageRepository extends CrudRepository<Message, Long> {
        @Query("select m from Message m where m.to.id = ?#{ authentication.name }")
        List<Message> findAll();
    }
    
  4. 实体类创建

    package com.jackmouse.security.entity;
    
    import com.fasterxml.jackson.databind.annotation.JsonSerialize;
    import com.jackmouse.security.annotation.AuthorizeRead;
    import jakarta.persistence.*;
    import org.springframework.security.authorization.method.AuthorizeReturnObject;
    
    import java.time.Instant;
    
    @Entity
    @JsonSerialize(as = Message.class)
    @AuthorizeReturnObject
    public class Message {
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
        private String text;
    
        private String summary;
        private Instant created = Instant.now();
    
        @ManyToOne
        private User to;
    
        public User getTo() {
            return this.to;
        }
    
        public void setTo(User to) {
            this.to = to;
        }
    
        public Long getId() {
            return this.id;
        }
    
        public void setId(Long id) {
            this.id = id;
        }
    
        public Instant getCreated() {
            return this.created;
        }
    
        public void setCreated(Instant created) {
            this.created = created;
        }
    
        @AuthorizeRead("message")
        public String getText() {
            return this.text;
        }
    
        public void setText(String text) {
            this.text = text;
        }
    
        @AuthorizeRead("message")
        public String getSummary() {
            return this.summary;
        }
    
        public void setSummary(String summary) {
            this.summary = summary;
        }
        
    }
    
    @Entity(name = "users")
    @JsonSerialize(as = User.class, contentUsing = JsonSerializer.class)
    public class User {
        @Id
        private String id;
    
        private String firstName;
    
        private String lastName;
    
        private String email;
    
        private String password;
    
        public String getId() {
            return this.id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        @AuthorizeRead("user")
        public String getFirstName() {
            return this.firstName;
        }
    
        public void setFirstName(String firstName) {
            this.firstName = firstName;
        }
    
        @AuthorizeRead("user")
        public String getLastName() {
            return this.lastName;
        }
    
        public void setLastName(String lastName) {
            this.lastName = lastName;
        }
    
        public String getEmail() {
            return this.email;
        }
    
        public void setEmail(String email) {
            this.email = email;
        }
    
        public String getPassword() {
            return this.password;
        }
    
        public void setPassword(String password) {
            this.password = password;
        }
    }
    

    这里的@AuthorizeRead注解后面会介绍到

  5. 创建AuthorizeRead注解,这里使用到SpringSecurity官方文档介绍到的模版注解

    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    @PreAuthorize("hasAuthority('{value}:read')")
    @HandleAuthorizationDenied(handlerClass = Null.class)
    public @interface AuthorizeRead {
        String value();
    }
    

    @AuthorizeRead(“user”)对应的注解是@PreAuthorize(“hasAuthority(‘user:read’)”)

    @AuthorizeRead(“message”)对应的注解是@PreAuthorize(“hasAuthority(‘message:read’)”)

    意味着只有拥有user:read权限,才能访问@AuthorizeRead(“user”)标记的方法,只有拥有message:read才能访问@AuthorizeRead(“message”)标记的方法。

    根据官方文档的介绍,使用模版注解还必须向Spring容器中注册一个PrePostTemplateDefaults bean,以解析我们设置的模版变量

    在SecurityConfiguration类中添加:

    @Bean
    @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
    static PrePostTemplateDefaults templateDefaults() {
        return new PrePostTemplateDefaults();
    }
    

    @AuthorizeReturnObject注解在类上,表示这个类所有的字段都需要进行鉴权。这意味着 Spring Security 将尝试代理任何返回对象,包括 String、 Integer 和其他类型。

    如果您希望对方法返回值类型(如 int、 String、 Double 或这些类型的集合)的类或接口使用@AuthorizeReturnObject,那么您还应该发布适当的 AuthorizationAdvisorProxyFactory。

    在SecurityConfiguration类中添加:

    @Bean
    static Customizer<AuthorizationAdvisorProxyFactory> skipValueTypes() {
        return (factory) -> factory.setTargetVisitor(AuthorizationAdvisorProxyFactory.TargetVisitor.defaultsSkipValueTypes());
    }
    
  6. 创建Null类实现MethodAuthorizationDeniedHandler接口。由于SpringSecurity在鉴权失败会抛出异常,我们只是希望没有权限的值不被用户看到,而不是程序报错,所以创建Null类在鉴权失败后返回null值。

    @Component
    public class Null implements MethodAuthorizationDeniedHandler {
        @Override
        public Object handleDeniedInvocation(MethodInvocation methodInvocation, AuthorizationResult authorizationResult) {
            return null;
        }
    }
    

    在AuthorizeRead注解上有一个注解@HandleAuthorizationDenied(handlerClass = Null.class),表示在鉴权失败时,使用Null处理。

  7. 数据源准备,在resources目录下创建import.sql,内容如下:

    insert into users (id,email,password,first_name,last_name) values ('rob','rob@example.com','password','Rob','Winch');
    insert into users (id,email,password,first_name,last_name) values ('luke','luke@example.com','password','Luke','Taylor');
    
    insert into message (id,created,to_id,summary,text) values (100,'2014-07-10 10:00:00','rob','Hello Rob','This message is for Rob');
    insert into message (id,created,to_id,summary,text) values (101,'2014-07-10 14:00:00','rob','How are you Rob?','This message is for Rob');
    insert into message (id,created,to_id,summary,text) values (102,'2014-07-11 22:00:00','rob','Is this secure?','This message is for Rob');
    
    insert into message (id,created,to_id,summary,text) values (110,'2014-07-12 10:00:00','luke','Hello Luke','This message is for Luke');
    insert into message (id,created,to_id,summary,text) values (111,'2014-07-12 10:00:00','luke','Greetings Luke','This message is for Luke');
    insert into message (id,created,to_id,summary,text) values (112,'2014-07-12 10:00:00','luke','Is this secure?','This message is for Luke');
    

    由于添加了h2依赖,SpringBoot会在启动时创建一个内存数据库,并插入以上数据。到这里数据级的鉴权就开发完了。

浏览器测试

登录rob用户:访问/message接口

764d14acf14b738a37e57474cbf8846a.png

由于rob有"message:read", "user:read"权限,所有可以看到message对象的text和summary内容、 user对象的firstName和lastName。

登录luke用户:访问/message接口

1afec724ac02d2d72ac2d2b844fe5f51.png

因为luke只有"message:read"权限,所以看不到user对象的firstName和lastName。

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

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

相关文章

StarRocks分布式元数据源码解析

1. 支持元数据表 https://github.com/StarRocks/starrocks/pull/44276/files 核心类&#xff1a;LogicalIcebergMetadataTable&#xff0c;Iceberg元数据表&#xff0c;将元数据的各个字段做成表的列&#xff0c;后期可以通过sql操作从元数据获取字段&#xff0c;这个表的组成…

Linux--线程的控制

目录 0.前言 1.pthread库 2.关于控制线程的接口 2.1.创建线程&#xff08;pthread_create&#xff09; 2.2.线程等待&#xff08;pthread_join&#xff09; 代码示例1&#xff1a; ​编辑 ***一些问题*** 2. 3.创建多线程 3.线程的终止 &#xff08;pthread_exit /…

python—读写csv文件

目录 csv库方法参数 读取数据 csv.reader方法 文件指定行或列数据读取操作 txt文件的readlines、read方法 csv.DictReader方法 写入数据 txt文件的write&#xff0c;writelines csv.writer方法 csv.DictWriter方法 读写联合(修改及插入数据) 读写csv 文件时&#xf…

语义言语流畅性的功能连接和有效连接

摘要 语义言语流畅性(SVF)受损在多种神经系统疾病中都存在。虽然已经报道了SVF相关区域的激活情况&#xff0c;但这些区域如何相互连接以及它们在脑网络中的功能作用仍存在分歧。本研究使用功能磁共振成像评估了健康被试SVF静态和动态功能连接(FC)以及有效连接。观察到额下回(…

c++初阶学习----入门(上)

大家好啊。最近学习了一点关于c的知识。这不就迫不及待的来与大家分享了嘛。但我这也是现学现卖所以咧。有很多遗落甚至不对的地方希望大家可以在评论区里面指出来。这样也可以增加大家对知识的巩固。 c语言与c的联系 不知道大家看到c会不会不由自主的联想到C语言啊。毕竟都是…

TVBox的Json配置接口编写指南,模板格式说明(如何打造一个专属于自己的TVBox配置文件)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 配置解析 📒📝 JSON基础📝 配置文件结构/参数说明📝 编写步骤📝 注意事项🎈 接口分享⚓️ 相关链接 ⚓️📖 介绍 📖 TVBox 是一款备受欢迎的电视盒子应用(免费影视必备),它以其高度自定义的特性深受用户喜爱…

Pearson 相关系数的可视化辅助判断和怎么用

Pearson 相关系数的可视化辅助判断和怎么用 flyfish Pearson 相关系数 是一种用于衡量两个连续型变量之间线性相关程度的统计量。其定义为两个变量协方差与标准差的乘积的比值。公式如下&#xff1a; r ∑ ( x i − x ˉ ) ( y i − y ˉ ) ∑ ( x i − x ˉ ) 2 ∑ ( y i −…

jitsi 使用JWT验证用户身份

前言 Jitsi Meet是一个很棒的会议系统,但是默认他运行所有人创建会议,这样在某种程度上,我们会觉得他不安全,下面我们就来介绍下使用JWT来验证用户身份 方案 卸载旧的lua依赖性sudo apt-get purge lua5.1 liblua5.1-0 liblua5.1-dev luarocks添加ubuntu的依赖源,有则不需…

AI时代算法面试:揭秘高频算法问题与解答策略

三种决策树算法的特点和区别 ID3算法&#xff1a;基本的决策树算法&#xff0c;适用于简单的分类问题C4.5算法&#xff1a;改进了ID3算法&#xff0c;适用于更复杂的分类问题&#xff0c;可以处理连续型数据和缺失值CART算法&#xff1a;更加通用的决策树算法&#xff0c;适用于…

住宅代理、移动代理和数据中心代理之间的区别

如果您是一名认真的互联网用户&#xff0c;可能需要反复访问某个网站或服务器&#xff0c;可能是为了数据抓取、价格比较、SEO 监控等用例&#xff0c;而不会被 IP 列入黑名单或被 CAPTCHA 阻止。 代理的工作原理是将所有传出数据发送到代理服务器&#xff0c;然后代理服务器将…

用LangGraph、 Ollama,构建个人的 AI Agent

如果你还记得今年的 Google I/O大会&#xff0c;你肯定注意到了他们今年发布的 Astra&#xff0c;一个人工智能体&#xff08;AI Agent&#xff09;。事实上&#xff0c;目前最新的 GPT-4o 也是个 AI Agent。 现在各大科技公司正在投入巨额资金来创建人工智能体&#xff08;AI …

VBA实现Excel的数据透视表

前言 本节会介绍通过VBA的PivotCaches.Create方法实现Excel创建新的数据透视表、修改原有的数据透视表的数据源以及刷新数据透视表内容。 本节测试内容以下表信息为例 1、创建数据透视表 语法&#xff1a;PivotCaches.Create(SourceType, [SourceData], [Version]) 说明&am…

面对数据不一致性的解决方案:

polarDB是读写分离和计算存储分离的分布式数据库&#xff0c;并且副本的log replicate是基于Parallel-Raft协议来实现的。所以在瞬时进行写和读的操作时&#xff0c;是不可避免会存在数据一致性问题&#xff0c;导致这个数据一致性问题的原因不是事务&#xff0c;而是多副本日志…

【考研数学】李林《880题》25版听说大改版?和和24版差别大吗?

25版和24版总体差别不大&#xff0c;只有小部分内容有所变动&#xff01; 拓展题部分的更新&#xff1a;25版在拓展题部分进行了一些更新&#xff0c;从李林的模拟题中挑选了大约40道题目添加到新版中。 高等数学&#xff1a;变动主要集中在前三章&#xff0c;但具体的题目变…

【C++】开源:坐标转换和大地测量GeographicLib库配置使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍坐标转换和大地测量GeographicLib库配置使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关…

Facebook社交平台的未来发展趋势分析

随着科技和社交需求的不断演变&#xff0c;Facebook作为全球最大的社交平台之一&#xff0c;其未来发展的趋势备受关注。从技术创新到社会影响&#xff0c;Facebook正在经历着前所未有的变化和挑战。本文将探讨Facebook未来发展的几个关键趋势&#xff0c;并分析其可能的影响和…

SpringBoot 实现视频分段播放(通过进度条来加载视频)

需求&#xff1a;现在我本地电脑中有一个文件夹&#xff0c;文件夹中都是视频&#xff0c;需要实现视频播放的功能。 问题&#xff1a;如果通过类似 SpringBoot static 文件夹的方式来实现&#xff0c;客户端要下载好完整的视频之后才可以播放&#xff0c;并且服务端也会占用大…

Androidstudio开发,天气预报APP

1.项目功能思维导图 2. 项目涉及到的技术点 数据来源&#xff1a;和风天气API使用okhttp网络请求框架获取api数据使用gson库解析json数据使用RecyclerViewadapter实现未来7天列表展示和天气指数使用PopupMenu 实现弹出选项框使用动画定时器实现欢迎页倒计时和logo动画使用Text…

用Vue3和Plotly.js绘制交互式3D散点图

本文由ScriptEcho平台提供技术支持 项目地址&#xff1a;传送门 使用 Plotly.js 创建 2D 密度图 应用场景介绍 密度图是一种可视化数据分布的图表&#xff0c;它显示了数据点的密度在不同区域的变化情况。在许多科学和工程领域中&#xff0c;密度图被广泛用于探索和分析数据…

java项目总结数据库

1.什么是数据库 用于存储和管理数据的仓库 2.数据库的特点 1.持久化存储数据。确实数据库就是一个文件系统。 2.便于存储和管理数据 3.使用统一的方式操作数据库 --SQL 3.MqSql服务启动 4.登录和退出 这里的ip值IP地址 5.客户端与服务器关系 6.目录结构 7.SQL 1.什么是SQL&…