【编程不良人】SpringSecurity实战学习笔记07---授权
配套视频: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---授权相关推荐
- 【编程不良人】MongoDB最新实战教程学习笔记
简介 视频链接:01.简介和历史_哔哩哔哩_bilibili 文档地址: https://docs.mongodb.com/manual/ MongoDB教程:MongoDB 教程 | 菜鸟教程 注意 ...
- Hadoop 从入门到精通----leo学习编程不良人视频的笔记--part01
编程不良人原版笔记 - https://blog.csdn.net/wei198621/article/details/111280555 part 01 hadoop 集群的搭建 – https:/ ...
- jwt实战详解--B站编程不良人视频笔记
文章目录 前言 一.什么是JWT 二.JWT能做什么 1.授权 2.信息交换 三.为什么使用JWT 四.JWT的结构是什么 五.使用JWT 1.引入依赖 2.生成token 3.根据令牌和签名解析数据 ...
- 【编程不良人】快速入门Spring学习笔记08---事务属性、Spring整合Structs2框架(SM)、Spring整合Mybatis+Struts2(SSM)、Spring注解、SSM注解式开发
1. 事务属性 1.1 事务传播属性 配套视频:[编程不良人]快速入门Spring,SpringBoot.SpringCloud学不好完全是因为Spring没有掌握!_哔哩哔哩_bilibili # ...
- 【编程不良人】快速入门SpringBoot学习笔记06---RestFul、异常处理、CORS跨域、Jasypt加密
1. RestFul 配套视频:[编程不良人]2021年SpringBoot最新最全教程_哔哩哔哩_bilibili 1.1 引言 REST全称是(Resources) Representationa ...
- Hadoop 从入门到精通----编程不良人笔记
编程不良人原版笔记 - https://blog.csdn.net/wei198621/article/details/111280555 part 01 hadoop 集群的搭建 – https:/ ...
- java后验条件_JAVA并发实战学习笔记——3,4章~
JAVA并发实战学习笔记 第三章 对象的共享 失效数据: java程序实际运行中会出现①程序执行顺序对打乱:②数据对其它线程不可见--两种情况 上述两种情况导致在缺乏同步的程序中出现失效数据这一现象, ...
- Java 8 实战学习笔记
Java 8 实战学习笔记 @(JAVASE)[java8, 实战, lambda] 文章目录 Java 8 实战学习笔记 参考内容 Lambda表达式 Lambda环绕执行模式(抽离步骤) 原始代码 ...
- 《机电传动控制》学习笔记-07
<机电传动控制>学习笔记07 胡恒谦 机卓1301 (注:本周补上第7周的学习笔记) PLC的编程元件: PLC内部有许多不同功能的器件,实际上这些器件是由电子电路和存储器组成的. 1. ...
最新文章
- angular reactive form
- 谷歌自研终端AI芯片曝出重大进展,联手三星,用于手机笔记本
- 云网融合 — 应用场景
- MongoDB 之 幽灵操作避免
- UVA-1 #1. A + B Problem
- navicat模型显示注释_RetinaNet模型构建面罩检测器
- 将字符串中的字符按Z字形排列,按行输出
- SpringBoot集成Flowable_Jsite已发任务菜单报500
- adsl服务器客户端配置cisco_【干货】Cisco路由排错经典案例分析
- 2011(信息学奥赛一本通-T1234)
- 自动驾驶领域常用的数据集(检测,分割,定位)
- 64位浮点数_浮点数误差
- 电脑新建文件夹快捷键
- 系统技巧:电脑系统盘实用清理方案介绍
- HDU5285.wyh2000 and pupil
- openGL之API学习(二零七)glTexCoordPointer
- 用opencv简单绘图
- 西门子——不同数据的存储方式
- java超级计算器,jdk自带类
- 【活动报名】大数据的流向,究竟去向何处?——深圳站