关于Json Web Token(token)在前后端的实践思考

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码进行对比,判断用户名和密码是否正确,并作出相应提示,在这样的背景下,Token便应运而生。

image.png


大家好,我是 那个曾经的少年回来了。10年前我也曾经年轻过,如今已步入被淘汰的年龄,但现在幡然醒悟,所以活在当下,每天努力一点点,来看看2024年的时候自己会是什么样子吧,2024年的前端又会是什么样子,而2024年的中国乃至全球又会变成什么样子,如果你也有想法,那还不赶紧行动起来。期待是美好的,但是更重要的是要为美好而为之奋斗付诸于行动。


最近在接触一点后端,把自己的思考记录一下。


1、前言


啥也不说了,直接进入正题,来学习一下Token在前端和后端的简单应用分析


Token是在客户端频繁向服务端请求数据,服务端频繁的去数据库查询用户名和密码进行对比,判断用户名和密码是否正确,并作出相应提示,在这样的背景下,Token便应运而生。


Token实际上就是在第一个登录的时候通过用户名和密码,在服务端验证OK后生成的一串字符串,也可以说是验证通过后服务端为其签发一个令牌,随后前端在访问服务端接口时,客户端就可以携带这个TOken令牌访问服务器,服务端只需要验证令牌的有效性即可。


下面便是请求接口的一个大致过程


  • 先登录,获取Token
  • 调用业务接口,后端要先验证Token
  • 验证OK,才继续调用业务接口返回数据
  • 验证失败,则返回给前端,比如Token过期,则重新跳转到登录


image.png


2、后端


登录接口,通过用户名和密码,或者手机号验证码的方式通过验证


public async Task<dynamic> Login([FromServices] IAuthService authService, [FromBody] FormLoginRequest loginModel)
{
  return await authService.login(loginModel);
  // authoService.login中的逻辑
  // 判断是否匹配,匹配成功
  // 创建token并写入redis,并设置超期时间
  // 之前业务接口调用时,直接从redis中获取
  // 如果有超期,返回给前端一个标识
}


这里有一个创建Token的过程,我们来看一下token的组成


image.png


我找了一个公司正在开发项目中的token进行解析查看。主要结构如上图所示。解密以后最重要的信息便是uid,或者说是用户在后端中的唯一的用户id,那么通过uid便可以查询到相关的身份认证信息。


截图所示便是JSON Web Token的组成结构,从截图左侧仔细可以看到,中间有两个.将JWT分成了三个部分


  • HEADER


alg属性表示签名的算法(algorithm),默认是 HMAC SHA256(写成 HS256)
typ属性表示这个令牌(token)的类型(type)


  • PAYLOAD


中间部分存放的就是实际要传输的数据


  • Signature


Signature部分是对前面的两部分的数据进行签名,防止数据篡改。


  • 最终生成便是


首先明确一点,是在后端生成的Token,后端会先定义一个秘钥,这个秘钥只有后端服务器才知道,
不能泄露给用户,然后使用Header中指定的签名算法(默认情况是HMAC SHA256), 
算出签名以后将Header、Payload、Signature三部分拼成一个字符串,每个部分用`.`分割开来,
就可以返给用户了。


前端在登录认证通过获得Token并保存到前端以后,再调用业务接口的时候每次便会携带Token


后端服务会通过全局注册的环绕AOP,处理每次前端有请求到达后端的时候来对token校验


AllowAnonymousAttribute allowAnonymousAttribute = descriptor.MethodInfo.GetCustomAttribute<AllowAnonymousAttribute>(false);
  // 判断可不验证token的接口
  if (allowAnonymousAttribute != null)
  {
      await next(); 
      return;
  }
  //获取请求头中的Authorization
  string token = context.HttpContext.Request.Headers["Authorization"];
  // 相当于对前端传递的token进行转换
  string tokenKey = "sso." + Utils.MD5(token);
  // redis获取,看看是否有效,直接取出返回
  string loginUserJson = await RedisHelper.GetAsync(tokenKey);
  if (!loginUserJson.IsNullOrWhiteSpace()) {
    RedisSSOVerifyResult resultInfo = JsonSerializer.Deserialize<RedisSSOVerifyResult>(loginUserJson);
    if(resultInfo.ExpiresAt > DateTime.now()) {
      loginUser = resultInfo.LoginUser;
    }
    else {
      RedisHelper.RemoveAsync(tokenKey); // 无效了 从redis中移除
      throw new ValidException("Token认证过期,请重新登录", -2);  // 这里用-2跟前端做好约定
    }
  } else {
    throw new ValidException("Token认证过期,请重新登录", -2);  // 这里用-2跟前端做好约定
  }


大致的一个token认证过程是这样的,实际项目中相对来说还是比较复杂的,这是我从公司项目中扣取出来的。还有很多代码没有列出来,要不然会显得比较臃肿,而且主要逻辑不容易查看。


3、前端


通过登录页面,输入登录名和密码,或者手机号和验证码,获取到token,现将token存储到localStorage中,再通过token获取其他业务接口的数据。 通常可能首先通过token获取个人信息或者一些权限数据(这里只是提一下)。


const adminLogin = async () => {
      //   state.loading = true
      const res = await loginByMobile({
        mobile: state.loginForm.phone,
        captchaValue: state.loginForm.verificationCode,
      });
      state.loading = false;
      if (res?.code === 200) {
        localStorage.setItem(
          "token",
          JSON.stringify({
            ...res.data,
            account: state.loginForm.phone,
          })
        );
        store.dispatch("fetchMenu");
      }
    };


我这里登录完,直接通过token来获取当前登录用户的个人信息以及后台勾选的菜单权限,后端分别通过两个接口进行的数据返回。


async fetchMenu({ commit }) {
      try {
        const information = await getMyInformation()
        if (information?.code === 200) {
          console.log(information, 'information')
          commit("setMyInformation" , information.data)
          const res = await getMyMenu()
          if(res?.code === 200) {
            commit("changeMenuList",res.data)
            window.location.href = "/"
          }
        }
      } catch (error) {
      }
    },


这里是axios针对每次的请求添加请求头的Authorization


instance.interceptors.request.use(
  (request) => {
    const token = localStorage.token
      ? JSON.parse(localStorage.token)
      : {};
    request.headers = {
      "Authorization": token.authorization || '',
      "Content-Type": "application/x-www-form-urlencoded",
      "Content-Type": "application/json",
    };
    return request;
  },
  (error) => Promise.reject(error)
);


这里是针对后端接口返回数据的判断处理,其中有一个-2的特殊判断,这里是跟后端返回一起约定的code


instance.interceptors.response.use(
  (response) => {
    // token
    if (response.data.code === -2) {   
      // token失效
      ElMessage({
        message: "身份认证无效,请重新登录",
        type: "warning",
      });
      // localStorage.clear();
      clear()
      window.location.href = "/";
      return false;
    }
    if (response.data.code !== 200) {
      return Promise.reject(new Error(response.data.message));
    }
    /// ..... 其他的逻辑判断
    return response.data;
  },
}


上面通过 code为-2 进行判断 ,然后清除掉缓存数据,那么在vue-router路由中会进行判断处理


router.beforeEach((to, _from, next) => {
  NProgress.start()
  if (to.path === '/login' || to.path === '/init-password' ||  to.path === '/login-cellphone') {
    next()
    return false;
  }
  if (!localStorage.getItem('token')) {
    next('/login')
    return false
  }
  if (to.name) {
    next()
    return false
  }
  if (childrenPath.some((item) => to.path.includes(item))) {
    next()
    console.log('child');
    return false
  }
  // 如果找不到路由跳转到404
  next("/404")
  return false
})


总结


前端和后端大致的一个过程就在这里简单说完了,梳理完了以后,发现自己更清楚了,其实还有很多的问题要去处理,比如


  • 请求业务接口Token超期失效了该怎么办? 可以通过每次调用业务接口的前,只要验证Token成功,就延迟Token的超期时间,但是这种方式每次都要去处理Token的时间,相对来说就比较麻烦,而且对服务器有一定的损耗。


  • 那还有更好的办法吗? 当然也是有的。比如通过双Token进行无痛刷新,就是当一个token失效或者超期后,通过另外一个refresh_token来重新获取token的处理,获取成功后,再重新调用期间异常的业务接口


  • 当然肯定还有其他的方式吧,暂时能想到的就这么多了。


我的个人博客:vue.tuokecat.com/blog

我的个人github:github.com/aehyok

我的前端项目:pnpm + monorepo + qiankun + vue3 + vite3 + 工具库、组件库 + 工程化 + 自动化

不断完善中,整体框架都有了

在线预览:vue.tuokecat.com

github源码:github.com/aehyok/vue-…

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
目录
相关文章
|
4月前
|
前端开发 算法 API
构建高性能图像处理Web应用:Next.js与TailwindCSS实践
本文分享了构建在线图像黑白转换工具的技术实践,涵盖技术栈选择、架构设计与性能优化。项目采用Next.js提供优秀的SSR性能和SEO支持,TailwindCSS加速UI开发,WebAssembly实现高性能图像处理算法。通过渐进式处理、WebWorker隔离及内存管理等策略,解决大图像处理性能瓶颈,并确保跨浏览器兼容性和移动设备优化。实际应用案例展示了其即时处理、高质量输出和客户端隐私保护等特点。未来计划引入WebGPU加速、AI增强等功能,进一步提升用户体验。此技术栈为Web图像处理应用提供了高效可行的解决方案。
|
3月前
|
开发框架 JSON 中间件
Go语言Web开发框架实践:路由、中间件、参数校验
Gin框架以其极简风格、强大路由管理、灵活中间件机制及参数绑定校验系统著称。本文详解其核心功能:1) 路由管理,支持分组与路径参数;2) 中间件机制,实现全局与局部控制;3) 参数绑定,涵盖多种来源;4) 结构体绑定与字段校验,确保数据合法性;5) 自定义校验器扩展功能;6) 统一错误处理提升用户体验。Gin以清晰模块化、流程可控及自动化校验等优势,成为开发者的优选工具。
|
3月前
|
开发框架 JSON 中间件
Go语言Web开发框架实践:使用 Gin 快速构建 Web 服务
Gin 是一个高效、轻量级的 Go 语言 Web 框架,支持中间件机制,非常适合开发 RESTful API。本文从安装到进阶技巧全面解析 Gin 的使用:快速入门示例(Hello Gin)、定义 RESTful 用户服务(增删改查接口实现),以及推荐实践如参数校验、中间件和路由分组等。通过对比标准库 `net/http`,Gin 提供更简洁灵活的开发体验。此外,还推荐了 GORM、Viper、Zap 等配合使用的工具库,助力高效开发。
|
3月前
|
缓存 前端开发 应用服务中间件
Web端实时通信技术SSE在携程机票业务中的实践应用
本文介绍了携程机票前端基于Server-Sent Events(SSE)实现服务端推送的企业级全链路通用技术解决方案。文章深入探讨了 SSE 技术在应用过程中包括方案对比、技术选型、链路层优化以及实际效果等多维度的技术细节,为类似使用场景提供普适性参考和借鉴。该方案设计目标是实现通用性,适用于各种网络架构和业务场景。
85 1
|
4月前
|
缓存 前端开发 应用服务中间件
Web端实时通信技术SSE在携程机票业务中的实践应用
本文介绍了携程机票前端基于Server-Sent Events(SSE)实现服务端推送的企业级全链路通用技术解决方案。文章深入探讨了 SSE 技术在应用过程中包括方案对比、技术选型、链路层优化以及实际效果等多维度的技术细节,为类似使用场景提供普适性参考和借鉴。
109 7
|
4月前
|
Web App开发 前端开发 JavaScript
鸿蒙5开发宝藏案例分享---Web适配一多开发实践
这是一份实用的鸿蒙Web多设备适配开发指南,针对开发者在不同屏幕尺寸下的布局难题提供了解决方案。文章通过三大法宝(相对单位、媒体查询和窗口监听)详细介绍如何实现智能适配,并提供了多个实战案例,如宫格布局、对话框变形和自适应轮播图等。此外,还分享了调试技巧及工具推荐,帮助开发者快速上手并优化性能。最后鼓励读者实践探索,并提示更多官方资源等待发现。
|
6月前
|
存储 消息中间件 缓存
支持百万人超大群聊的Web端IM架构设计与实践
本文将回顾实现一个支持百万人超大群聊的Web端IM架构时遇到的技术挑战和解决思路,内容包括:通信方案选型、消息存储、消息有序性、消息可靠性、未读数统计。希望能带给你启发。
136 0
支持百万人超大群聊的Web端IM架构设计与实践
|
10月前
|
存储 JSON 安全
如何使用 JSON Web Tokens 进行身份验证?
总的来说,JWT 是一种强大而灵活的身份验证方式,通过正确使用和管理,可以为应用提供可靠的身份验证机制,同时提高系统的可扩展性和安全性。在实际应用中,需要根据具体的需求和场景,合理设计和实施 JWT 身份验证方案。
311 63
|
9月前
|
弹性计算 Java 关系型数据库
Web应用上云经典架构实践教学
Web应用上云经典架构实践教学
165 2
Web应用上云经典架构实践教学
|
8月前
|
Kubernetes Java 持续交付
小团队 CI/CD 实践:无需运维,Java Web应用的自动化部署
本文介绍如何使用GitHub Actions和阿里云Kubernetes(ACK)实现Java Web应用的自动化部署。通过CI/CD流程,开发人员无需手动处理复杂的运维任务,从而提高效率并减少错误。文中详细讲解了Docker与Kubernetes的概念,并演示了从创建Kubernetes集群、配置容器镜像服务到设置GitHub仓库Secrets及编写GitHub Actions工作流的具体步骤。最终实现了代码提交后自动构建、推送镜像并部署到Kubernetes集群的功能。整个过程不仅简化了部署流程,还确保了应用在不同环境中的稳定运行。
394 9

热门文章

最新文章