SpringSecurity过滤器链,图中绿色的是认证相关的,蓝色部分是异常相关的,而橙色部分是授权相关,今天我们就是要理清橙色部分授权相关的流程,以及实现动态授权。
整个动态授权的过程
通过上面的授权流程分析,咱们大致清楚了SpringSecurity是怎么授权的,那么我们要实现动态授权应该怎么做?其实就是实现自定义上图中的两个类:一个是SecurityMetadataSource类用来获取当前请求所需要的权限;另一个是AccessDecisionManager类来实现授权决策
FilterInvocationSecurityMetadataSource
:通过此类,获取哪些角色可以访问该 url 。
AccessDecisionManager
:通过此类,判断用户时候拥有上述中的角色。
因为要实现 动态 基于路径的权限管理, 因此 需要 数据库的支持
项目目录结构
数据库 设计 共 5张表
menu表DROP TABLE IF EXISTS `menu`; CREATE TABLE `menu` ( `id` int(0) NOT NULL AUTO_INCREMENT, `pattern` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE )
插入数据INSERT INTO `menu` VALUES (1, '/test/add'); INSERT INTO `menu` VALUES (2, '/test/export'); INSERT INTO `menu` VALUES (3, '/test/update'); INSERT INTO `menu` VALUES (4, '/test/delete'); INSERT INTO `menu` VALUES (5, '/test/query'); INSERT INTO `menu` VALUES (6, '/test/toLogin');
role 角色表DROP TABLE IF EXISTS `role`; CREATE TABLE `role` ( `id` int(0) NOT NULL AUTO_INCREMENT, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `nameZh` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE )
角色表数据INSERT INTO `role` VALUES (1, 'ROLE_admin', '系统管理员'); INSERT INTO `role` VALUES (2, 'ROLE_user', '普通用户');
role_menu 角色与菜单 表CREATE TABLE `role_menu` ( `id` int(0) NOT NULL AUTO_INCREMENT, `mid` int(0) NULL DEFAULT NULL, `rid` int(0) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, INDEX `mid`(`mid`) USING BTREE, INDEX `rid`(`rid`) USING BTREE, CONSTRAINT `role_menu_ibfk_1` FOREIGN KEY (`mid`) REFERENCES `menu` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `role_menu_ibfk_2` FOREIGN KEY (`rid`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT )
数据INSERT INTO `role_menu` VALUES (1, 1, 1); INSERT INTO `role_menu` VALUES (2, 2, 1); INSERT INTO `role_menu` VALUES (3, 3, 1); INSERT INTO `role_menu` VALUES (4, 4, 1); INSERT INTO `role_menu` VALUES (5, 5, 1); INSERT INTO `role_menu` VALUES (6, 2, 2); INSERT INTO `role_menu` VALUES (7, 5, 2);
user表 用户表DROP TABLE IF EXISTS `user`; CREATE TABLE `user` ( `id` int(0) NOT NULL AUTO_INCREMENT, `username` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `password` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, `enabled` bit(1) NULL DEFAULT NULL, `locked` bit(1) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE )
数据INSERT INTO `user` VALUES (1, 'sale', '111', b'1', b'0'); INSERT INTO `user` VALUES (2, 'admin', '111', b'1', b'0');
user_role 用户-角色表
DROP TABLE IF EXISTS `user_role`; CREATE TABLE `user_role` ( `id` int(0) NOT NULL AUTO_INCREMENT, `uid` int(0) NULL DEFAULT NULL, `rid` int(0) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE, INDEX `uid`(`uid`) USING BTREE, INDEX `rid`(`rid`) USING BTREE, CONSTRAINT `user_role_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, CONSTRAINT `user_role_ibfk_2` FOREIGN KEY (`rid`) REFERENCES `role` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT )
数据INSERT INTO `user_role` VALUES (1, 1, 2); INSERT INTO `user_role` VALUES (3, 2, 1);
本次采用 父子工程方式: 其中 springboot 版本为 2.7.3 ,
父工程的pom.xml
org.springframework.boot
spring-boot-dependencies
2.7.3
import
pom
mysql
mysql-connector-java
8.0.30
import
pom
org.projectlombok
lombok
1.18.24
import
pom
子工程
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-security
com.baomidou
mybatis-plus-boot-starter
3.5.2
mysql
mysql-connector-java
org.projectlombok
lombok
org.springframework.boot
spring-boot-starter-thymeleaf
2.7.3
org.thymeleaf.extras
thymeleaf-extras-springsecurity5
3.0.4.RELEASE
需要创建 Menu与 User ,Role 三个实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Menu {
private int id;
private String pattern;
private List roles; // 菜单对应的角色
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Role {
private Integer id;
private String name;
private String nameZh;
}
User 需要 实现UserDetails 接口
package com.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements UserDetails {
private Integer id;
private String username;
private String password;
private boolean enabled;
private boolean locked;
private Listroles; //用户对应的角色
// 返回当前用户的权限列表
@Override
public Collection extends GrantedAuthority> getAuthorities() {
List authorities=new ArrayList<>();
for(Role role:roles){
authorities.add(new SimpleGrantedAuthority(role.getName()));
}
return authorities;
}
@Override
public String getPassword() {
return this.password;
}
@Override
public String getUsername() {
return this.username;
}
//账号是否未过期,直接返回true 表示账户未过期,也可以在数据库中添加该字段
@Override
public boolean isAccountNonExpired() {
return true;
}
//账号是否被锁, 这里和数据库中的locked字段刚好相反,所有取反
@Override
public boolean isAccountNonLocked() {
return !this.locked;
}
//密码是否为过期,数据库中无该字段,直接返回true
@Override
public boolean isCredentialsNonExpired() {
return true;
}
//账户是否可用,从数据库中获取该字段
@Override
public boolean isEnabled() {
return this.enabled;
}
}
数据源及mybatis-plus配置
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/security?serverTimezone=GMT%2B8 username: 自己的用户名 password: 自己的密码 mybatis-plus: mapper-locations: classpath:mapper/*Mapper.xml type-aliases-package: com.entity configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
创建 MenuMapper
package com.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.entity.Menu;
import java.util.List;
public interface MenuMapper extends BaseMapper
MenuMapper.xml
UserMapper.java
package com.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.entity.Role;
import com.entity.User;
import java.util.List;
public interface UserMapper extends BaseMapper {
//根据用户名 查询 用户信息
User loadUserByUsername(String username);
//获取当前用户的角色
List getUserRolesById(int uid);
}
UserMapper.xml