Spring Security-动态权限控制(1)

SpringSecurity过滤器链,图中绿色的是认证相关的,蓝色部分是异常相关的,而橙色部分是授权相关,今天我们就是要理清橙色部分授权相关的流程,以及实现动态授权。

整个动态授权的过程

accessDecision-flow.png

通过上面的授权流程分析,咱们大致清楚了SpringSecurity是怎么授权的,那么我们要实现动态授权应该怎么做?其实就是实现自定义上图中的两个类:一个是SecurityMetadataSource类用来获取当前请求所需要的权限;另一个是AccessDecisionManager类来实现授权决策

image-20201117151931194

FilterInvocationSecurityMetadataSource:通过此类,获取哪些角色可以访问该 url 。

AccessDecisionManager:通过此类,判断用户时候拥有上述中的角色。

img

因为要实现 动态 基于路径的权限管理, 因此 需要 数据库的支持

项目目录结构

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>

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

射手座的程序媛

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值