Sa-Token(二)实现登录注销操作
目录
一、简介与配置
二、实现登录注销操作 <— 你在这里 ( •̀ ω •́ )y
三、权限认证构成与鉴权方法
四、RBAC 结构实现
Sa-Token(二)实现登录注销操作
[!NOTE]
博主前言:Spring Security 的学习真是惊掉我下巴,就一个鉴权功能,搞那么多复杂概念,又是过滤器链又是注入的,花了我一个星期左右,实际上还很不好用(未知错误给前端返回 401 是真绷不住)。Sa-Token 作为国产鉴权,性能更强的同时简化开发的程度相比 Spring Security 起码有十倍不止。最近我还注意到对标 Spring 生态的 Solon 框架,也是国产,性能提升3倍多,内存占用减少 50%+,打包还更小,真不知道这些工作为什么老外都做的这么复杂。
经过刚才的配置,我们已经对 Sa-Token 有了初步的了解,接下来要实现接入数据库的,真正的登录注销操作。
一,理解概念与分析需求
Cookie
与前后端分离常规 Web 端鉴权方法,一般由
Cookie
完成,而Cookie
有两个特性:- 可由后端控制写入。
- 每次请求自动提交。
这就使得我们在前端代码中,无需任何特殊操作,就能完成鉴权的全部流程(因为整个流程都是后端控制完成的)
我们在上一章初步实现登录时,发现没有显式返回
token
。实际上,StpUtil.login(id)
方法利用了Cookie
自动注入的特性,省略了你手写返回token
的代码。但是,对于前后端分离框架(如
APP
和小程序),后端无法控制Cookie
的写入,我们就需要显式返回token
,前端存储在本地,每次请求时,封装到header
内,供后端校验。分析需求
- 登录:
- 传递:账号与密码,供后端校验
- 返回:该用户的数据(不含密码)和
token
- 登出:
- 传递:前端只发起请求,后端要根据当前会话执行登出
- 返回:无,可以返回一些登出成功回调信息
另外,我们还需要配置单向密码加密,不能明文存储密码,也不能明文比对密码。
- 登录:
二、准备数据模型
[!IMPORTANT]
本文基于 Mybatis Plus 处理数据层逻辑,构建
Service
层和Mapper
层的代码这里不再赘述。
我们需要自定义一个用户类:
列名 | 数据类型 | 描述 |
---|---|---|
id(主键) | bigint | 用户主键 |
account | varchar(32) | 账号 |
password | varchar(64) | 密码 |
建表语句:
[!NOTE]
下述命令将创建用户
admin
,密码明文为 password
1 | -- ---------------------------- |
对应数据模型类代码:
1 | /** |
定义用户登录回调UserLoginVO
:
1 | /** |
之后,准备登录Controller
及相关接口:
[!TIP]
这里的返回结构为我自定,读者亦可自定
Sa-Token 亦提供了一套返回封装
SaResult
,读者可自行尝试代码参考如下:
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 /**
* 返回结果封装体
*
* @param <T> 数据体类型
* @author Amane64
*/
public class Result<T> {
/**
* 状态码
*/
private int code;
/**
* 状态信息
*/
private String message;
/**
* 请求时间
*/
private LocalDateTime requestTime;
/**
* 数据体
*/
private T data;
public Result() {
this.requestTime = LocalDateTime.now();
}
/**
* 成功回调
*
* @param data 数据体
* @param <T> 数据体类型
* @return 返回结果
*/
public static <T> Result<T> success(T data) {
var res = new Result<T>();
res.code = RequestEnum.SUCCESS.getCode();
res.message = RequestEnum.SUCCESS.getMessage();
res.data = data;
return res;
}
/**
* 成功回调(空回调)
*
* @return 返回结果
*/
public static Result<?> success() {
return Result.success(null);
}
/**
* 错误回调
*
* @param code 状态码
* @param message 状态信息
* @return 返回结果
*/
public static Result<?> error(int code, String message) {
var res = new Result<>();
res.code = code;
res.message = message;
return res;
}
}
1 | /** |
三、逻辑实现
首先,定义服务层:
1 | public interface UserService extends IService<User> { |
实现登录逻辑:
[!TIP]
LocalStringUtils
为我个人定义的字符串工具,检验一个字符串是否为null
、为空或全为空格,读者可自定义或使用其它现成的包。
BaseRequestException(RequestEnum.REQUEST_EMPTY)
为我个人定义的异常,旨在参数为空时抛出,之后自动向前端返回HTTP 400
的欲封装回调,读者亦可自定义全局异常处理,参考这篇文章:Spring Boot 全局异常拦截配置
BCrypt.checkpw(raw, hashed)
为 Sa-Token 封装的BCrypt
加密验证工具,关于BCrypt
算法,参考:Spring Security(三)基础自定义配置
1 |
|
实现登出逻辑:
1 |
|
全局异常拦截里,处理未登录异常返回:
1 | // 返回 http 401 状态码 |
四、验证
- 前后端不分离,
Cookie
方式
执行登录:
手动封装Cookie
:
登出,可以看到能根据Cookie
正确识别当前用户:
- 前后端分离,
Header
传递token
手动清空Cookie
:
执行登录:
封装Header
:
登出,可以看到能根据Header
正确识别当前用户: