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
?
<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>
需要创建 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;
? ? }
?}
数据源及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<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>
?
?