目录

一、引入依赖及原理简述
二、多用户维护配置
三、基础自定义配置
四、前后端分离及登录结果管理
五、角色权限管理基础 <— 你在这里 ( •̀ ω •́ )y


从此处开始,为新的原创内容,相关数据结构代码换了一套新的,与之前的代码关系不大了。
建议新建一个项目,将配置文件复制过来,然后按照步骤走。

六、RBAC 结构实现
七、自定义响应式登录与 JWT 配置
八、集成 Redis

Spring-Security(五)角色权限管理基础

博主前言:本以为这个就是代替传统 jwt 的插件,没想到复杂程度如此之高。Spring Security 本身是个高度自定义化的组件,必须花时间重点学习一下。以下为个人配置学习的流程,从零到权限管理、redis嵌入等步骤。
本文基于尚硅谷的 Spring Security 教程学习,文章与原教程有不小出入,仅供参考。
B站视频链接:尚硅谷Java项目SpringSecurity+OAuth2权限管理实战教程

现阶段的项目,一般都包含管理端和用户端,管理端部分功能用户端是不能使用的,所以我们需要一套权限系统来实现。

回想起之前写提瓦特外卖的时光了,那时候也是双端,管理端和用户端。


一、权限简介及修改逻辑结构

授权管理的实现在 Spring Security 中非常灵活,可以帮助应用程序实现以下两种常见的授权需求:

  • 用户 - 权限 - 资源:例如张三的权限是添加用户、查看用户列表,李四的权限是查看用户列表
  • 用户 - 角色 - 资源:例如 张三是角色是管理员、李四的角色是普通用户,管理员能做所有操作,普通用户只能查看信息

对于用户,我们需要为其添加权限字段,供 Spring Security 来判断,每个用户可以拥有多个字段,对应多个权限。

我们先来改造一下User类,要求添加数据库排除的权限字段及重写对应接口方法:

实际上,该字段应该在数据库中体现,有些结构还配置有权限表进行一对多或多对多的链接。
此处为演示,简单写个字段,数据库中没有,需要被排除。

1
2
3
4
5
6
7
@TableField(exist = false)
private List<GrantedAuthority> authorities;

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}

之后,我们重写用户信息维护服务中的装载用户方法,就可以为其写死一个权限:

1
2
3
4
5
6
7
8
9
10
11
12
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
var user = getByUsername(username);
if (user == null)
throw new UsernameNotFoundException("用户不存在:" + username);
var authorityList = new ArrayList<GrantedAuthority>();
// 这里以【用户 - 权限 - 资源】为例
authorityList.add(() -> "USER_LIST");
// 没有添加 USER_ADD 权限,为接下来的操作做区分
user.setAuthorities(authorityList);
return user;
}

二、配置自定义无权限返回数据

首先让SecurityResultHandler继承AccessDeniedHandler接口,然后实现对应方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* 无权限访问
*
* @param request 请求
* @param response 响应
* @param accessDeniedException 异常信息
*/
@Override
public void handle(
HttpServletRequest request,
HttpServletResponse response,
AccessDeniedException accessDeniedException)
throws IOException {
// 简单构造一个无权限访问的响应结果 json
var result = Result.error(-1, "该用户无权访问");
var resultJSON = JSON.toJSONString(result);

// 返回 json 数据给前端
response.setContentType("application/json;charset=UTF-8");
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.getWriter().println(resultJSON);
}

接下来,在Config类中配置:

1
2
3
4
5
6
7
// 自定义异常处理
http.exceptionHandling(exception -> exception
// 登录异常(请求未认证)处理
.authenticationEntryPoint(securityResultHandler)
// 无权限访问处理
.accessDeniedHandler(securityResultHandler)
);

三、基于请求的权限分配

实际开发中,我们可以定义两层结构:权限一层,权限包含的资源是另一层。通过不同的权限分配不同的资源,适用于功能较多逻辑较复杂的情形(例如各大云服务器平台)。

  1. 用户 - 权限 - 资源

    假定目前有以下需求:

    • 具有USER_LIST权限的用户可以访问 /user/list 接口
    • 具有USER_ADD权限的用户可以访问 /user/add 接口

    Config类中配置:

    1
    2
    3
    4
    5
    6
    7
    8
    // 对来自 http/https 的请求的授权保护方法
    http.authorizeHttpRequests(authorize -> authorize
    // 基于请求的权限配置(用户 - 权限 - 资源)
    .requestMatchers("/user/list").hasAuthority("USER_LIST")
    .requestMatchers("/user/add").hasAuthority("USER_ADD")
    // 对所有请求均做授权保护,已认证的会自动授权
    .anyRequest().authenticated())

    重写用户信息维护服务中的装载用户方法,按照【一、权限简介及修改逻辑结构】中的方式重写并写死一个权限。

    此时登录账户并操作,我们会发现查询用户列表正常拿到数据,但尝试添加用户则返回自定义的403结果。

    查询用户列表结果1

    添加用户结果

  2. 用户 - 角色 - 资源

    假定目前有以下需求:

    • 具有ADMIN权限的用户可以访问 /admin 下的所有接口
    • 具有USER权限的用户可以访问 /user 下的所有接口

    Config类中配置(注释掉上面配置的代码):

    1
    2
    3
    4
    5
    6
    7
    // 对来自 http/https 的请求的授权保护方法
    http.authorizeHttpRequests(authorize -> authorize
    // 基于请求的权限配置(用户 - 角色 - 资源)
    .requestMatchers("/admin/**").hasRole("ADMIN")
    .requestMatchers("/user/**").hasRole("USER")
    // 对所有请求均做授权保护,已认证的会自动授权
    .anyRequest().authenticated())

    重写用户信息维护服务中的装载用户方法:

    自定义一个AdminController并写一个测试接口:

    此时登录账户并操作,我们会发现查询用户列表正常拿到数据,但尝试访问测试接口则返回自定义的403结果。

    查询用户列表结果2

    访问测试接口结果