配套视频:61.授权之授权核心概念_哔哩哔哩_bilibili

  • 什么是权限管理?

  • 权限管理核心概念

  • Spring Security权限管理策略

  • 基于URL地址方式实现的权限管理

  • 基于方法实现的权限管理

  • 授权实战

1.权限管理

1.1 认证

   身份认证,就是判断一个用户是否为系统合法用户的处理过程。Spring Security中支持多种不同方式的认证,但是无论开发者使用哪种方式认证,都不会影响授权功能的使用,这是因为Spring Security很好地做到了认证和授权的解耦。

1.2 授权

   授权,即访问控制,控制谁可以访问哪些资源。简单的理解授权就是根据系统提前设置好的规则,给用户分配可以访问某一个资源的权限,用户根据自己所具有的权限,去执行相应的操作。

1.2.1 授权核心概念

在前面学习认证的过程中,我们知道认证成功之后会将当前登录的用户信息保存到Authentication对象中,Authentication对象中有一个getAuthorities()方法,用来返回当前登录用户所具备的权限信息,也就是当前用户所具有的权限信息。该方法的返回值为 Collection<? extends GrantedAuthority>,当用户需要进行权限判断时,就会根据集合返回权限信息调用相应的方法进行判断。

那么问题来了,对于GrantedAuthority这个返回值应该如何理解呢? 是角色还是权限?

我们认为授权可以是基于角色的权限管理,也可以是基于资源的权限管理 (RBAC:Role/Resource Base Access Control),从设计层面上来说,角色和权限是两个完全不同的东西:权限是一些具体的操作,而角色则是某些权限的集合,例如:READ_BOOK 和 ROLE_ADMIN 是完全不同的,因此返回值是什么取决于具体的业务设计情况:

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

  • 基于资源的权限设计就是: 用户<=>权限<=>资源 三者关系 返回就是用户的权限  --- 权限字符串

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

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

2. 权限管理策略

配套视频:62.授权之权限管理两种策略_哔哩哔哩_bilibili

Spring Security中提供的权限管理策略主要有两种类型(可以访问系统中哪些资源:http url、method):

  • 基于过滤器(URL)的权限管理 (Filter Security Interceptor):请求未到达方法之前就进行校验

    • 主要是用来拦截HTTP请求,拦截下来之后,根据HTTP请求地址进行权限校验。

  • 基于AOP(方法)的权限管理 (Method Security Interceptor):请求已经到达方法之后,在进行方法调用的过程中通过切面的方式进行权限管理

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

2.1 基于URL权限管理

配套视频:63.授权之URL 权限管理策略_哔哩哔哩_bilibili

2.1.1 新建项目

创建spring boot项目:spring-security-authorize,spring boot版本建议选择2.7以下,导入Spring Web、Spring Security依赖。

2.1.2 开发controller:DemoController

 package com.study.controller;​import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;​/*** @ClassName DemoController* @Description TODO* @Author Jiangnan Cui* @Date 2023/2/26 9:56* @Version 1.0*/@RestControllerpublic class DemoController {@GetMapping("/admin") // ROLE_ADMINpublic String admin() {return "admin ok";}​@GetMapping("/user") // ROLE_USERpublic String user() {return "user ok";}​@GetMapping("/getInfo") // READ_INFOpublic String getInfo() {return "info ok";}}

2.1.3 配置授权config:SecurityConfig

 package com.study.config;​import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.core.userdetails.User;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.provisioning.InMemoryUserDetailsManager;​/*** @ClassName SecurityConfig* @Description Security配置类* @Author Jiangnan Cui* @Date 2023/2/26 9:57* @Version 1.0*/@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {// 创建内存数据源@Beanpublic UserDetailsService userDetailsService() {InMemoryUserDetailsManager inMemoryUserDetailsManager = new InMemoryUserDetailsManager();inMemoryUserDetailsManager.createUser(User.withUsername("root").password("{noop}123").roles("ADMIN", "USER").build());inMemoryUserDetailsManager.createUser(User.withUsername("cjn").password("{noop}123").roles("USER").build());inMemoryUserDetailsManager.createUser(User.withUsername("xiaocui").password("{noop}123").authorities("READ_INFO").build());return inMemoryUserDetailsManager;}​// 配置到AuthenticationManager中@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userDetailsService());}​// 基本配置@Overrideprotected void configure(HttpSecurity http) throws Exception {//        http.authorizeRequests()//                .anyRequest().authenticated()//                .and().formLogin()//                .and().csrf().disable();​http.authorizeRequests().mvcMatchers("/admin").hasRole("ADMIN") // 匹配路径,具有ADMIN角色,角色前自动加ROLE_.mvcMatchers("/user").hasRole("USER")   // 匹配路径,具有USER角色,角色前自动加ROLE_.mvcMatchers("/getInfo").hasAuthority("READ_INFO")  // 匹配路径,具有READ_INFO权限.anyRequest().authenticated().and().formLogin().and().csrf().disable();}}

2.1.4 启动项目测试

分别以用户root-123、cjn-123、xiaocui-123身份登录,访问:http://localhost:8080/admin、http://localhost:8080/user、http://localhost:8080/getInfo,结果如下:

(1)root-123:可访问admin、user接口,不可访问getInfo接口

(2)cjn-123:可访问user接口,不可访问admin、getInfo接口

(3)xiaocui-123:可访问getInfo接口,不可访问admin、user接口

对比发现,测试结果与代码逻辑一致。

2.1.5 权限表达式

方法 说明
hasAuthority(String authority) 当前用户是否具备指定权限
hasAnyAuthority(String... authorities) 当前用户是否具备指定权限中任意一个
hasRole(String role) 当前用户是否具备指定角色
hasAnyRole(String... roles); 当前用户是否具备指定角色中任意一个
permitAll(); 放行所有请求/调用
denyAll(); 拒绝所有请求/调用
isAnonymous(); 当前用户是否是一个匿名用户
isAuthenticated(); 当前用户是否已经认证成功
isRememberMe(); 当前用户是否通过 Remember-Me 自动登录
isFullyAuthenticated(); 当前用户是否既不是匿名用户又不是通过 Remember-Me 自动登录的
hasPermission(Object targetId, Object permission); 当前用户是否具备指定目标的指定权限信息
hasPermission(Object targetId, String targetType, Object permission); 当前用户是否具备指定目标的指定权限信息

2.1.6 antMatchers、mvcMathers与regexMatchers的区别

配套视频:64.antMatchers、mvcMatchers、regexMatchers_哔哩哔哩_bilibili

  • 先有的antMatchers;

  • Spring 4版本之后新增的mvcMatchers:特点是强大、通用,先按照mvcMathers匹配规则进行匹配,匹配不到的再按照antMatchers匹配方式进行匹配;

  • regexMatchers:正则matchers,与上面两种方式相比,好处是支持正则表达式。

2.2 基于方法的权限管理

配套视频:65.授权之方法权限管理策略说明_哔哩哔哩_bilibili

基于方法的权限管理主要是通过A0P来实现的,Spring Security中通过Method Security Interceptor来提供相关的实现。不同在于,Filter Security Interceptor只是在请求之前进行前置处理,Method Security Interceptor除了前置处理之外,还可以进行后置处理。前置处理就是在请求之前判断是否具备相应的权限,后置处理则是对方法的执行结果进行二次过滤。前置处理和后置处理分别对应了不同的实现类。

2.2.1 @EnableGlobalMethodSecurity

EnableGlobalMethodSecurity注解是用来开启全局权限的注解,用法如下:

 @Configuration@EnableGlobalMethodSecurity(prePostEnabled=true,securedEnabled=true, jsr250Enabled=true) // 此处一般只需要设置prePostEnabled=true就可以了,即仅开启prePostEnabled,其余两个注解用的很少public class SecurityConfig extends WebsecurityConfigurerAdapter{}
  • perPostEnabled: 开启 Spring Security提供的四个权限注解,@PostAuthorize、@PostFilter、@PreAuthorize 以及@PreFilter,这些原生注解支持权限表达式;

  • securedEnabled: 开启 Spring Security提供的@Secured 注解支持,仅支持角色的校验,该注解不支持权限表达式;

  • jsr250Enabled: 开启 JSR-250提供的注解,主要是@DenyAll、@PermitAll、@RolesAll,仅支持角色的校验,同样这些注解也不支持权限表达式。

 # 以上注解含义如下:- @PostAuthorize: 在目标方法执行之后进行权限校验。- @PostFiter: 在目标方法执行之后对方法的返回结果进行过滤。- @PreAuthorize:在目标方法执行之前进行权限校验。(常用)- @PreFiter:在目标方法执行之前对方法参数进行过滤。- @Secured:访问目标方法必须具备相应的角色。- @DenyAll:拒绝所有访问。- @PermitAll:允许所有访问。- @RolesAllowed:访问目标方法必须具备相应的角色。

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

2.2.2 基本用法

配套视频:66.授权之 方法 权限管理策略使用_哔哩哔哩_bilibili

  • 在SecurityConfig类上添加EnableGlobalMethodSecurity注解,使全局权限注解作用生效

 package com.study.config;......@Configuration@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = true) // 开启全局权限注解public class SecurityConfig extends WebSecurityConfigurerAdapter {......}}
  • 编写测试相关注解的测试类AuthorizeMethodController:

 package com.study.controller;​import com.study.entity.User;import org.springframework.security.access.annotation.Secured;import org.springframework.security.access.prepost.PostAuthorize;import org.springframework.security.access.prepost.PostFilter;import org.springframework.security.access.prepost.PreAuthorize;import org.springframework.security.access.prepost.PreFilter;import org.springframework.web.bind.annotation.*;​import javax.annotation.security.DenyAll;import javax.annotation.security.PermitAll;import javax.annotation.security.RolesAllowed;import java.util.ArrayList;import java.util.List;​@RestController@RequestMapping("/hello")public class AuthorizeMethodController {/*** @PreAuthorize:既可以验证角色,又可以验证权限字符串 支持and、or等连接符*///    @PreAuthorize("hasRole('ADMIN')  and authentication.name =='root'") // 验证具有ADMIN角色,且用户名是root//    @PreAuthorize("hasRole('ADMIN')  and authentication.name =='cjn'") // 验证具有ADMIN角色,且用户名是cjn@PreAuthorize("hasAuthority('READ_INFO')") // 验证READ_INFO权限@GetMappingpublic String hello() {return "hello";}​@PreAuthorize("authentication.name==#name") // EL表达式@GetMapping("/name")public String hello(String name) {return "hello:" + name;}​@PreFilter(value = "filterObject.id % 2 != 0", filterTarget = "users") // filterTarget必须是数组、集合类型,filterObject为数组元素对象@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(id, "blr");}​@PostFilter("filterObject.id % 2 == 0")// 用来对方法返回值进行过滤@GetMapping("/lists")public List<User> getAll() {List<User> users = new ArrayList<>();for (int i = 0; i < 10; i++) {users.add(new User(i, "blr:" + i));}return users;}​/*** 以下这些注解用的比较少,了解即可:*/@Secured({"ROLE_USER"}) // 只能判断角色@GetMapping("/secured")public User getUserByUsername() {return new User(99, "secured");}​@Secured({"ROLE_ADMIN", "ROLE_USER"}) //具有其中一个即可@GetMapping("/username")public User getUserByUsername2(String username) {return new User(99, username);}​@PermitAll@GetMapping("/permitAll")public String permitAll() {return "PermitAll";}​@DenyAll@GetMapping("/denyAll")public String denyAll() {return "DenyAll";}​@RolesAllowed({"ROLE_ADMIN", "ROLE_USER"}) // 具有其中一个角色即可@GetMapping("/rolesAllowed")public String rolesAllowed() {return "RolesAllowed";}}
  • 上面涉及的User实体类:

 package com.study.entity;​public class User {private Integer id;private String name;​public User(Integer id, String name) {this.id = id;this.name = name;}​public User() {}​@Overridepublic StringtoString() {return "User{" +"id=" + id +", name='" + name + '\'' +'}';}​public Integer getId() {return id;}​public void setId(Integer id) {this.id = id;}​public String getName() {return name;}​public void setName(String name) {this.name = name;}}

2.2.3 启动项目测试

(1)@PreAuthorize("hasAuthority('READ_INFO')")

          @GetMapping

由于满足条件的用户只有xiaocui-123,所以以xiaocui-123进行登录,然后访问:http://localhost:8080/hello

以root-123、cjn-123登录时均报403错误:

(2)@PreAuthorize("authentication.name==#name")

        @GetMapping("/name")

此请求只要name为root、cjn、xiaocui即可:

a. 以root-123登录,访问:http://localhost:8080/hello/name?name=root

b. 以cjn-123登录,访问:http://localhost:8080/hello/name?name=cjn

c.以xiaocui-123登录,访问:http://localhost:8080/hello/name?name=xiaocui

(3)@PreFilter(value = "filterObject.id % 2 != 0", filterTarget = "users")           

          @PostMapping("/users")

由于此处是Post请求,为方便使用,采用Postman进行测试:

a. 先F12,以root-123进行登录,获取cookis:

b. 打开Postman:

填写路径:http://localhost:8080/hello/users

编写Body-raw-JSON参数体:

 [{"id":1,"name":"root"},{"id":2,"name":"cjn"},{"id":3,"name":"xiaocui"}]

复制上面的JSESSIONID到Cookies:

发请求:

       Postman没有得到响应内容,但IDEA后台输出:users = [User{id=1, name='root'}, User{id=3, name='xiaocui'}],过滤掉id为偶数的User对象。

(4)@PostAuthorize("returnObject.id==1") @GetMapping("/userId")

以任一用户登录,指定id=1,访问:http://localhost:8080/hello/userId?id=1

(5)@PostFilter("filterObject.id % 2 == 0")// 用来对方法返回值进行过滤         @GetMapping("/lists")

以任一用户登录,访问:http://localhost:8080/hello/lists,返回id为偶数的User对象

2.2.4 项目整体目录结构

2.3 原理分析(此处都是对源码讲解,需要耐心听!!!)

配套视频:67.授权之原理分析_哔哩哔哩_bilibili

  • ConfigAttribute:在 Spring Security 中,用户请求一个资源(通常是一个接口或者一个 Java 方法)需要的角色会被封装成一个 ConfigAttribute 对象,在 ConfigAttribute 中只有一个 getAttribute方法,该方法返回一个 String 字符串,就是角色的名称。一般来说,角色名称都带有一个 ROLE_ 前缀,投票器 AccessDecisionVoter 所做的事情,其实就是比较用户所具备的角色和请求某个资源所需的 ConfigAtuibute 之间的关系。

  • AccesDecisionVoter 和 AccessDecisionManager 都有众多的实现类,在AccessDecisionManager中会逐个遍历 AccessDecisionVoter,进而决定是否允许用户访问,因而 AccesDecisionVoter 和 AccessDecisionManager 两者的关系类似于 AuthenticationProvider 和 ProviderManager的关系。

(暂时还是没弄明白,下次还需要再细看,争取弄懂弄会)

2.4 实战

配套视频:68.授权实战之权限模型说明(一)_哔哩哔哩_bilibili

在前面的案例中,我们配置的URL拦截规则和请求URL所需要的权限都是通过代码来配置的,这样就比较死板,如果想要调整访问某一个URL所需要的权限,就需要修改代码。

动态管理权限规则就是我们将URL拦截规则和访问URL所需要的权限都保存在数据库中,这样,在不修改源代码的情况下,只需要修改数据库中的数据,就可以对权限进行调整。

库表关系:用户<--中间表--> 角色 <--中间表--> 菜单

2.4.1 库表设计

新建数据库security_authorize,创建相关表,sql语句如下:

 SET NAMES utf8mb4;SET FOREIGN_KEY_CHECKS = 0;-- ------------------------------ Table structure for menu-- ----------------------------DROP TABLE IF EXISTS `menu`;CREATE TABLE `menu` (`id` int(11) NOT NULL AUTO_INCREMENT,`pattern` varchar(128) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;​-- ------------------------------ Records of menu-- ----------------------------BEGIN;INSERT INTO `menu` VALUES (1, '/admin/**');INSERT INTO `menu` VALUES (2, '/user/**');INSERT INTO `menu` VALUES (3, '/guest/**');COMMIT;​-- ------------------------------ Table structure for menu_role-- ----------------------------DROP TABLE IF EXISTS `menu_role`;CREATE TABLE `menu_role` (`id` int(11) NOT NULL AUTO_INCREMENT,`mid` int(11) DEFAULT NULL,`rid` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `mid` (`mid`),KEY `rid` (`rid`),CONSTRAINT `menu_role_ibfk_1` FOREIGN KEY (`mid`) REFERENCES `menu` (`id`),CONSTRAINT `menu_role_ibfk_2` FOREIGN KEY (`rid`) REFERENCES `role` (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;​-- ------------------------------ Records of menu_role-- ----------------------------BEGIN;INSERT INTO `menu_role` VALUES (1, 1, 1);INSERT INTO `menu_role` VALUES (2, 2, 2);INSERT INTO `menu_role` VALUES (3, 3, 3);INSERT INTO `menu_role` VALUES (4, 3, 2);COMMIT;​-- ------------------------------ Table structure for role-- ----------------------------DROP TABLE IF EXISTS `role`;CREATE TABLE `role` (`id` int(11) NOT NULL AUTO_INCREMENT,`name` varchar(32) DEFAULT NULL,`nameZh` varchar(32) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;​-- ------------------------------ Records of role-- ----------------------------BEGIN;INSERT INTO `role` VALUES (1, 'ROLE_ADMIN', '系统管理员');INSERT INTO `role` VALUES (2, 'ROLE_USER', '普通用户');INSERT INTO `role` VALUES (3, 'ROLE_GUEST', '游客');COMMIT;​-- ------------------------------ Table structure for user-- ----------------------------DROP TABLE IF EXISTS `user`;CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,`username` varchar(32) DEFAULT NULL,`password` varchar(255) DEFAULT NULL,`enabled` tinyint(1) DEFAULT NULL,`locked` tinyint(1) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;​-- ------------------------------ Records of user-- ----------------------------BEGIN;INSERT INTO `user` VALUES (1, 'admin', '{noop}123', 1, 0);INSERT INTO `user` VALUES (2, 'user', '{noop}123', 1, 0);INSERT INTO `user` VALUES (3, 'blr', '{noop}123', 1, 0);COMMIT;​-- ------------------------------ Table structure for user_role-- ----------------------------DROP TABLE IF EXISTS `user_role`;CREATE TABLE `user_role` (`id` int(11) NOT NULL AUTO_INCREMENT,`uid` int(11) DEFAULT NULL,`rid` int(11) DEFAULT NULL,PRIMARY KEY (`id`),KEY `uid` (`uid`),KEY `rid` (`rid`),CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`),CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`rid`) REFERENCES `role` (`id`)) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;​-- ------------------------------ Records of user_role-- ----------------------------BEGIN;INSERT INTO `user_role` VALUES (1, 1, 1);INSERT INTO `user_role` VALUES (2, 1, 2);INSERT INTO `user_role` VALUES (3, 2, 2);INSERT INTO `user_role` VALUES (4, 3, 3);COMMIT;SET FOREIGN_KEY_CHECKS = 1;

创建好的数据库表如下:

2.4.2 项目实践(此节视频中会出现各种报错,注意及时作出对应修改)

配套视频:69.授权实战之动态权限实现(二)_哔哩哔哩_bilibili

  • 新建SpringBoot项目:spring-security-dynamic-authorize,spring boot选择2.7以下版本,勾选Spring Web、Spring Security依赖。

  • 引入其它相关依赖:

     <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.38</version></dependency><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.2.8</version></dependency><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>2.2.2</version></dependency>
  • 配置配置文件

     server.port=8080spring.datasource.type=com.alibaba.druid.pool.DruidDataSourcespring.datasource.driver-class-name=com.mysql.jdbc.Driverspring.datasource.url=jdbc:mysql://localhost:3306/security_authorize?characterEncoding=UTF-8&useSSL=falsespring.datasource.username=rootspring.datasource.password=rootmybatis.mapper-locations=classpath:com/study/mapper/*.xmlmybatis.type-aliases-package=com.study.entity
  • 创建实体类

(1)User

 package com.study.entity;​import org.springframework.security.core.GrantedAuthority;import org.springframework.security.core.authority.SimpleGrantedAuthority;import org.springframework.security.core.userdetails.UserDetails;​import java.util.Collection;import java.util.List;import java.util.stream.Collectors;​/*** @ClassName User* @Description TODO* @Author Jiangnan Cui* @Date 2023/2/26 22:44* @Version 1.0*/public class User implements UserDetails { // 注意此处实现了UserDetails,并重写了其中方法private Integer id;private String password;private String username;private boolean enabled;private boolean locked;private List<Role> roles;​@Overridepublic Collection<? extends GrantedAuthority> getAuthorities() {return roles.stream().map(r -> new SimpleGrantedAuthority(r.getName())).collect(Collectors.toList());}​@Overridepublic String getPassword() {return password;}​public void setPassword(String password) {this.password = password;}​@Overridepublic String getUsername() {return username;}​public void setUsername(String username) {this.username = username;}​@Overridepublic boolean isAccountNonExpired() {return true;}​@Overridepublic boolean isAccountNonLocked() {return !locked;}​@Overridepublic boolean isCredentialsNonExpired() {return true;}​@Overridepublic boolean isEnabled() {return enabled;}​public void setEnabled(boolean enabled) {this.enabled = enabled;}​public void setLocked(boolean locked) {this.locked = locked;}​public Integer getId() {return id;}​public void setId(Integer id) {this.id = id;}​public List<Role> getRoles() {return roles;}​public void setRoles(List<Role> roles) {this.roles = roles;}}​

(2)Role

 package com.study.entity;​/*** @ClassName Role* @Description TODO* @Author Jiangnan Cui* @Date 2023/2/26 22:45* @Version 1.0*/public class Role {private Integer id;private String name;private String nameZh;​public Integer getId() {return id;}​public void setId(Integer id) {this.id = id;}​public String getName() {return name;}​public void setName(String name) {this.name = name;}​public String getNameZh() {return nameZh;}​public void setNameZh(String nameZh) {this.nameZh = nameZh;}}​

(3)Menu

 package com.study.entity;​import java.util.List;​/*** @ClassName Menu* @Description TODO* @Author Jiangnan Cui* @Date 2023/2/26 22:45* @Version 1.0*/public class Menu {private Integer id;private String pattern;private List<Role> roles;​public List<Role> getRoles() {return roles;}​public void setRoles(List<Role> roles) {this.roles = roles;}​public Integer getId() {return id;}​public void setId(Integer id) {this.id = id;}​public String getPattern() {return pattern;}​public void setPattern(String pattern) {this.pattern = pattern;}}​
  • 创建mapper接口

(1)UserMapper

 package com.study.mapper;​import com.study.entity.Role;import com.study.entity.User;import org.apache.ibatis.annotations.Mapper;​import java.util.List;​/*** @ClassName UserMapper* @Description TODO* @Author Jiangnan Cui* @Date 2023/2/26 23:08* @Version 1.0*/@Mapperpublic interface UserMapper {// 根据用户id获取角色信息List<Role> getUserRoleByUid(Integer uid);​// 根据用户名获取用户信息User loadUserByUsername(String username);}​

(2)MenuMapper

 package com.study.mapper;​import com.study.entity.Menu;import org.apache.ibatis.annotations.Mapper;​import java.util.List;​/*** @ClassName MenuMapper* @Description TODO* @Author Jiangnan Cui* @Date 2023/2/26 23:09* @Version 1.0*/@Mapperpublic interface MenuMapper {List<Menu> getAllMenu();}​
  • 创建mapper文件

(1)UserMapper

 <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.study.mapper.UserMapper">​<select id="loadUserByUsername" resultType="com.study.entity.User">select *from userwhere username = #{username};</select>​<select id="getUserRoleByUid" resultType="com.study.entity.Role">select r.*from role r,user_role urwhere ur.uid = #{uid}and ur.rid = r.id</select></mapper>​

(2)MenuMapper

 <!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.study.mapper.MenuMapper"><resultMap id="MenuResultMap" type="com.study.entity.Menu"><id property="id" column="id"/><result property="pattern" column="pattern"></result><collection property="roles" ofType="com.study.entity.Role"><id column="rid" property="id"/><result column="rname" property="name"/><result column="rnameZh" property="nameZh"/></collection></resultMap>​<select id="getAllMenu" resultMap="MenuResultMap">select m.*, r.id as rid, r.name as rname, r.nameZh as rnameZhfrom menu mleft join menu_role mr on m.`id` = mr.`mid`left join role r on r.`id` = mr.`rid`</select></mapper>​
  • 创建service接口

(1)UserService

 package com.study.service;​import com.study.entity.User;import com.study.mapper.UserMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.core.userdetails.UserDetails;import org.springframework.security.core.userdetails.UserDetailsService;import org.springframework.security.core.userdetails.UsernameNotFoundException;import org.springframework.stereotype.Service;​/*** @ClassName UserService* @Description TODO* @Author Jiangnan Cui* @Date 2023/2/26 23:12* @Version 1.0*/@Servicepublic class UserService implements UserDetailsService {private final UserMapper userMapper;​@Autowiredpublic UserService(UserMapper userMapper) {this.userMapper = userMapper;}​@Overridepublic UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {// 1.根据用户名查询用户信息User user = userMapper.loadUserByUsername(username);if (user == null) {throw new UsernameNotFoundException("用户不存在");}user.setRoles(userMapper.getUserRoleByUid(user.getId()));return user;}}​

(2)MenuService

 package com.study.service;​import com.study.entity.Menu;import com.study.mapper.MenuMapper;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;​import java.util.List;​/*** @ClassName MenuService* @Description TODO* @Author Jiangnan Cui* @Date 2023/2/26 23:14* @Version 1.0*/@Servicepublic class MenuService {private final MenuMapper menuMapper;​@Autowiredpublic MenuService(MenuMapper menuMapper) {this.menuMapper = menuMapper;}​public List<Menu> getAllMenu() {return menuMapper.getAllMenu();}}​
  • 创建测试controller

     package com.study.controller;​import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;​/*** @ClassName HelloController* @Description TODO* @Author Jiangnan Cui* @Date 2023/2/26 22:49* @Version 1.0*/@RestControllerpublic class HelloController {/*** 数据库表中菜单与角色关系:* /admin/**   ROLE_ADMIN* /user/**    ROLE_USER* /guest/**   ROLE_USER ROLE_GUEST* <p>* 数据库表中用户与角色关系:* admin      ADMIN USER* user       USER* blr        GUEST*/@GetMapping("/admin/hello")public String admin() {return "hello admin";}​@GetMapping("/user/hello")public String user() {return "hello user";}​@GetMapping("/guest/hello")public String guest() {return "hello guest";}​@GetMapping("/hello")public String hello() {return "hello";}}​
  • 创建CustomSecurityMetadataSource

     package com.study.security.metasource;​import com.study.entity.Menu;import com.study.service.MenuService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.security.access.ConfigAttribute;import org.springframework.security.access.SecurityConfig;import org.springframework.security.web.FilterInvocation;import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;import org.springframework.stereotype.Component;import org.springframework.util.AntPathMatcher;​import java.util.Collection;import java.util.List;​/*** @ClassName CustomSecurityMetadataSource* @Description TODO* @Author Jiangnan Cui* @Date 2023/2/26 23:01* @Version 1.0*/@Componentpublic class CustomSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {private final MenuService menuService;AntPathMatcher antPathMatcher = new AntPathMatcher();​@Autowiredpublic CustomSecurityMetadataSource(MenuService menuService) {this.menuService = menuService;}​/*** 自定义动态资源权限元数据信息** @param object* @return* @throws IllegalArgumentException*/@Overridepublic Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {// 1.当前请求对象String requestURI = ((FilterInvocation) object).getRequest().getRequestURI();// 2.查询所有菜单List<Menu> allMenu = menuService.getAllMenu();for (Menu menu : allMenu) {if (antPathMatcher.match(menu.getPattern(), requestURI)) {String[] roles = menu.getRoles().stream().map(r -> r.getName()).toArray(String[]::new);return SecurityConfig.createList(roles);}}return null;}​@Overridepublic Collection<ConfigAttribute> getAllConfigAttributes() {return null;}​@Overridepublic boolean supports(Class<?> clazz) {return FilterInvocation.class.isAssignableFrom(clazz);}}​
  • 配置Security配置

     package com.study.security.config;​import com.study.security.metasource.CustomSecurityMetadataSource;import com.study.service.UserService;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.ApplicationContext;import org.springframework.context.annotation.Configuration;import org.springframework.security.config.annotation.ObjectPostProcessor;import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;import org.springframework.security.config.annotation.web.builders.HttpSecurity;import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;import org.springframework.security.config.annotation.web.configurers.UrlAuthorizationConfigurer;import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;​/*** @ClassName SecurityConfig* @Description TODO* @Author Jiangnan Cui* @Date 2023/2/26 22:50* @Version 1.0*/@Configurationpublic class SecurityConfig extends WebSecurityConfigurerAdapter {private final CustomSecurityMetadataSource customSecurityMetadataSource;private final UserService userService;​@Autowiredpublic SecurityConfig(CustomSecurityMetadataSource customSecurityMetadataSource, UserService userService) {this.customSecurityMetadataSource = customSecurityMetadataSource;this.userService = userService;}​// 注入@Overrideprotected void configure(AuthenticationManagerBuilder auth) throws Exception {auth.userDetailsService(userService);}​// 基本配置@Overrideprotected void configure(HttpSecurity http) throws Exception {// 1.获取工厂对象ApplicationContext applicationContext = http.getSharedObject(ApplicationContext.class);// 2.设置自定义的url权限处理http.apply(new UrlAuthorizationConfigurer<>(applicationContext)).withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {@Overridepublic <O extends FilterSecurityInterceptor> O postProcess(O object) {object.setSecurityMetadataSource(customSecurityMetadataSource);// 是否拒绝公共资源访问object.setRejectPublicInvocations(false);return object;}});http.formLogin().and().csrf().disable();}}​

2.4.3 启动入口类进行测试

根据用户、角色、菜单关系,进行对应测试,此处略。

2.4.4 项目整体目录结构

如有问题,欢迎批评指正! 

【编程不良人】SpringSecurity实战学习笔记07---授权相关推荐

  1. 【编程不良人】MongoDB最新实战教程学习笔记

    简介 视频链接:01.简介和历史_哔哩哔哩_bilibili 文档地址: https://docs.mongodb.com/manual/ MongoDB教程:MongoDB 教程 | 菜鸟教程 注意 ...

  2. Hadoop 从入门到精通----leo学习编程不良人视频的笔记--part01

    编程不良人原版笔记 - https://blog.csdn.net/wei198621/article/details/111280555 part 01 hadoop 集群的搭建 – https:/ ...

  3. jwt实战详解--B站编程不良人视频笔记

    文章目录 前言 一.什么是JWT 二.JWT能做什么 1.授权 2.信息交换 三.为什么使用JWT 四.JWT的结构是什么 五.使用JWT 1.引入依赖 2.生成token 3.根据令牌和签名解析数据 ...

  4. 【编程不良人】快速入门Spring学习笔记08---事务属性、Spring整合Structs2框架(SM)、Spring整合Mybatis+Struts2(SSM)、Spring注解、SSM注解式开发

    1. 事务属性 1.1 事务传播属性 配套视频:[编程不良人]快速入门Spring,SpringBoot.SpringCloud学不好完全是因为Spring没有掌握!_哔哩哔哩_bilibili # ...

  5. 【编程不良人】快速入门SpringBoot学习笔记06---RestFul、异常处理、CORS跨域、Jasypt加密

    1. RestFul 配套视频:[编程不良人]2021年SpringBoot最新最全教程_哔哩哔哩_bilibili 1.1 引言 REST全称是(Resources) Representationa ...

  6. Hadoop 从入门到精通----编程不良人笔记

    编程不良人原版笔记 - https://blog.csdn.net/wei198621/article/details/111280555 part 01 hadoop 集群的搭建 – https:/ ...

  7. java后验条件_JAVA并发实战学习笔记——3,4章~

    JAVA并发实战学习笔记 第三章 对象的共享 失效数据: java程序实际运行中会出现①程序执行顺序对打乱:②数据对其它线程不可见--两种情况 上述两种情况导致在缺乏同步的程序中出现失效数据这一现象, ...

  8. Java 8 实战学习笔记

    Java 8 实战学习笔记 @(JAVASE)[java8, 实战, lambda] 文章目录 Java 8 实战学习笔记 参考内容 Lambda表达式 Lambda环绕执行模式(抽离步骤) 原始代码 ...

  9. 《机电传动控制》学习笔记-07

    <机电传动控制>学习笔记07 胡恒谦 机卓1301 (注:本周补上第7周的学习笔记) PLC的编程元件: PLC内部有许多不同功能的器件,实际上这些器件是由电子电路和存储器组成的. 1.  ...

最新文章

  1. angular reactive form
  2. 谷歌自研终端AI芯片曝出重大进展,联手三星,用于手机笔记本
  3. 云网融合 — 应用场景
  4. MongoDB 之 幽灵操作避免
  5. UVA-1 #1. A + B Problem
  6. navicat模型显示注释_RetinaNet模型构建面罩检测器
  7. 将字符串中的字符按Z字形排列,按行输出
  8. SpringBoot集成Flowable_Jsite已发任务菜单报500
  9. adsl服务器客户端配置cisco_【干货】Cisco路由排错经典案例分析
  10. 2011(信息学奥赛一本通-T1234)
  11. 自动驾驶领域常用的数据集(检测,分割,定位)
  12. 64位浮点数_浮点数误差
  13. 电脑新建文件夹快捷键
  14. 系统技巧:电脑系统盘实用清理方案介绍
  15. HDU5285.wyh2000 and pupil
  16. openGL之API学习(二零七)glTexCoordPointer
  17. 用opencv简单绘图
  18. 西门子——不同数据的存储方式
  19. java超级计算器,jdk自带类
  20. 【活动报名】大数据的流向,究竟去向何处?——深圳站

热门文章

  1. fcm算法 java_使用FCM从服务器发送推送通知
  2. 计算机三级网络app,‎App Store 上的“计算机三级网络技术题库”
  3. 【java面对对象】分数类型加减乘除运算的实现
  4. servlet使用监听器统计网站在线人数
  5. 关于梅森素数定理(网上收集)
  6. 同学用javascript写好了论坛自动灌水机。
  7. 齐聚绿城 | 锦江都城酒店聚焦中高端酒店投资新方向
  8. Linux命令·chmod
  9. 中国现在开源系统cms 论坛排行榜
  10. AOSP中make clean与make clobber的区别