news 2026/9/13 16:38:04

Sa-Token 前后端分离鉴权实战:无 Cookie 模式下 Token 的下发、存储与提交

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Sa-Token 前后端分离鉴权实战:无 Cookie 模式下 Token 的下发、存储与提交

Sa-Token 前后端分离鉴权实战:无 Cookie 模式下 Token 的下发、存储与提交

【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token

在 App、小程序、以及前后端分离的 Web 场景中,终端往往不支持 Cookie 或不便依赖 Cookie 自动携带凭证,这使得传统"后端写 Cookie、浏览器自动带"的鉴权方式失效。本文以 Sa-Token 官方文档 前后端分离(无Cookie模式) 为骨架,结合核心源码与官方示例,完整讲解后端如何把 Token 下发到前端、前端如何存储与提交 Token、后端如何再次读取 Token的整套闭环,并给出可直接落地的 uni-app 请求封装方案。读完本文,你将能在不依赖 Cookie 的任意终端(App、小程序、分离式 Web、Hybrid)上完整跑通 Sa-Token 的登录与鉴权流程。

一、何为"无 Cookie 模式"?

无 Cookie 模式,特指不支持 Cookie 功能的终端场景,通俗来讲就是我们常说的前后端分离模式

常规 Web 端鉴权,一般由Cookie 模式完成。Cookie 有两个关键特性:

  1. 可由后端控制写入:后端在响应中下发Set-Cookie,浏览器自动保存;
  2. 每次请求自动提交:浏览器在同域请求中自动携带 Cookie,前端无需任何代码参与。

正是这两个特性,使得传统 PC 端在前端零代码介入的情况下,就能完成鉴权的全部流程(整个流程均由后端控制)。

而在 App、小程序等前后端分离场景中,一般没有 Cookie 这一功能。此时如何鉴权?见招拆招,答案其实很简单,只需要把 Cookie 的两个特性"手动化":

Cookie 特性分离模式下对应的解法核心难点
后端控制写入前端自己写入(存储到本地)后端如何将 Token 传递到前端
请求自动提交前端手动提交(塞进 Header)前端如何提交 Token,以及后端如何将其读取出来

整个无 Cookie 模式的实现,就是围绕上面两个难点展开的。

二、后端将 Token 返回到前端

2.1 两步核心操作

  1. 首先调用StpUtil.login(id)完成登录;
  2. 调用StpUtil.getTokenInfo()获取当前会话的 Token 详细参数。

getTokenInfo()返回一个SaTokenInfo对象,其中有两个关键属性:tokenNametokenValue(即Token 的名称Token 的值)。将此对象返回给前端,让前端把这两个值保存到本地即可。

2.2 登录接口代码示例

// 登录接口 @RequestMapping("doLogin") public SaResult doLogin() { // 第1步,先登录上 StpUtil.login(10001); // 第2步,获取 Token 相关参数 SaTokenInfo tokenInfo = StpUtil.getTokenInfo(); // 第3步,返回给前端 return SaResult.data(tokenInfo); }

仓库中的官方示例 NotCookieController.java 给出了更完整的对比写法:doLogin是前后端一体模式(登录后仅返回SaResult.ok(),Token 由后端写入 Cookie),而doLogin2是前后端分离模式——登录后通过SaResult.data(tokenInfo)SaTokenInfo整体塞进响应体返回给前端。两者的差别,正是是否主动下发 Token 信息。

2.3 SaTokenInfo 对象各字段详解

SaTokenInfo定义于 SaTokenInfo.java,它描述了一个 Token 的全部常见参数。以源码中的 Javadoc 示例为参照,一次典型的返回结构如下:

{ "tokenName": "satoken", "tokenValue": "e67b99f1-3d7a-4a8d-bb2f-e888a0805633", "isLogin": true, "loginId": "10001", "loginType": "login", "tokenTimeout": 2591977, "sessionTimeout": 2591977, "tokenSessionTimeout": -2, "tokenActiveTimeout": -1, "loginDeviceType": "DEF" }

各字段含义(依据源码字段定义与注释):

字段含义说明
tokenNameToken 名称即全局配置项token-name,默认值为satoken
tokenValueToken 值当前会话的 Token 值
isLogin此 Token 是否已登录true/false
loginId此 Token 对应的账号 ID未登录时为null
loginType账号类型标识多账号体系下区分不同账号体系,默认login
tokenTimeoutToken 剩余有效期(秒)-1代表永久有效,-2代表值不存在
sessionTimeoutAccount-Session 剩余有效时间(秒)同上约定
tokenSessionTimeoutToken-Session 剩余有效时间(秒)-2表示系统中不存在这个缓存
tokenActiveTimeoutToken 距离被冻结还剩多少时间(秒)即无操作冻结机制的剩余活跃时间
loginDeviceType登录设备类型默认DEF,可配合"同端互斥登录"使用

提示:前端真正必需保存的只有tokenNametokenValue两个字段,其余字段可用于展示登录状态、剩余有效期等信息。

三、前端将 Token 提交到后端

无论是 App 还是小程序,Token 的传递方式都大同小异:将 Token 塞到请求的Header里,格式为{tokenName: tokenValue}

以经典跨端框架uni-app为例,官方文档提供了两种实现方式。

3.1 方式一:简单粗暴(只存 tokenValue)

tokenValue存到本地,发起请求时硬编码 header 参数名(注意此处参数名是satoken,与默认tokenName保持一致):

// 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); } });

3.2 方式二:更加灵活(tokenName 与 tokenValue 一起存)

tokenNametokenValue都存入本地,发起请求时动态组装 header,header 参数名完全跟随后端配置,更通用、更不易出错:

// 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 都写一遍,这种重复性代码应当封装在一个统一的请求函数里,例如封装request(options)工具函数,在函数内部统一注入 header,业务页面只关心业务参数即可。

四、后端如何读取前端提交的 Token:源码级解读

前端提交 Token 后,Sa-Token 是怎么把它"找"出来的?这决定了前端的提交方式必须与后端的读取逻辑严格对齐。

4.1 Token 的读取顺序

从源码 StpLogic.java 的getTokenValueNotCut()方法可以看到,后端读取 Token 时按以下顺序依次尝试:

  1. 先尝试从 Storage 存储器里读取:即本次请求中登录动作刚刚创建、暂存在请求级缓存里的 Token(解决"登录接口立即取 Token"的场景);
  2. 再尝试从请求体 / URL 参数里读取:当配置项isReadBody开启时,调用request.getParam(tokenName)从 Query 参数与表单体读取(例如GET /user/getInfo?satoken=xxx);
  3. 再尝试从 Header 头里读取:当配置项isReadHeader开启时,调用request.getHeader(tokenName)读取,即本文第三章前端所采用的方式;
  4. 最后尝试从 Cookie 里读取:当配置项isReadCookie开启时,调用request.getCookieValue(tokenName)读取(兼容传统 PC 端场景)。

只要在前面的步骤中读到了值,就不再继续向后尝试。官方示例 LoginAuthController.java 中的注释也印证了这一读取顺序:Query 参数 → Header 头 → Cookie,且明确说明"以上三个地方都读取不到 Token 信息的话,则视为前端没有提交 Token"。

4.2 关键配置项

上述读取行为由全局配置控制,配置项定义于 SaTokenConfig.java:

配置项默认值含义
token-namesatokenToken 名称,同时也是 Cookie 名称、提交 Token 时参数的名称、存储 Token 时的 key 前缀
is-read-bodytrue是否尝试从请求体里读取 Token
is-read-headertrue是否尝试从 header 里读取 Token
is-read-cookietrue是否尝试从 cookie 里读取 Token

因此前端 Header 中的参数名必须与token-name保持一致(默认satoken),后端才能读得到。这也是第三章"方式二"比"方式一"更推荐的原因:它从后端返回的tokenName动态取值,后端一旦修改token-name配置,前端无需改动代码。

4.3 响应头方式:另一种"后端下发"的变体

除了"登录接口返回SaTokenInfo"这种最常见的下发方式,源码还提供了setTokenValueToResponseHeader()方法(见 StpLogic.java):登录后将 Token 写入当前请求的响应头中,同时会自动添加Access-Control-Expose-Headers: tokenName响应头,否则跨域场景下前端 JS 无法读取到该自定义响应头。这种方式适合登录接口不方便改返回结构、希望通过响应头透传 Token 的团队,可结合项目实际情况选用。

五、其它解决方案:手动模拟 Cookie

如果你对 Cookie 非常了解,就会明白一个本质:所谓 Cookie,本质上就是一个特殊的 Header 参数而已(即Cookie: name=value)。

既然它只是一个 Header 参数,我们就能手动模拟实现它:前端自行维护一个"类 Cookie"机制(如把 Token 存入本地存储,并在每次请求时以Cookie头或自定义头的方式带上),从而在不依赖浏览器 Cookie 的前提下完成鉴权闭环。这其实是"无 Cookie 模式"的另一种通用解法,其思路与第三章的 Header 提交方案殊途同归,感兴趣的同学可以进一步研究 Cookie 的规范细节,在此不再赘述。

六、小结与更多延伸

至此,无 Cookie 模式下的完整闭环已经清晰:

  • 下发:登录后调用StpUtil.getTokenInfo(),把tokenName+tokenValue返回给前端保存;
  • 存储:前端将这两个值存入本地存储(如 uni-app 的setStorageSync);
  • 提交:前端在统一请求封装中,将{tokenName: tokenValue}塞入请求 Header;
  • 读取:Sa-Token 按 Storage → 请求体/Query → Header → Cookie 的顺序自动识别前端提交的 Token,完成鉴权。

在此基础上,还可以继续探索更进阶的配套能力(均为本仓库内官方文档):

  • 登录认证详解:StpUtil.login()的完整登录流程与多账号体系;
  • 框架配置:token-nameis-read-header等全部配置项说明;
  • Token 前缀:如Bearer等前缀模式下,前后端如何正确提交与裁剪 Token;
  • NotCookieController.java 完整示例:可直接运行的前后端分离登录样例(运行后可访问http://localhost:8081/NotCookie/doLogin2?name=zhang&pwd=123456观察返回的SaTokenInfo结构)。

【免费下载链接】Sa-Token✨ 开源、免费、一站式 Java 权限认证框架,让鉴权变得简单、优雅!—— 登录认证、权限认证、分布式 Session 会话、微服务网关鉴权、SSO 单点登录、OAuth2.0 统一认证、jwt 集成、API Key 秘钥授权、API 参数签名项目地址: https://gitcode.com/GitHub_Trending/sa/Sa-Token

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 16:36:26

电商导购返利小程序实战:淘宝京东拼多多联盟API对接与uniapp开发

简介:首席省钱赚钱专家v1.9.18小程序源码,面向个人创业者、电商运营与小程序开发者,基于拼多多优惠商品接口,实现购物返利、推广分销、团队奖励等典型电商小程序功能,帮助快速搭建“自购省钱分享赚钱”的应用场景。资源…

作者头像 李华
网站建设 2026/9/13 16:36:17

基于JSP的银行预约管理系统:从Servlet原理到并发排错实战

简介:这是一份基于JSPSQLServerTomcat技术栈的银行预约管理系统毕业设计源码包,面向Java Web方向的毕业生或需要快速搭建预约类管理系统的开发者,解决银行业务预约、客户信息管理、后台审核等环节的一体化实现问题。资源共499个文件&#xff…

作者头像 李华
网站建设 2026/9/13 16:35:46

基于Simulink的OFDM瑞利信道估计与同步仿真指南

简介:OFDM系统在瑞利衰落信道下的信道估计,是无线通信领域的典型问题。这套Simulink仿真资源提供了完整的导频辅助估计实现方案,适合通信专业本科生、研究生以及进行课程设计、毕业设计的开发者,既能帮助理解导频插入与信道估计原…

作者头像 李华
网站建设 2026/9/13 16:35:23

浙江科技新小龙企业评选标准与创新趋势分析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华