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 删除功能时结果如下图:

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

相关推荐
zh_xuan7 小时前
Android Hilt实现依赖注入
android·hilt
freshman_y7 小时前
Qtcreator怎么新建安卓项目?编写一个五子棋游戏APP?
android·qt
时寒的笔记8 小时前
js逆向7_案例惠nong网
android·开发语言·javascript
肯多洛夫斯基9 小时前
安卓工控屏静默连WiFi全攻略
android
极梦网络无忧9 小时前
Android无障碍服务实现抖音直播间界面监控(场控助手核心原理)
android
call me by ur name10 小时前
ERNIE 5.0 Technical Report论文解读
android·开发语言·人工智能·机器学习·ai·kotlin
kerli11 小时前
Compose 组件:Box 核心参数及其 Bias 算法
android·前端
BLUcoding11 小时前
Android 常用控件及核心属性
android
遥不可及zzz11 小时前
[特殊字符] Android AAB 一键安装工具配置指南
android·macos