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

五、角色权限管理基础


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

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

Spring Security(四)前后端分离及登录结果管理

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

现在的项目基本上都是前后端分离(包括我个人的一些开源项目),前后端分离的项目,后端验证后要向前端返回json数据,而不是跳转链接,所以要另行配置。

本文使用 fastjson 来处理序列化问题,读者亦可使用其他的方式处理,本文仅作参考。

一、定义并整合登录结果管理器

​ 这个东西实际上可以当作LoginController + LoginExceptionHandler等控制类。

​ 我通常不习惯手写返回体,习惯定义主返回体结构,再往里面放数据,这是我定义的通用返回结果类:

我使用的返回体为黑马的几个实战项目常用的返回体,并稍加改造。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
/**
* 通用返回结果类,服务端响应的数据最终都会封装成此对象
*
* @param <T>
* @author Amane64
*/
@Data
public class Result<T> {
/**
* 状态码
*/
private Integer code;
/**
* 错误信息
*/
private String msg;
/**
* 数据体
*/
private T data;

/**
* 成功回调
*
* @param data 数据体
* @param <T> 回调类型
* @return 成功回调数据
*/
public static <T> Result<T> success(T data) {
var result = new Result<T>();
result.data = data;
result.msg = "success";
result.code = 200;
return result;
}

/**
* 成功回调(无数据)
*/
public static <T> Result<T> success() {
var result = new Result<T>();
result.data = null;
result.msg = "success";
result.code = 200;
return result;
}

/**
* @param msg 错误信息
* @param <T> 空回调类型
* @return 空回调
*/
public static <T> Result<T> error(int code, String msg) {
var result = new Result<T>();
result.code = code;
result.msg = msg;
return result;
}
}

​ 对于登录结果管理,我们要实现五件事:

  1. 登录成功之后,处理返回json数据
  2. 登录失败之后,处理返回json数据 * 2
  3. 注销成功之后,处理返回json数据 * 3
  4. 请求未认证后,处理返回json数据 * 4
  5. 会话并发处理,处理返回json数据 * 5

╮(╯▽╰)╭ 五次啊五次 ╮(╯▽╰)╭

会话并发处理:简单来讲就是后登录的账号会使先登录的账号失效

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
/**
* 登录结果处理器
*
* @author Amane64
*/
public class AuthenticationHandler implements AuthenticationSuccessHandler, AuthenticationFailureHandler, LogoutSuccessHandler, AuthenticationEntryPoint, SessionInformationExpiredStrategy {
/**
* 登录成功处理
*
* @param request 请求
* @param response 响应
* @param authentication 认证信息
*/
@Override
public void onAuthenticationSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException {
// 可以在这里生成 token
var resultToken = "登录成功!这是登录后的 token 数据...";

// 简单构造一个登录成功的响应结果 json
var result = Result.success(resultToken);
var resultJSON = JSON.toJSONString(result);

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

/**
* 登录失败处理
*
* @param request 请求
* @param response 响应
* @param exception 异常信息
*/
@Override
public void onAuthenticationFailure(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException exception)
throws IOException {
// 简单构造一个登录失败的响应结果 json
var result = Result.error(-1, exception.getLocalizedMessage());
var resultJSON = JSON.toJSONString(result);

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

/**
* 注销成功处理
*
* @param request 请求
* @param response 响应
* @param authentication 认证信息
*/
@Override
public void onLogoutSuccess(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication)
throws IOException {
// 简单构造一个注销成功的响应结果 json
var resultData = "注销成功!这是注销后的数据...";
var result = Result.success(resultData);
var resultJSON = JSON.toJSONString(result);

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

/**
* 请求未认证处理
*
* @param request 请求
* @param response 响应
* @param authException 异常信息
*/
@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
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);
}

/**
* 会话并发处理
*
* @param event 会话信息
*/
@Override
public void onExpiredSessionDetected(SessionInformationExpiredEvent event) throws IOException {
// 简单构造一个账号登录数量已满的响应结果 json
var result = Result.error(-1, "该账号已从其他设备登录");
var resultJSON = JSON.toJSONString(result);

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

​ 在这之后还没完,要在Config类中配置好,不然是不会生效的。

因为是前后端分离,不要忘了跨域问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 准许跨域访问
.cors(Customizer.withDefaults())
// 使用表单授权方式登录
.formLogin(form -> {
// 指定自定义登录页的 url,并放行相关 url
form.loginPage("/login").permitAll()
// 设置登录成功处理器
.successHandler(new AuthenticationHandler())
// 设置登录失败处理器
.failureHandler(new AuthenticationHandler());
})
// 登出配置
.logout(logout -> logout.logoutSuccessHandler(new AuthenticationHandler()))
// 登录异常(请求未认证)处理
.exceptionHandling(exception -> exception.authenticationEntryPoint(new AuthenticationHandler()));
// 会话并发处理
.sessionManagement(session -> {
// 最多同时登录的账号数量
session.maximumSessions(1)
.expiredSessionStrategy(new AuthenticationHandler());
});

二、用户认证信息分析

​ 上述登录结果管理器代码中,我们可以调用Authentication中的各项数据,其中的名称貌似跟用户没什么关系,它们实际上是 Spring Security 的一套新的概念。

​ 在 Spring Security 框架中,SecurityContextHolderSecurityContextAuthenticationPrincipalCredential是一些与身份验证和授权相关的重要概念。它们之间的关系如下:

  1. SecurityContextHolderSecurityContextHolder是 Spring Security 存储已认证用户详细信息的地方。
  2. SecurityContextSecurityContext是从SecurityContextHolder获取的内容,包含当前已认证用户的Authentication信息。
  3. AuthenticationAuthentication表示用户的身份认证信息。它包含了用户的PrincipalCredentialAuthority信息。
  4. Principal:表示用户的身份标识。它通常是一个表示用户的实体对象,例如用户名。Principal可以通过Authentication对象的getPrincipal()方法获取。
  5. Credentials:表示用户的凭证信息,例如密码、证书或其他认证凭据。Credential可以通过Authentication对象的getCredentials()方法获取。
  6. GrantedAuthority:表示用户被授予的权限