一、架构剖析
现在绝大多数体系都现已采用 “前后端别离” 架构来规划了,传统的Session形式鉴权也不再合适这种架构(或许需求额外写很多的代码来专门适配)。
Sa-Token 是一个 java 轻量级权限认证结构,专为前后端别离架构打造,首要解决登录认证、权限认证、单点登录、OAuth2、微服务网关鉴权 等一系列权限相关问题。
Gitee 开源地址:gitee.com/dromara/sa-…
本文将介绍在 Springboot 架构下的前后端别离项目,如何运用 Sa-Token 便利的完结登录认证。
首要在项目中引进 Sa-Token 依赖:
<!-- Sa-Token 权限认证 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.34.0</version>
</dependency>
注:假如你运用的是 SpringBoot 3.x
,只需求将 sa-token-spring-boot-starter
修改为 sa-token-spring-boot3-starter
即可。
二、无 Cookie 形式
无 Cookie 形式:特指不支持 Cookie 功能的终端,通俗来讲就是咱们常说的 —— 前后端别离形式。
惯例 Web 端鉴权办法,一般由 Cookie形式
完结,而 Cookie 有两个特性:
- 可由后端控制写入。
- 每次恳求主动提交。
这就使得咱们在前端代码中,无需任何特殊操作,就能完结鉴权的全部流程(因为整个流程都是后端控制完结的)
而在app、小程序等前后端别离场景中,一般是没有 Cookie 这一功能的,此时大多数人都会一脸懵逼,咋进行鉴权啊?
见招拆招,其实答案很简单:
- 不能后端控制写入了,就前端自己写入。(难点在后端如何将 Token 传递到前端)
- 每次恳求不能主动提交了,那就手动提交。(难点在前端如何将 Token 传递到后端,同时后端将其读取出来)
三、后端将 token 回来到前端
- 首要调用
StpUtil.login(id)
进行登录。 - 调用
StpUtil.getTokenInfo()
回来当前会话的 token 具体参数。- 此办法回来一个目标,其有两个要害属性:
tokenName
和tokenValue
(token 的称号和 token 的值)。 - 将此目标传递到前台,让前端人员将这两个值保存到本地。
- 此办法回来一个目标,其有两个要害属性:
代码示例:
// 登录接口
@RequestMapping("doLogin")
public SaResult doLogin() {
// 第1步,先登录上
StpUtil.login(10001);
// 第2步,获取 Token 相关参数
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
// 第3步,回来给前端
return SaResult.data(tokenInfo);
}
四、前端将 token 提交到后端
- 无论是app仍是小程序,其传递办法都大同小异。
- 那就是,将 token 塞到恳求
header
里 ,格式为:{tokenName: tokenValue}
。 - 以经典跨端结构 uni-app 为例:
办法1,简单粗暴
// 1、首要在登录时,将 tokenValue 存储在本地,例如:
uni.setStorageSync('tokenValue', tokenValue);
// 2、在发起ajax恳求的当地,获取这个值,并塞到header里
uni.request({
url: 'https://www.example.com/request', // 仅为示例,并非实在接口地址。
header: {
"content-type": "application/x-www-form-urlencoded",
"satoken": uni.getStorageSync('tokenValue') // 要害代码, 留意参数姓名是 satoken
},
success: (res) => {
console.log(res.data);
}
});
办法2,愈加灵活
// 1、首要在登录时,将tokenName和tokenValue一同存储在本地,例如:
uni.setStorageSync('tokenName', tokenName);
uni.setStorageSync('tokenValue', tokenValue);
// 2、在发起ajax的当地,获取这两个值, 并安排到head里
var tokenName = uni.getStorageSync('tokenName'); // 从本地缓存读取tokenName值
var tokenValue = uni.getStorageSync('tokenValue'); // 从本地缓存读取tokenValue值
var header = {
"content-type": "application/x-www-form-urlencoded"
};
if (tokenName != undefined && tokenName != '') {
header[tokenName] = tokenValue;
}
// 3、后续在发起恳求时将 header 目标塞到恳求头部
uni.request({
url: 'https://www.example.com/request', // 仅为示例,并非实在接口地址。
header: header,
success: (res) => {
console.log(res.data);
}
});
- 只需依照如此办法将
token
值传递到后端,Sa-Token 就能像传统PC端相同主动读取到 token 值,进行鉴权。 - 你可能会有疑问,莫非我每个
ajax
都要写这么一坨?岂不是费事死了?- 你当然不能每个 ajax 都写这么一坨,因为这种重复性代码都是要封装在一个函数里统一调用的。
其它解决方案:
假如你对 Cookie 非常了解,那你就会理解,所谓 Cookie ,本质上就是一个特殊的header
参数罢了,
而既然它仅仅一个 header 参数,咱们就能手动模拟实现它,从而完结鉴权操作。
这其实是对无Cookie形式
的另一种解决方案,有爱好的同学能够百度了解一下,在此暂不赘述。
五、代码比照
为了愈加直观的显示出 前后端一体架构 和 前后端别离架构 的差异,此处再供给一个示例:
package com.pj.cases.up;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.dev33.satoken.util.SaResult;
/**
* Sa-Token 前后端别离形式示例
*
* @author kong
* @since 2022-10-17
*/
@RestController
@RequestMapping("/NotCookie/")
public class NotCookieController {
// 前后端一体形式的登录样例 ---- http://localhost:8081/NotCookie/doLogin?name=zhang&pwd=123456
@RequestMapping("doLogin")
public SaResult doLogin(String name, String pwd) {
if("zhang".equals(name) && "123456".equals(pwd)) {
// 会话登录
StpUtil.login(10001);
return SaResult.ok();
}
return SaResult.error("登录失利");
}
// 前后端别离形式的登录样例 ---- http://localhost:8081/NotCookie/doLogin2?name=zhang&pwd=123456
@RequestMapping("doLogin2")
public SaResult doLogin2(String name, String pwd) {
if("zhang".equals(name) && "123456".equals(pwd)) {
// 会话登录
StpUtil.login(10001);
// 与惯例登录不同点之处:这儿需求把 Token 信息从呼应体中回来到前端
SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
return SaResult.data(tokenInfo);
}
return SaResult.error("登录失利");
}
}
- 接口一:Token 将在 Cookie 上下文回来到前端,并由浏览器每次恳求时主动提交,这种形式合适前后端一体的架构。
- 接口二:Token 将在呼应 body 里回来到前端,并由前端手动存储,并手动在每次恳求时提交,这种形式合适前后端别离的架构。
六、自定义 Token 提交的前缀
在某些体系中,前端提交token时会在前面加个固定的前缀,例如:
{
"satoken": "Bearer xxxx-xxxx-xxxx-xxxx"
}
此时后端假如不做任何特殊处理,结构将会把Bearer
视为token的一部分,无法正常读取token信息,导致鉴权失利。
为此,咱们需求在yml中增加如下装备:
sa-token:
# token前缀
token-prefix: Bearer
此时 Sa-Token 便可在读取 Token 时裁剪掉 Bearer
,成功获取xxxx-xxxx-xxxx-xxxx
。
留意点:
- Token前缀 与 Token值 之间必须有一个空格。
- 一旦装备了 Token前缀,则前端提交
Token
时,必须带有前缀,否则会导致结构无法读取 Token。 - 因为
Cookie
中无法存储空格字符,也就意味装备 Token 前缀后,Cookie 鉴权办法将会失效,此时只能将 Token 提交到header
里进行传输。
七、自定义 Token 风格
Sa-Token默许的token生成战略是uuid风格,其容貌类似于:623368f0-ae5e-4475-a53f-93e4225f16ae
。
假如你对这种风格不太伤风,还能够将token生成设置为其他风格。
怎样设置呢?只需求在yml装备文件里设置 sa-token.token-style=风格类型
即可,其有多种取值:
// 1. token-style=uuid —— uuid风格 (默许风格)
"623368f0-ae5e-4475-a53f-93e4225f16ae"
// 2. token-style=simple-uuid —— 同上,uuid风格, 只不过去掉了中划线
"6fd4221395024b5f87edd34bc3258ee8"
// 3. token-style=random-32 —— 随机32位字符串
"qEjyPsEA1Bkc9dr8YP6okFr5umCZNR6W"
// 4. token-style=random-64 —— 随机64位字符串
"v4ueNLEpPwMtmOPMBtOOeIQsvP8z9gkMgIVibTUVjkrNrlfra5CGwQkViDjO8jcc"
// 5. token-style=random-128 —— 随机128位字符串
"nojYPmcEtrFEaN0Otpssa8I8jpk8FO53UcMZkCP9qyoHaDbKS6dxoRPky9c6QlftQ0pdzxRGXsKZmUSrPeZBOD6kJFfmfgiRyUmYWcj4WU4SSP2ilakWN1HYnIuX0Olj"
// 6. token-style=tik —— tik风格
"gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__"
八、自定义 Token 生成战略
假如你觉着以上风格都不是你喜爱的类型,那么你还能够自定义token生成战略,来定制化token生成风格。
怎样做呢?只需求重写 SaStrategy
战略类的 createToken
算法即可:
参考步骤如下:
1、在SaTokenConfigure
装备类中增加代码:
@Configuration
public class SaTokenConfigure {
/**
* 重写 Sa-Token 结构内部算法战略
*/
@Autowired
public void rewriteSaStrategy() {
// 重写 Token 生成战略
SaStrategy.me.createToken = (loginId, loginType) -> {
return SaFoxUtil.getRandomString(60); // 随机60位长度字符串
};
}
}
2、再次调用 StpUtil.login(10001)
办法进行登录,观察其生成的token样式:
gfuPSwZsnUhwgz08GTCH4wOgasWtc3odP4HLwXJ7NDGOximTvT4OlW19zeLH
参考资料
- Sa-Token 文档:sa-token.cc
- Gitee 仓库地址:gitee.com/dromara/sa-…
- GitHub 仓库地址:github.com/dromara/sa-…