前后端分离架构下使用 Sa-Token 完成登录认证

打印 上一主题 下一主题

主题 912|帖子 912|积分 2736

一、架构分析

目前绝大多数系统都已经采用 “前后端分离” 架构来设计了,传统的Session模式鉴权也不再适合这种架构(或者需要额外写很多的代码来专门适配)。
Sa-Token 是一个 java 轻量级权限认证框架,专为前后端分离架构打造,主要解决登录认证、权限认证、单点登录、OAuth2、微服务网关鉴权 等一系列权限相关问题。
Gitee 开源地址:https://gitee.com/dromara/sa-token
本文将介绍在 Springboot 架构下的前后端分离项目,如何使用 Sa-Token 方便的完成登录认证。
首先在项目中引入 Sa-Token 依赖:
  1. <dependency>
  2.     <groupId>cn.dev33</groupId>
  3.     <artifactId>sa-token-spring-boot-starter</artifactId>
  4.     <version>1.34.0</version>
  5. </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 的值)。
    • 将此对象传递到前台,让前端人员将这两个值保存到本地。

代码示例:
  1. // 登录接口
  2. @RequestMapping("doLogin")
  3. public SaResult doLogin() {
  4.         // 第1步,先登录上
  5.         StpUtil.login(10001);
  6.         // 第2步,获取 Token  相关参数
  7.         SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
  8.         // 第3步,返回给前端
  9.         return SaResult.data(tokenInfo);
  10. }
复制代码
四、前端将 token 提交到后端


  • 无论是app还是小程序,其传递方式都大同小异。
  • 那就是,将 token 塞到请求header里 ,格式为:{tokenName: tokenValue}。
  • 以经典跨端框架 uni-app 为例:
方式1,简单粗暴
  1. // 1、首先在登录时,将 tokenValue 存储在本地,例如:
  2. uni.setStorageSync('tokenValue', tokenValue);
  3. // 2、在发起ajax请求的地方,获取这个值,并塞到header里
  4. uni.request({
  5.         url: 'https://www.example.com/request', // 仅为示例,并非真实接口地址。
  6.         header: {
  7.                 "content-type": "application/x-www-form-urlencoded",
  8.                 "satoken": uni.getStorageSync('tokenValue')                // 关键代码, 注意参数名字是 satoken
  9.         },
  10.         success: (res) => {
  11.                 console.log(res.data);       
  12.         }
  13. });
复制代码
方式2,更加灵活
  1. // 1、首先在登录时,将tokenName和tokenValue一起存储在本地,例如:
  2. uni.setStorageSync('tokenName', tokenName);
  3. uni.setStorageSync('tokenValue', tokenValue);
  4. // 2、在发起ajax的地方,获取这两个值, 并组织到head里
  5. var tokenName = uni.getStorageSync('tokenName');        // 从本地缓存读取tokenName值
  6. var tokenValue = uni.getStorageSync('tokenValue');        // 从本地缓存读取tokenValue值
  7. var header = {
  8.         "content-type": "application/x-www-form-urlencoded"
  9. };
  10. if (tokenName != undefined && tokenName != '') {
  11.         header[tokenName] = tokenValue;
  12. }
  13. // 3、后续在发起请求时将 header 对象塞到请求头部
  14. uni.request({
  15.         url: 'https://www.example.com/request', // 仅为示例,并非真实接口地址。
  16.         header: header,
  17.         success: (res) => {
  18.                 console.log(res.data);       
  19.         }
  20. });
复制代码

  • 只要按照如此方法将token值传递到后端,Sa-Token 就能像传统PC端一样自动读取到 token 值,进行鉴权。
  • 你可能会有疑问,难道我每个ajax都要写这么一坨?岂不是麻烦死了?

    • 你当然不能每个 ajax 都写这么一坨,因为这种重复性代码都是要封装在一个函数里统一调用的。

其它解决方案:
如果你对 Cookie 非常了解,那你就会明白,所谓 Cookie ,本质上就是一个特殊的header参数而已,
而既然它只是一个 header 参数,我们就能手动模拟实现它,从而完成鉴权操作。
这其实是对无Cookie模式的另一种解决方案,有兴趣的同学可以百度了解一下,在此暂不赘述。
五、代码对比

为了更加直观的显示出 前后端一体架构 和 前后端分离架构 的差异,此处再提供一个示例:
  1. package com.pj.cases.up;
  2. import org.springframework.web.bind.annotation.RequestMapping;
  3. import org.springframework.web.bind.annotation.RestController;
  4. import cn.dev33.satoken.stp.SaTokenInfo;
  5. import cn.dev33.satoken.stp.StpUtil;
  6. import cn.dev33.satoken.util.SaResult;
  7. /**
  8. * Sa-Token 前后端分离模式示例
  9. *
  10. * @author kong
  11. * @since 2022-10-17
  12. */
  13. @RestController
  14. @RequestMapping("/NotCookie/")
  15. public class NotCookieController {
  16.         // 前后端一体模式的登录样例    ---- http://localhost:8081/NotCookie/doLogin?name=zhang&pwd=123456
  17.         @RequestMapping("doLogin")
  18.         public SaResult doLogin(String name, String pwd) {
  19.                 if("zhang".equals(name) && "123456".equals(pwd)) {
  20.                         // 会话登录
  21.                         StpUtil.login(10001);
  22.                     return SaResult.ok();
  23.                 }
  24.                 return SaResult.error("登录失败");
  25.         }
  26.        
  27.         // 前后端分离模式的登录样例    ---- http://localhost:8081/NotCookie/doLogin2?name=zhang&pwd=123456
  28.         @RequestMapping("doLogin2")
  29.         public SaResult doLogin2(String name, String pwd) {
  30.                
  31.                 if("zhang".equals(name) && "123456".equals(pwd)) {
  32.                        
  33.                         // 会话登录
  34.                         StpUtil.login(10001);
  35.                        
  36.                         // 与常规登录不同点之处:这里需要把 Token 信息从响应体中返回到前端
  37.                         SaTokenInfo tokenInfo = StpUtil.getTokenInfo();
  38.                     return SaResult.data(tokenInfo);
  39.                 }
  40.                 return SaResult.error("登录失败");
  41.         }
  42.        
  43. }
复制代码

  • 接口一:Token 将在 Cookie 上下文返回到前端,并由浏览器每次请求时自动提交,这种模式适合前后端一体的架构。
  • 接口二:Token 将在响应 body 里返回到前端,并由前端手动存储,并手动在每次请求时提交,这种模式适合前后端分离的架构。
六、自定义 Token 提交的前缀

在某些系统中,前端提交token时会在前面加个固定的前缀,例如:
  1. {
  2.         "satoken": "Bearer xxxx-xxxx-xxxx-xxxx"
  3. }
复制代码
此时后端如果不做任何特殊处理,框架将会把Bearer 视为token的一部分,无法正常读取token信息,导致鉴权失败。
为此,我们需要在yml中添加如下配置:
  1. sa-token:
  2.         # token前缀
  3.         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. // 1. token-style=uuid    —— uuid风格 (默认风格)
  2. "623368f0-ae5e-4475-a53f-93e4225f16ae"
  3. // 2. token-style=simple-uuid    —— 同上,uuid风格, 只不过去掉了中划线
  4. "6fd4221395024b5f87edd34bc3258ee8"
  5. // 3. token-style=random-32    —— 随机32位字符串
  6. "qEjyPsEA1Bkc9dr8YP6okFr5umCZNR6W"
  7. // 4. token-style=random-64    —— 随机64位字符串
  8. "v4ueNLEpPwMtmOPMBtOOeIQsvP8z9gkMgIVibTUVjkrNrlfra5CGwQkViDjO8jcc"
  9. // 5. token-style=random-128    —— 随机128位字符串
  10. "nojYPmcEtrFEaN0Otpssa8I8jpk8FO53UcMZkCP9qyoHaDbKS6dxoRPky9c6QlftQ0pdzxRGXsKZmUSrPeZBOD6kJFfmfgiRyUmYWcj4WU4SSP2ilakWN1HYnIuX0Olj"
  11. // 6. token-style=tik    —— tik风格
  12. "gr_SwoIN0MC1ewxHX_vfCW3BothWDZMMtx__"
复制代码
八、自定义 Token 生成策略

如果你觉着以上风格都不是你喜欢的类型,那么你还可以自定义token生成策略,来定制化token生成风格。

怎么做呢?只需要重写 SaStrategy 策略类的 createToken 算法即可:
参考步骤如下:

1、在SaTokenConfigure配置类中添加代码:
  1. @Configuration
  2. public class SaTokenConfigure {
  3.     /**
  4.      * 重写 Sa-Token 框架内部算法策略
  5.      */
  6.     @Autowired
  7.     public void rewriteSaStrategy() {
  8.             // 重写 Token 生成策略
  9.             SaStrategy.me.createToken = (loginId, loginType) -> {
  10.                     return SaFoxUtil.getRandomString(60);        // 随机60位长度字符串
  11.             };
  12.     }
  13. }
复制代码
2、再次调用 StpUtil.login(10001)方法进行登录,观察其生成的token样式:
  1. gfuPSwZsnUhwgz08GTCH4wOgasWtc3odP4HLwXJ7NDGOximTvT4OlW19zeLH
复制代码
参考资料


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

火影

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表