SpringBoot+vue前后端分离整合sa-token(无cookie登录态 & 详细的登录流程)

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前端

我们来进行前端的书写,前端的逻辑最主要的就是两点。

  1. 在点击登录按钮的时候,调用后端书写的接口,我们要保存返回的token值到本地。
  2. 我们每次调用请求的时候都在请求头上携带上这个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
------🥰本人技术有限,如果有不对指正需要更改或者有更好的方法,欢迎到评论区留言。
相关推荐
陈文锦丫4 小时前
MQ的学习
java·开发语言
乌暮4 小时前
JavaEE初阶---线程安全问题
java·java-ee
爱笑的眼睛114 小时前
GraphQL:从数据查询到应用架构的范式演进
java·人工智能·python·ai
Seven974 小时前
剑指offer-52、正则表达式匹配
java
代码or搬砖5 小时前
RBAC(权限认证)小例子
java·数据库·spring boot
青蛙大侠公主5 小时前
Thread及其相关类
java·开发语言
Coder_Boy_5 小时前
DDD从0到企业级:迭代式学习 (共17章)之 四
java·人工智能·驱动开发·学习
2301_768350235 小时前
MySQL为什么选择InnoDB作为存储引擎
java·数据库·mysql
派大鑫wink5 小时前
【Java 学习日记】开篇:以日记为舟,渡 Java 进阶之海
java·笔记·程序人生·学习方法