SpringBoot+vue前后端分离整合sa-token(无cookie登录态 详细的登录流程)
SpringBoot+vue前后端分离整合sa-token(无cookie登录态 & 详细的登录流程)
- 1.介绍sa-token
- 1.1 框架定位
- 1.2 核心优势
- 2.如何整合sa-token
- 3.如何进行无cookie模式登录
- 3.1后端
- 3.1.1 VO层
- 3.1.2 Controller层
- 3.1.3 Service层
- 3.2前端
- 3.2.1 登录按钮
- 自定义axios
- 4.验证
- 4.1验证前的最后准备
- 4.2 验证结果
- 5.结语
😀大家好!我是向阳🌞,一个想成为优秀全栈开发工程师的有志青年!
📔今天来说一说如何来编写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中引入依赖
<!-- 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版本请引入
<!-- 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文件(这只是官方给的默认的配置,后面咱们需要修改)
############## 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模式登录
首先我们要对官方文档给出的一些方法进行了解。
// 会话登录:参数填写要登录的账号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登录态,我们先到配置文件中修改一下我们的配置。
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,都是可以的,看你自己的需求)
@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类返回。
/*** 用户登录** @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进去,代码我也给大家贴了。
@Overridepublic 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);}@Overridepublic 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,并存到本地。
/*** 登录按钮* @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.tokenValuelocalStorage.setItem('token', tokenValue)await loginUserStore.fetchLoginUser()message.success('登录成功')router.push({path: '/',replace: true,})} else {message.error('登录失败,' + res.data.message)}
}
自定义axios
主要介绍在请求拦截器中的代码。
逻辑分析以及一些提示:
- 核心点就是在每次发起请求前,请求头当中携带上我们保存的token值。
// 创建 Axios 实例
const myAxios = axios.create({baseURL: 写你的后端请求地址,timeout: 60000,withCredentials: false, // 不使用cookie来存储登录态,所以关闭
})
// 全局请求拦截器
myAxios.interceptors.request.use(function (config) {// Do something before request is sentconst tokenValue = localStorage.getItem('token')config.headers['token'] = tokenValuereturn config},function (error) {// Do something with request errorreturn Promise.reject(error)},
)
4.验证
4.1验证前的最后准备
到这里,我们就已经做完全部工作了,我们通过前端登录来进行验证,当然我们还需要补充一个验证是否登录成功的方法,这里我们就使用 isLogin 方法来进行验证,我们通过Knife4j来调用这个方法验证是否成功登录。(有感兴趣的同学可以了解博博主的另另另一篇文章:SpringBoot整合Knife4j)。
@Overridepublic UserVO getLoginUser(HttpServletRequest request) {// 直接通过 Sa-Token 获取登录IDif (!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
——⏳[更新]:2025.4.3
——🥰本人技术有限,如果有不对指正需要更改或者有更好的方法,欢迎到评论区留言。