权限配置
权限配置一般有两种做法:一种是给用户挂角色,角色管权限;另一种是直接给用户赋具体权限。两种方式在不同场景下都要用。
要搞懂权限,得先知道三张表的关系:
- 用户和角色:多对多一个用户能有多个角色,一个角色也能对应多个用户。
- 角色和权限:多对多一个角色能拥有多个权限,一个权限也可以属于多个角色。
如图:

多对多的表关系,在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 删除功能时结果如下图:
此时发现当前用户尚未拥有删除权限,无法执行删除操作。