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

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。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

自定义@AnonymousAccess注解

一.目的&#xff1a; 自定义AnonymousAccess注解&#xff0c;可以直接在controller上添加该注解使请求绕过权限验证进行匿名访问&#xff0c;便于快速调用调试以及部分不需要进行安全验证的接口。而不是每次都需要去SecurityConfig文件中进行修改。 二.流程&#xff1a; 三.实…

阿里MotionShop——AI视频工具:一键替换视频人物为3D虚拟角色~

近期AI相关的新奇应用层出不穷&#xff0c;今天小元老师要给大家安利一个由阿里巴巴研发的AI视频生成技术——MotionShop&#xff01; 1、一键替换3D虚拟角色 MotionShop通过视频处理、角色检测、背景修复等多重步骤&#xff0c;能够将视频中的人物角色&#xff0c;一键转换成…

Pytorch(笔记7损失函数类型)

前言 损失函数&#xff08;Loss Function&#xff09;&#xff1a;是定义在单个样本上的&#xff0c;是指一个样本的误差&#xff0c;度量模型一次预测的好坏。 代价函数&#xff08;Cost Function&#xff09;成本函数经验风险&#xff1a;是定义在整个训练集上的&#xff0c…

文件防止拷贝如何实现?这些攻略给你了

在信息爆炸的时代&#xff0c;数据安全成为企业和个人不可忽视的重要环节。文件的非法拷贝不仅可能侵犯知识产权&#xff0c;还可能导致敏感信息的泄露&#xff0c;进而引发严重的后果。 因此&#xff0c;了解并掌握文件防止拷贝的方法和技术至关重要。本文将详细介绍几种常见…

压测jmeter 插件 之 tps和响应时间图

1. 背景 进行压测ing 2. 需要插件 TPS 和 响应时间 3. 插件 在 选项-最下面-plugins Manager 在 Available Plugins 中 搜索 &#xff1a;jpgc - Standard Set 重启安装就好啦

12--RabbitMQ消息队列

前言&#xff1a;前面一章内容太多&#xff0c;写了kafka&#xff0c;这里就写一下同类产品rabbitmq&#xff0c;rabbitmq内容较少&#xff0c;正好用来过度一下&#xff0c;概念还是会用一些例子来说明&#xff0c;实际部署的内容会放在概念之后。 1、基础概念 1.1、MQ消息队…

年化15.73%:创业板指数布林带突破Backtrader策略(代码+数据)

原创文章第582篇&#xff0c;专注“AI量化投资、世界运行的规律、个人成长与财富自由"。 昨天咱们使用backtrader&#xff0c;重写了创业板动量趋势择时&#xff1a;年化19.2%&#xff1a;backtraderquantstats实现创业板动量择时(代码数据) 今天咱们换一个通道指标&#…

计算机视觉研究方向初学习,计算机视觉都有什么方向??!到底是干什么的?!

计算机视觉研究方向初学习&#xff0c;计算机视觉都有什么方向&#xff1f;&#xff1f;&#xff01;到底是干什么的&#xff1f;&#xff01; 语义分割图像分类目标检测和定位实例分割、全景分割物体跟踪姿态估计人脸识别人体识别图像增强风格迁移图像生成视觉问答视频分析光学…

C++视觉开发 七.模板匹配

模板匹配是一种基于图像处理的技术&#xff0c;用于在目标图像中寻找与给定模板图像最相似的部分。通过设定的模板&#xff0c;将目标图像与模板图像比较&#xff0c;计算其相似度&#xff0c;实现对目标图像的判断。 目录 一.手写数字识别 重要函数&#xff1a; 1.cv::glob…

【已解决】腾讯云安装了redis,但是本地访问不到,连接不上

汇总了我踩过的所有问题。 查看配置文件redis.conf 1、把bind 127.0.0.1给注释掉&#xff08;前面加个#就是&#xff09;或者改成bind 0.0.0.0&#xff0c;因为刚下载时它是默认只让本地访问。&#xff08;linux查找文档里的内容可以输入/后面加需要匹配的内容&#xff0c;然后…

FAO(脂肪酸β-氧化,Fatty acid beta-oxidation)应用实例

一、FAOBlue及其香豆素衍生物的吸收光谱和荧光光谱 在PBS缓冲液&#xff08;pH 7.4&#xff09;中&#xff0c;FAO代谢后释放的FAOBlue和香豆素衍生物的吸收光谱&#xff08;左&#xff09;、荧光光谱&#xff08;右&#xff09;。 FAOBlue经过FAO转化为香豆素衍生物后&#…

同步时钟系统支持多种校时方式

在当今数字化、信息化高速发展的时代&#xff0c;时间的准确性和同步性变得至关重要。无论是金融交易、通信网络、交通运输&#xff0c;还是工业生产、科学研究等领域&#xff0c;都离不开一个精确且同步的时钟系统。而同步时钟系统之所以能够在众多领域发挥关键作用&#xff0…

使用Python绘制箱线图并分析数据

使用Python绘制箱线图并分析数据 在这篇博客中&#xff0c;我们将探讨如何使用Python中的pandas库和matplotlib库来绘制箱线图&#xff0c;并分析数据文件中的内容。箱线图是一种常用的图表类型&#xff0c;用于展示数据的分布情况及其统计特性&#xff0c;如中位数、四分位数…

程序员日志之DNF手游强化20攻略

目录 传送门正文日志1、概要2、炭的获取3、强化 传送门 SpringMVC的源码解析&#xff08;精品&#xff09; Spring6的源码解析&#xff08;精品&#xff09; SpringBoot3框架&#xff08;精品&#xff09; MyBatis框架&#xff08;精品&#xff09; MyBatis-Plus SpringDataJP…

全能型CAE/CFD建模工具SimLab 详解Part1: Geomtry,轻松集成力学、电磁学、疲劳优化等功能

SimLab的建模功能 SimLab集成了结构力学&#xff0c;流体力学&#xff0c;电磁学&#xff0c;疲劳和优化等功能&#xff0c;是全能型的CAE / CFD建模工具。 具有强大的几何、网格编辑功能&#xff0c;能够快速的清理复杂模型&#xff0c;减少手动修复的工作量&#xff0c;提高…

websocket推送消息,模拟推送

上一篇文章&#xff1a;什么是webSocket&#xff1f;以及它的一些相关理论知识 背景&#xff1a; MQTT 的发布/订阅模式与 WebSocket 的双向通信特性相结合。 通过将 MQTT 与 WebSocket 结合使用&#xff0c;可以在 Web 应用中实现高效、实时的消息传输&#xff0c;特别适用于…

C# 下sendmessage和postmessage的区别详解与示例

文章目录 1、SendMessage2、PostMessage3、两者的区别&#xff1a; 总结 在C#中&#xff0c;SendMessage和PostMessage是两个用于Windows编程的API&#xff0c;它们用于向窗口发送消息。这两个方法都位于System.Windows.Forms命名空间中&#xff0c;通常用于自动化Windows应用程…

AI应用观:从“卷模型”到“卷应用”的时代跨越

在2024年世界人工智能大会的舞台上&#xff0c;百度创始人李彦宏的发言如同一股清流&#xff0c;为当前如火如荼的人工智能领域注入了深刻的思考。他提出的“大家不要卷模型&#xff0c;要卷应用”的观点&#xff0c;不仅是对当前AI技术发展趋势的精准洞察&#xff0c;更是对未…

帮企建站包响应式建站源码系统 带完整的安装代码包以及搭建部署教程

系统概述 帮企建站包响应式建站源码系统是一款为企业和个人提供便捷、高效建站解决方案的工具。它融合了先进的技术和设计理念&#xff0c;旨在帮助用户轻松构建具有专业水准的网站&#xff0c;无论在桌面端还是移动端都能呈现出完美的展示效果。 该系统基于响应式设计原则&a…

C++ 信号量和锁的区别

网上关于信号量和锁的区别&#xff0c;写的比较官方晦涩难懂&#xff0c;对于这个知识点吸收难&#xff0c;通过示例&#xff0c;我们看到信号量&#xff0c;可以控制同一时刻的线程数量&#xff0c;就算同时开启很多线程&#xff0c;依然可以的达到线程数可控 #include <i…