SpringSecurity(三)

权限配置

权限配置一般有两种做法:一种是给用户挂角色,角色管权限;另一种是直接给用户赋具体权限。两种方式在不同场景下都要用。

要搞懂权限,得先知道三张表的关系:

  • 用户和角色:多对多一个用户能有多个角色,一个角色也能对应多个用户。
  • 角色和权限:多对多一个角色能拥有多个权限,一个权限也可以属于多个角色。

如图:

多对多的表关系,在MySQL中就学过,就需要通过中间表来维护两个表之间的表关系。

一、角色权限配置

1.1 创建表

sql 复制代码
DROP TABLE IF EXISTS `user`;
-- 创建用户表
CREATE TABLE IF NOT EXISTS `user`
(
  `id`          int(11)   NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `username`    varchar(50)  NOT NULL COMMENT '用户名',
  `password`    varchar(100) NOT NULL COMMENT '密码',
  `real_name`   varchar(50) DEFAULT NULL COMMENT '真实姓名',
  `phone`       varchar(20) DEFAULT NULL COMMENT '手机号',
  `status`      tinyint(4)  DEFAULT 1 COMMENT '状态:0-禁用,1-正常',
  `create_time` datetime    DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_username` (`username`)
) ENGINE = InnoDB
  DEFAULT CHARSET = utf8mb4 COMMENT ='用户表';
-- 插入用户示例数据(密码统一"123456")
INSERT INTO `user` (`username`, `password`, `real_name`, `phone`, `status`, `create_time`)
VALUES
('liuyan', '$2a$10$ocD9tqXW9qDoSeLtw9YQHOMZuvdmHxl7Uwr6Mz0R3eteUeDYtXRw6', '柳岩', '13800138019', 0, '2024-04-12 08:30:00'),
('liutao', '$2a$10$Tbu3qCWY/GFAeh8Ihfs1v.hsM/KFfmaYi7zAx6vuYmauU7GohdNLS', '刘涛', '13800138020', 0, '2024-04-20 15:10:00');

select * from user;


# ---------------------------------------------------
DROP TABLE IF EXISTS `t_role`;
-- 角色表
CREATE TABLE `t_role`
(
  `id`        int(11) NOT NULL AUTO_INCREMENT,
  `role`      varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `role_name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
  AUTO_INCREMENT = 6
  CHARACTER SET = utf8mb4
  COLLATE = utf8mb4_general_ci COMMENT = '角色表'
  ROW_FORMAT = DYNAMIC;
-- 添加数据
INSERT INTO `t_role`
VALUES (1, 'admin', '管理员');
INSERT INTO `t_role`
VALUES (2, 'saler', '销售员');
INSERT INTO `t_role`
VALUES (3, 'manager', '销售经理');
INSERT INTO `t_role`
VALUES (4, 'marketing ', '市场营销');
INSERT INTO `t_role`
VALUES (5, 'accountant', '会计');

-- 用户与角色中间表
DROP TABLE IF EXISTS `t_user_role`;
CREATE TABLE `t_user_role`
(
  `id`      int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NULL DEFAULT NULL,
  `role_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `t_user_role_ibfk_1` (`user_id`) USING BTREE,
  INDEX `t_user_role_ibfk_2` (`role_id`) USING BTREE,
  CONSTRAINT `t_user_role_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT `t_user_role_ibfk_2` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE = InnoDB
  AUTO_INCREMENT = 3
  CHARACTER SET = utf8mb4
  COLLATE = utf8mb4_general_ci COMMENT = '用户角色关系表'
  ROW_FORMAT = DYNAMIC;

-- ----------------------------
-- 添加数据
-- ----------------------------
INSERT INTO `t_user_role`
VALUES (1, 1, 1);
INSERT INTO `t_user_role`
VALUES (2, 2, 2);

1.2 通过逆向工程生成

通过逆向工程生成t_role的相关实体,mapper及映射文件

1.3 角色权限需求

用户(登录认证成功)--> 给用户配置角色 --> 给角色配置能访问的资源

1.4 角色权限步骤

(1)、需要有一个用户;(从数据库查询用户)

(2)、给用户配置角色;(从数据库查询用户的角色)

(3)、给角色配置能访问的资源(这一步采用切面拦截,使用的是注解)

常用注解如下:

@PreAuthorize 在方法调用前进行权限检查;(常用)

@PostAuthorize 在方法调用后进行权限检查;(很少用)

如果需要如上两个注解生效,需要在security的配置类上添加如下注解使其生效:

@EnableMethodSecurity

注解的详细用法:

@PreAuthorize("hasRole('xxx')")和@PreAuthorize("hasAuthority('xxx')")都是用于方法级权限控制的注解,但它们在权限标识的处理上存在关键区别,核心在于角色(Role)和权限(Authority)的语义与格式差异。

@PreAuthorize("hasRole('admin')"):一般用来做角色的授权行为

@PreAuthorize("hasAuthority('xxx')"):一般来做资源的授权定位

本质区别:角色与权限的语义

  • hasRole('角色名'):专门用于判断用户是否拥有某个角色。

Spring Security 会自动为角色名添加前缀****ROLE_ (默认配置),因此实际校验的权限是ROLE_角色名。

比如某个用户拥有角色为admin,那么框架实际验证的是ROLE_admin,因此我们需要在做角色授权的时候自己加上ROLE_。 这是因为Spring Security框架内的DefaultMethodSecurityExpressionHandler
在验证表达式时是有"ROLE_"前缀的。

  • hasAuthority('权限名'):用于判断用户是否拥有某个具体权限 。
    不会对权限名做任何处理,直接校验用户是否包含该权限字符串。

也可以自定义前缀:

1.5 具体实现

如果需要看某个用户是否拥有某个角色,我们是不是应该有一个数据库查询,根据用户id查询角色信息呢?

而逆向工程中是没有这个操作的,因为这是一个联表查询

TRoleMapper中添加通过用户id查询角色信息的抽象方法

1.5.1 根据用户id查询角色信息

1.5.2 映射文件

这个没必要和user表也关联了,因为这里在中间表中有用户id了。

1.5.3 给用户配置角色

我们当时实现UserDeatils的接口的时候里面有一个权限的列表属性:

这边需要的是一个集合,那么我们给它角色的集合就好了,怎么办呢?

好办,在TUser中添加一个关于TRole的集合不久好了吗!

集合泛型是一个接口,那么我们给它实现类的类型就好了,

ctrl+H看该接口有哪些实现:

看吧,在创建该对象的时候将角色赋值给它就好了,如下如图:

所以最终的代码如下:

TUser.java

java 复制代码
/**
 * 用户表 user
 */
@Data
public class TUser implements UserDetails, Serializable {
    /**
     * 用户ID
     */
    private Integer id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    @JsonIgnore  // 忽略返回的JSON内容
    private String password;

    /**
     * 真实姓名
     */
    private String realName;

    /**
     * 手机号
     */
    private String phone;

    /**
     * 状态:0-禁用,1-正常
     */
    private Byte status;

    /**
     * 创建时间
     */
    // @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;

    /**
     * 当前用户所有拥有的角色
     */
    @JsonIgnore // 忽略返回的JSON内容
    private List<TRole> tRoleList;

    /**
     * ------------------实现UserDetails的内容----------------------------
     * 获取权限(角色/权限code), 该方法会在角色验证的时候自动来调用
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        // 集合中存储着权限信息
        Collection<SimpleGrantedAuthority> collection = new ArrayList<>();
        // 遍历当前用户所有拥有的角色
        for (TRole tRole : tRoleList) {
            // 给用户授予角色配置  手动在角色先拼接一个ROLE_前缀
            collection.add(new SimpleGrantedAuthority("ROLE_" + tRole.getRole()));
        }
        return collection;
    }
}

1.5.4 自定义Service赋值角色

在用户对象的业务层查询当前用户所拥有的角色,并设置到TUser对象中

1.5.5 自定义Controller相关方法

创建新的ProductController进行测试

java 复制代码
package com.sy.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/product")
public class ProductController {
    
    @PreAuthorize("hasRole('saler')")
    @GetMapping("/list")
    public String productList(){
        return "productList";
    }


    @PreAuthorize("hasRole('saler')")
    @GetMapping("/update")
    public String productUpdate(){
        return "productUpdate";
    }


    //表示只要有以下角色中的任意一个角色就行可以操作
    @PreAuthorize("hasAnyRole('admin','saler')")
    @GetMapping("/add")
    public String productAdd(){
        return "productAdd";
    }


    @PreAuthorize("hasRole('admin')")
    @GetMapping("/del")
    public String productDel(){
        return "productDel";
    }
}

注意:需要在配置类中添加注解表示开启注解支持

测试结果如下:

当前访问 http://localhost:8080/index1 地址时先进行用户的认证

用户认证成功后,获取用户的详细信息

由于liutao是销售员当访问 http://localhost:8080/product/list 请求时发现可以正常访问

当访问 http://localhost:8080/product/add 请求时发现可以正常访问

当访问 http://localhost:8080/product/del 请求时发现无法正常访问,说明没有权限

以上是我们使用liutao用户去测试的,该用户有saler角色,但是没有admin角色。

1.5.6 定义错误页面

当没有权限的时候错误页,给用户的体验性不好,我们去自定义一个错误页

必须严格按照这个格式来编写,默认读取static/error下的对应状态码的页面

再次访问 http://localhost:8080/product/del 请求时发现无法正常访问时跳转到权限不足的403页面。

二、资源权限配置

数据表设计:权限表和权限和角色的中间表

sql 复制代码
#权限表
DROP TABLE IF EXISTS `t_permission`;
CREATE TABLE `t_permission`
(
  `id`        int(11)  NOT NULL AUTO_INCREMENT comment '权限ID',
  `name`      varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NULL DEFAULT NULL COMMENT '权限名称',
  `code`      varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci  NULL DEFAULT NULL COMMENT '权限标识符',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB
  AUTO_INCREMENT = 9
  CHARACTER SET = utf8mb4
  COLLATE = utf8mb4_general_ci COMMENT = '权限表'
  ROW_FORMAT = DYNAMIC;
#数据添加
INSERT INTO `t_permission` VALUES (21, '客户管理-列表', 'customer:list');
INSERT INTO `t_permission` VALUES (22, '客户管理-查看', 'customer:view');
INSERT INTO `t_permission` VALUES (23, '客户管理-导出', 'customer:export');
INSERT INTO `t_permission` VALUES (30, '产品管理-列表', 'product:list');
INSERT INTO `t_permission` VALUES (31, '产品管理-录入', 'product:add');
INSERT INTO `t_permission` VALUES (32, '产品管理-编辑', 'product:edit');
INSERT INTO `t_permission` VALUES (33, '产品管理-查看', 'product:view');
INSERT INTO `t_permission` VALUES (34, '产品管理-删除', 'product:delete');
# 角色和权限中间表
DROP TABLE IF EXISTS `t_role_permission`;
CREATE TABLE `t_role_permission`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `role_id` int(11) NULL DEFAULT NULL,
  `permission_id` int(11) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `t_role_permission_ibfk_1`(`role_id`) USING BTREE,
  INDEX `t_role_permission_ibfk_2`(`permission_id`) USING BTREE,
  CONSTRAINT `t_role_permission_ibfk_1` FOREIGN KEY (`role_id`) REFERENCES `t_role` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE,
  CONSTRAINT `t_role_permission_ibfk_2` FOREIGN KEY (`permission_id`) REFERENCES `t_permission` (`id`) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE = InnoDB AUTO_INCREMENT = 77 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色权限关系表' ROW_FORMAT = DYNAMIC;

#  添加数据
INSERT INTO `t_role_permission` VALUES (1, 1, 21);
INSERT INTO `t_role_permission` VALUES (2, 1, 21);
INSERT INTO `t_role_permission` VALUES (3, 1, 23);
INSERT INTO `t_role_permission` VALUES (4, 1, 30);
INSERT INTO `t_role_permission` VALUES (5, 1, 31);
INSERT INTO `t_role_permission` VALUES (6, 1, 32);
INSERT INTO `t_role_permission` VALUES (7, 1, 33);
INSERT INTO `t_role_permission` VALUES (8, 1, 34);
INSERT INTO `t_role_permission` VALUES (9, 2, 30);
INSERT INTO `t_role_permission` VALUES (10, 2, 31);
INSERT INTO `t_role_permission` VALUES (11, 2, 32);
INSERT INTO `t_role_permission` VALUES (12, 2, 33);

剩下的和角色配置大致一致,不同的是注解使用 @PreAuthorize("hasAuthority('xxx')")就可以了

步骤:

  • 逆向工程生成权限的实体,Mapper接口 和 映射
  • 添加方法,通过用户ID查询所有的权限(5表联合查询)/同样在用户实体中添加权限的集合
  • 给用户配置权限 用户实体中/自定义service中
  • 添加controller相关方法测试

2.1 Mapper编写以及映射文件

Mapper接口

Mapper映射文件

TPermissionMapper.xml

XML 复制代码
<!--根据用户id查询权限标识符-->
<select id="findPermissionByUid" resultType="com.sy.pojo.TPermission" parameterType="java.lang.Integer">
  SELECT
    tp.id,
    tp.`name`,
    tp.CODE
  FROM
    t_permission tp
      LEFT JOIN t_role_permission trp ON tp.id = trp.permission_id
      LEFT JOIN t_role tr ON trp.role_id = tr.id
      LEFT JOIN t_user_role tur ON tr.id = tur.role_id
  WHERE
    tur.user_id = #{uid}
</select>

2.2 给用户配置权限

2.2.1 给实体User中添加临时的权限集合属性

2.2.2 给用户授予权限

TUser.java

java 复制代码
/**
 * 用户表 user
 */
@Data
public class TUser implements UserDetails, Serializable {
    /**
     * 用户ID
     */
    private Integer id;

    /**
     * 用户名
     */
    private String username;

    /**
     * 密码
     */
    @JsonIgnore  // 忽略返回的JSON内容
    private String password;

    /**
     * 真实姓名
     */
    private String realName;

    /**
     * 手机号
     */
    private String phone;

    /**
     * 状态:0-禁用,1-正常
     */
    private Byte status;

    /**
     * 创建时间
     */
    // @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date createTime;

    /**
     * 当前用户所有拥有的角色
     */
    // @JsonIgnore // 忽略返回的JSON内容
    // private List<TRole> tRoleList;

    @JsonIgnore // 忽略返回的JSON内容
    private List<TPermission> tPermissionList;

    /**
     * ------------------实现UserDetails的内容----------------------------
     * 获取权限(角色/权限code), 该方法会在角色验证的时候自动来调用
     * @return
     */
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        Collection<SimpleGrantedAuthority> authorities = new ArrayList<>();
        for (TPermission tPermission : tPermissionList) {
            SimpleGrantedAuthority simpleGrantedAuthority =
                    new SimpleGrantedAuthority(tPermission.getCode());
            authorities.add(simpleGrantedAuthority);
        }
        return authorities;
    }
}

2.2.3 给User实体中的权限集合赋值

依赖注入对应的Mapper

通过数据库查询然后给属性赋值

2.2.4 设定场景测试

GoodsController.java

java 复制代码
package com.sy.controller;

import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/goods")
public class GoodsController {

    /**
     * 模拟商品列表功能
     * @return
     */
    //验证当前用户是否用于product:list的权限标识符
    @PreAuthorize("hasAuthority('product:list')")
    @GetMapping("/list")
    public String goodsList(){
        return "goodsList";
    }

    /**
     * 模拟商品查看功能
     * @return
     */
    @PreAuthorize("hasAuthority('product:view')")
    @GetMapping("/view")
    public String goodsView(){
        return "goodsView";
    }

    /**
     * 模拟商品修改功能
     * @return
     */
    @PreAuthorize("hasAuthority('product:edit')")
    @GetMapping("/edit")
    public String goodsEdit(){
        return "goodsEdit";
    }

    /**
     * 模拟商品添加功能
     * @return
     */
    @PreAuthorize("hasAuthority('product:add')")
    @GetMapping("/add")
    public String goodsAdd(){
        return "goodsAdd";
    }

    
    @PreAuthorize("hasAuthority('product:del')")
    @GetMapping("/del")
    public String goodsDel(){
        return "goodsDel";
    }

}

测试结果如下:

当前访问 http://localhost:8080/index 地址时先进行用户的认证

用户认证成功后,获取用户的详细信息

认证成功后,可以看到 liutao 当前用户的权限 有 增改查 权限操作,没有删除权限。

先测试下 增改查 的某一个功能,http://localhost:8080/goods/list 结果如下图:

通过测试 http://localhost:8080/goods/del 删除功能时结果如下图:

此时发现当前用户尚未拥有删除权限,无法执行删除操作。

相关推荐
三少爷的鞋3 小时前
AI 时代,我们都将成为通才型开发者:只懂 Android,已经不够了
android
Dovis(誓平步青云)7 小时前
家里设备越来越多,如何用一张空间地图控制灯光和温度![
android·java·前端·javascript·人工智能·电脑
晚风叙码8 小时前
MySQL 数据类型详解:从数值到字符串,一篇讲透
android·mysql·adb
传奇开心果编程9 小时前
【Compose Multiplatform 跨端开发学与练】第3课 布局与组件
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程12 小时前
【Compose Multiplatform 跨端开发学与练】第8课 资源管理与主题
android·windows·学习·ios·kotlin·web·composer
传奇开心果编程13 小时前
【Compose Multiplatform 跨端开发学与练】第9课 测试与调试
android·学习·macos·ios·kotlin·web·composer
传奇开心果编程13 小时前
【Compose Multiplatform 跨端开发学与练】第4课 导航与路由
android·windows·学习·ui·ios·kotlin·composer
传奇开心果编程14 小时前
【Compose Multiplatform 跨端开发学与练】第6课 状态管理与架构
android·学习·ui·ios·架构·kotlin·composer
事圆则缓14 小时前
Android AOSP 定制常见概念:源码目录、系统镜像与刷机流程
android
传奇开心果编程14 小时前
【Compose Multiplatform 跨端开发学与练】第2课 Compose 基础语法
android·windows·学习·ui·ios·kotlin·composer