1. 技术选型
最近在做登录、授权的功能,一开始考虑到的是spring boot + spring security,但spring security太重,而我们是轻量级的项目,所以,spring security不适合我们。
而后考虑spring boot + shiro,但shiro自带的aop会影响spring boot的aop,所以,shiro也不适合我们。
后来浏览github时,发现Sa-Token这个框架,如下是Sa-Token的功能图:
2. Sa-Token概述
2.1 简单介绍
Sa-Token是一个轻量级Java权限认证框架。
主要解决的问题如下:
-
登录认证
-
权限认证
-
单点登录
-
OAuth2.0
-
分布式Session会话
-
微服务网关鉴权等一系列权限相关问题。
2.2 登录认证
设计思路
对于一些登录之后才能访问的接口(例如:查询我的账号资料),我们通常的做法是增加一层接口校验:
-
如果校验通过,则:正常返回数据。
-
如果校验未通过,则:抛出异常,告知其需要先进行登录。
那么,判断会话是否登录的依据是什么?我们先来简单分析一下登录访问流程:
-
用户提交name + password参数,调用登录接口。
-
登录成功,返回这个用户的Token会话凭证。
-
用户后续的每次请求,都携带上这个Token。
-
服务器根据Token判断此会话是否登录成功。
所谓登录认证,指的就是服务器校验账号密码,为用户颁发Token会话凭证的过程,这个Token也是我们后续通过接口校验的关键所在。
登录与注销
根据以上思路,我们需要一个会话登录的函数:
// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等
StpUtil.login(Object id);
只此一句代码,便可以使会话登录成功,实际上Sa-Token在背后做了大量的工作,包括但不限于:
-
检查此账号是否已被封禁
-
检查此账号是否之前已有登录
-
为账号生成 Token 凭证与 Session 会话
-
通知全局侦听器,xx 账号登录成功
-
将 Token 注入到请求上下文
-
等等其它工作……
你暂时不需要完整的了解整个登录过程,你只需要记住关键一点:Sa-Token 为这个账号创建了一个Token凭证,且通过Cookie 上下文返回给了前端。
所以一般情况下,我们的登录接口代码,会大致类似如下:
// 会话登录接口
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
// 第一步:比对前端提交的账号名称、密码
if("zhang".equals(name) && "123456".equals(pwd)) {
// 第二步:根据账号id,进行登录
StpUtil.login(10001);
return SaResult.ok("登录成功");
}
return SaResult.error("登录失败");
}
如果你对以上代码阅读没有压力,你可能会注意到略显奇怪的一点:此处仅仅做了会话登录,但并没有主动向前端返回Token信息。是因为不需要吗?严格来讲是需要的,只不过StpUtil.login(id)
方法利用了Cookie自动注入的特性,省略了你手写返回Token的代码。
你需要了解Cookie最基本的两点:
-
Cookie可以从后端控制往浏览器中写入Token值。
-
Cookie会在每次请求时自动提交Token值。
因此,在Cookie功能的加持下,我们可以仅靠StpUtil.login(id)
一句代码就完成登录认证。
除了登录方法,我们还需要:
// 当前会话注销登录
StpUtil.logout();
// 获取当前会话是否已经登录,返回true=已登录,false=未登录
StpUtil.isLogin();
// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.checkLogin();
异常NotLoginException代表当前会话暂未登录,可能的原因有很多:
-
前端没有提交 Token
-
前端提交的 Token 是无效的
-
前端提交的 Token 已经过期
-
…… 等等
会话查询
// 获取当前会话账号id, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.getLoginId();
// 类似查询API还有:
StpUtil.getLoginIdAsString(); // 获取当前会话账号id, 并转化为`String`类型
StpUtil.getLoginIdAsInt(); // 获取当前会话账号id, 并转化为`int`类型
StpUtil.getLoginIdAsLong(); // 获取当前会话账号id, 并转化为`long`类型
// ---------- 指定未登录情形下返回的默认值 ----------
// 获取当前会话账号id, 如果未登录,则返回null
StpUtil.getLoginIdDefaultNull();
// 获取当前会话账号id, 如果未登录,则返回默认值 (`defaultValue`可以为任意类型)
StpUtil.getLoginId(T defaultValue);
Token查询
// 获取当前会话的token值
StpUtil.getTokenValue();
// 获取当前`StpLogic`的token名称
StpUtil.getTokenName();
// 获取指定token对应的账号id,如果未登录,则返回 null
StpUtil.getLoginIdByToken(String tokenValue);
// 获取当前会话剩余有效期(单位:s,返回-1代表永久有效)
StpUtil.getTokenTimeout();
// 获取当前会话的token信息参数
StpUtil.getTokenInfo();
有关TokenInfo参数详解,如下代码所示:
{
"code": 200,
"msg": "ok",
"data": {
"tokenName": "satoken", // token名称
"tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633", // token值
"isLogin": true, // 此token是否已经登录
"loginId": "10001", // 此token对应的LoginId,未登录时为null
"loginType": "login", // 账号类型标识
"tokenTimeout": 2591977, // token剩余有效期 (单位: 秒)
&#