SpringBoot+vue前后端分离整合sa-token(无cookie登录态 & 详细的登录流程)
-
- [1.1 框架定位](#1.1 框架定位)
- [1.2 核心优势](#1.2 核心优势)
-
- 4.1验证前的最后准备
- [4.2 验证结果](#4.2 验证结果)
-
😀大家好!我是向阳🌞,一个想成为优秀全栈开发工程师的有志青年!
📔今天来说一说如何来编写SpringBoot+vue前后端分离整合sa-token(无cookie登录态)。
1.介绍sa-token

https://sa-token.cc/doc.html#/
1.1 框架定位
Sa-Token 是一款面向Java开发者的轻量级权限认证框架,专为现代化应用设计。其核心价值在于:
- 🚀 极简API设计:5分钟快速接入,10行代码完成基础权限控制
- 🛡️ 全场景支持:覆盖会话管理、权限认证、单点登录、OAuth2.0等常见安全场景
- 🌐 跨端适配:完美支持前后端分离架构,原生适配APP、小程序等非Web环境
- 📦 轻量无依赖:核心包仅700KB,无需额外依赖,拒绝臃肿
1.2 核心优势
相较于Spring Security、Shiro等传统方案,Sa-Token具有以下突破性优势:
| 特性 | Sa-Token | 传统方案 |
|---|---|---|
| 学习成本 | ★★☆☆☆ | ★★★★★(陡峭) |
| RESTful支持 | 原生适配 | 需要复杂配置 |
| 前后端分离支持 | 开箱即用 | 需自定义解决方案 |
| 注解鉴权 | 声明式注解 | 需编写拦截器逻辑 |
| 分布式会话 | 一行代码 | 依赖外部存储 |
还有一些创新特性,我简单列举几点,剩下的大家可以到官方文档(链接:Sa-Token)中看:
- 无Cookie会话管理:原生支持Token认证模式,完美适配APP、小程序等移动端场景
- 动态权限刷新:权限修改实时生效,无需用户重新登录
- 踢人下线:精准控制账号登录状态,支持根据设备维度下线
- 临时令牌:支持临时Token颁发,适用于第三方授权等场景
- 二级认证:敏感操作时进行二次验证,提升系统安全性
- ...
2.如何整合sa-token
本项目是SpringBoot3.x版本,如果您是2.x版本请注意版本问题。
1.首先在maven中引入依赖
xml
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
<version>1.41.0</version>
</dependency>
如果你是spring boot2.x版本请引入
xml
<!-- Sa-Token 权限认证,在线文档:https://sa-token.cc -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot-starter</artifactId>
<version>1.41.0</version>
</dependency>
2.然后配置对应的yml文件(这只是官方给的默认的配置,后面咱们需要修改)
yml
############## Sa-Token 配置 (文档: https://sa-token.cc) ##############
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: satoken
# token 有效期(单位:秒) 默认30天,-1 代表永久有效
timeout: 2592000
# token 最低活跃频率(单位:秒),如果 token 超过此时间没有访问系统就会被冻结,默认-1 代表不限制,永不冻结
active-timeout: -1
# 是否允许同一账号多地同时登录 (为 true 时允许一起登录, 为 false 时新登录挤掉旧登录)
is-concurrent: true
# 在多人登录同一账号时,是否共用一个 token (为 true 时所有登录共用一个 token, 为 false 时每次登录新建一个 token)
is-share: false
# token 风格(默认可取值:uuid、simple-uuid、random-32、random-64、random-128、tik)
token-style: uuid
# 是否输出操作日志
is-log: true
3.如何进行无cookie模式登录
首先我们要对官方文档给出的一些方法进行了解。
java
// 会话登录:参数填写要登录的账号id,建议的数据类型:long | int | String, 不可以传入复杂类型,如:User、Admin 等等
StpUtil.login(Object id);
// 当前会话注销登录
StpUtil.logout();
// 获取当前会话是否已经登录,返回true=已登录,false=未登录
StpUtil.isLogin();
// 检验当前会话是否已经登录, 如果未登录,则抛出异常:`NotLoginException`
StpUtil.checkLogin();
我们暂且了解这么多方法就够了,我们只要知道,通过login方法可以给我们返回一个token来作为唯一凭证进行登录,返回的token格式可以在yml配置文件中配置,官方文档默认设置的是uuid,后面我会来带大家集成 jwt,让我们的token更加安全。
3.1后端
我们导入以来后,接下来我们来进行编写我们的后端代码。首先我们要做的是无cookie登录态,我们先到配置文件中修改一下我们的配置。
yml
sa-token:
# token 名称(同时也是 cookie 名称)
token-name: token
# 其他配置默认不变
......
# 添加下面两个配置,关闭cookie读取,通过请求头读取
# 关闭 Cookie 读取
is-read-cookie: false
# 开启 Header 读取
is-read-header: true
3.1.1 VO层
接下来,我们要在登录的接口中要返回StpUtil.login方法给出的token值,我们要在返回的UserVO对象中添加SaTokenInfo对象,这个SaTokenInfo对象里面就是这个token值的一些信息(或者你可以添加一个字符串tokenValue,都是可以的,看你自己的需求)
java
@Data
public class UserVO implements Serializable {
/**
* id
*/
private Long id;
/**
* 用户昵称
*/
private String userName;
/**
* 账号
*/
private String userAccount;
/**
* 访问令牌
*/
private SaTokenInfo saTokenInfo;
private static final long serialVersionUID = 1L;
}
3.1.2 Controller层
一些提示:
- UserLoginRequest只是一个封装类,里面就两个参数,一个账号一个密码。
- ThrowUtils.throwIf是博主自己封装的一个方法,用来抛出异常类,你直接Throw一个异常类就可以。当然这里抛出的全局异常,感兴趣的话可以看博主的这边文章(SpringBoot如何配置全局异常处理器)
- 返回的BaseResponse是统一了响应体返回,感兴趣也是看上面那个链接(SpringBoot如何配置全局异常处理器),你也可以直接返回UserVO,但是报错了之后就没有那么美观了。
- 返回的ResultUils.success也是一个工具类,实际上就是new了一个BaseResponse类返回。
java
/**
* 用户登录
*
* @param userLoginRequest
* @param request
* @return
*/
@PostMapping("/login")
public BaseResponse<UserVO> userLogin(@RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request) {
ThrowUtils.throwIf(userLoginRequest == null, ErrorCode.PARAMS_ERROR);
String userAccount = userLoginRequest.getUserAccount();
String userPassword = userLoginRequest.getUserPassword();
if (StringUtils.isAnyBlank(userAccount, userPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR);
}
UserVO userVO = userService.userLogin(userAccount, userPassword, request);
return ResultUtils.success(userVO);
}
3.1.3 Service层
接下来我们进入到userLogin方法看看发生了什么。
流程分析以及一些提示:
- 首先就是校验参数是否正确,不正确则抛出异常。
- 因为存储在数据库中的密码肯定是加密过的嘛,所以我们要先对传过来的密码进行加密,然后进行对比。
- 查询用户如果存在,则调用StpUtil.login(user.getId())进行登录,否则报错。注意,我们调用login方法的时候参数要传一个唯一值,这样每个用户才会拥有自己独有的token。假如你拿性别作为条件传进去,那么一半一半的用户使用的都是同一个token值,那岂不是很恐怖了😥。
- 最后的返回值调用了 getUserVOInfo 方法,这个方法就是将User类转为UserVO类,并且把产生的 tokeninfo set进去,代码我也给大家贴了。
java
@Override
public UserVO userLogin(String userAccount, String userPassword, HttpServletRequest request) {
// 1. 校验
if (StringUtils.isAnyBlank(userAccount, userPassword)) {
throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
}
ThrowUtils.throwIf(userAccount.length() < 4, ErrorCode.PARAMS_ERROR, "账号错误");
ThrowUtils.throwIf(userPassword.length() < 8, ErrorCode.PARAMS_ERROR, "密码错误");
// 2. 加密
String encryptPassword = DigestUtils.md5DigestAsHex((SALT + userPassword).getBytes());
// 查询用户是否存在
QueryWrapper<User> queryWrapper = new QueryWrapper();
queryWrapper.eq("userAccount", userAccount);
queryWrapper.eq("userPassword", encryptPassword);
User user = userMapper.selectOne(queryWrapper);
// 用户不存在
ThrowUtils.throwIf(user == null, ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
// 3. 用户登录
try {
StpUtil.login(user.getId());
} catch (Exception e) {
throw new BusinessException(ErrorCode.SYSTEM_ERROR, "网络超时");
}
// 将token 信息返回给前端
return getUserVOInfo(user);
}
@Override
public UserVO getUserVOInfo(User user) {
UserVO userVO = new UserVO();
BeanUtils.copyProperties(user, userVO);
userVO.setSaTokenInfo(StpUtil.getTokenInfo());
return userVO;
}
3.2前端
我们来进行前端的书写,前端的逻辑最主要的就是两点。
- 在点击登录按钮的时候,调用后端书写的接口,我们要保存返回的token值到本地。
- 我们每次调用请求的时候都在请求头上携带上这个token值,这样后端就能认出我们是谁了。
话不多说,我们直接开干。
3.2.1 登录按钮
逻辑分析以及一些提示:
- 我们在点击按钮后,调用后端登录的接口,这里的方法都是封装好的,因为是通过一个插件生成的这些方法,感兴趣的同学了解一下这篇文章(一键生成后端的请求接口)
- 调用成功后,我们获取到返回值中的token,并存到本地。
ts
/**
* 登录按钮
* @returns {Promise<void>}
*/
const handleSubmit = async () => {
const res = await userLogin(form)
if (res.data.code === 0 && res.data.data) {
const tokenValue = res.data.data.saTokenInfo.tokenValue
localStorage.setItem('token', tokenValue)
await loginUserStore.fetchLoginUser()
message.success('登录成功')
router.push({
path: '/',
replace: true,
})
} else {
message.error('登录失败,' + res.data.message)
}
}
自定义axios
主要介绍在请求拦截器中的代码。
逻辑分析以及一些提示:
- 核心点就是在每次发起请求前,请求头当中携带上我们保存的token值。
ts
// 创建 Axios 实例
const myAxios = axios.create({
baseURL: 写你的后端请求地址,
timeout: 60000,
withCredentials: false, // 不使用cookie来存储登录态,所以关闭
})
// 全局请求拦截器
myAxios.interceptors.request.use(
function (config) {
// Do something before request is sent
const tokenValue = localStorage.getItem('token')
config.headers['token'] = tokenValue
return config
},
function (error) {
// Do something with request error
return Promise.reject(error)
},
)
4.验证
4.1验证前的最后准备
到这里,我们就已经做完全部工作了,我们通过前端登录来进行验证,当然我们还需要补充一个验证是否登录成功的方法,这里我们就使用 isLogin 方法来进行验证,我们通过Knife4j来调用这个方法验证是否成功登录。(有感兴趣的同学可以了解博博主的另另另一篇文章:SpringBoot整合Knife4j)。
java
@Override
public UserVO getLoginUser(HttpServletRequest request) {
// 直接通过 Sa-Token 获取登录ID
if (!StpUtil.isLogin()) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
}
// 从数据库查询 下面的代码可以忽略掉...
Long userId = StpUtil.getLoginIdAsLong();
User user = this.getById(userId);
if (user == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
}
return this.getUserVOInfo(user);
}
4.2 验证结果
我们登录后可以看到响应体的返回值中携带了token的信息,其中tokenValue就是我们需要的token值。这里博主的tokenValue复杂是因为博主整合了jwt,下一篇文章来带大家整合!

并且到浏览器本地查看是否存储了token值,可以看到是缓存到浏览器里面的,并且没有携带cookie。


在调用登录后,我们进入到主页时,我调用了获取用户登录状态的信息,发现也是调用成功,至此,我们的登录流程已经完满结束。


5.结语
本章让大家了解并认识了sa-token这个框架,这个使用起来简单上手,使用起来也很便捷,接下来我会持续更新这个框架的一些东西,有感兴趣的小伙伴可以持续关注博主的博客。
(预告)在下篇章节中,我们将介绍JWT ,整合JWT 来生成更安全的token值。还有一个问题,我们在每次重启项目后我们就需要重新登录,我们该如何解决这个问题呢?接下来我会带大家一一解决。

------👦[作者]:向阳256
------⏳[更新]:2024.4.3
------🥰本人技术有限,如果有不对指正需要更改或者有更好的方法,欢迎到评论区留言。