SpringSecurity过滤器链,图中绿色的是认证相关的,蓝色部分是异常相关的,而橙色部分是授权相关,今天我们就是要理清橙色部分授权相关的流程,以及实现动态授权。
整个动态授权的过程
通过上面的授权流程分析,咱们大致清楚了SpringSecurity是怎么授权的,那么我们要实现动态授权应该怎么做?其实就是实现自定义上图中的两个类:一个是SecurityMetadataSource类用来获取当前请求所需要的权限;另一个是AccessDecisionManager类来实现授权决策
FilterInvocationSecurityMetadataSource
:通过此类,获取哪些角色可以访问该 url 。
AccessDecisionManager
:通过此类,判断用户时候拥有上述中的角色。
因为要实现 动态 基于路径的权限管理, 因此 需要 数据库的支持
项目目录结构
1.建表
数据库 设计 共 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);
2.建立boot项目
本次采用 父子工程方式: 其中 springboot 版本为 2.7.3 ,
父工程的pom.xml
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.3</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<!--引入mysql -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
<scope>import</scope>
<type>pom</type>
</dependency>
<!--引入 lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
子工程
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.7.3</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.thymeleaf.extras/thymeleaf-extras-springsecurity4 -->
<dependency>
<groupId>org.thymeleaf.extras</groupId>
<artifactId>thymeleaf-extras-springsecurity5</artifactId>
<version>3.0.4.RELEASE</version>
</dependency>
</dependencies>
3.创建实体层
需要创建 Menu与 User ,Role 三个实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Menu {
private int id;
private String pattern;
private List<Role> 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 List<Role>roles; //用户对应的角色
// 返回当前用户的权限列表
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
List<SimpleGrantedAuthority> 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;
}
}
4.增加yaml配置
数据源及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
5.创建mapper层
创建 MenuMapper
package com.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.entity.Menu;
import java.util.List;
public interface MenuMapper extends BaseMapper<Menu> {
//查询所有的菜单项(权限)
List<Menu> getAllMenus();
}
MenuMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.MenuMapper">
<resultMap id="tMap" type="menu">
<id column="id" property="id"/>
<result column="pattern" property="pattern"/>
<collection property="roles" ofType="role">
<id column="rid" property="id"/>
<result column="rname" property="name"/>
<result column="rnameZh" property="nameZh"/>
</collection>
</resultMap>
<select id="getAllMenus" resultMap="tMap">
select m.*,r.id as rid,r.name as rname,r.nameZh as rnameZh
from menu m
left join role_menu mr on m.id=mr.mid
left join role r on mr.rid=r.id
</select>
</mapper>
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> {
//根据用户名 查询 用户信息
User loadUserByUsername(String username);
//获取当前用户的角色
List<Role> getUserRolesById(int uid);
}
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.mapper.UserMapper">
<select id="loadUserByUsername" resultType="user">
select * from user where username=#{username}
</select>
<select id="getUserRolesById" parameterType="int" resultType="role">
select * from role where id in(select rid from user_role where uid=#{id})
</select>
</mapper>