jwt 实践应用以及特殊案例思考
JSON Web Token 是 rfc7519 出的一份标准,使用 JSON 来传递数据,用于判定用户是否登录状态。
jwt 之前,使用 session
来做用户认证。
以下代码均使用 javascript 编写。
- 原文链接: 山月的博客
session
传统判断是否登录的方式是使用 session + token
。
token
是指在客户端使用 token 作为用户状态凭证,浏览器一般存储在 localStorage
或者 cookie
中。
session
是指在服务器端使用 redis 或者 sql 类数据库,存储 user_id 以及 token 的键值对关系,基本工作原理如下。
在服务器端使用 sessions
存储键值对
const sessions = {
"ABCED1": 10086,
"CDEFA0": 10010
}
每次客户端请求带权限数据时携带 token,在服务器端根据 token 与 sessions 获取 user_id, 完成认证过程
function getUserIdByToken (token) {
return sessions[token]
}
如果存储在 cookie
中就是经常听到的 session + cookie
的登录方案。其实存储在 cookie
,localStorage
甚至 IndexedDB
或者 WebSQL
各有利弊,核心思想一致。
关于 cookie
以及 token
优缺点,在 token authetication vs cookies 中有讨论。
如果不使用 cookie,可以采取 localStorage + Authorization
的方式进行认证,更加无状态化
// http 的头,每次请求权限接口时,需要携带 Authorization Header
const headers = {
Authorization: `Bearer ${localStorage.get('token')}`
}
推荐一个前端的存储库 localForage,使用
IndexedDB
,WebSQL
以及IndexedDB
做键值对存储。
无状态登录
session
需要在数据库中保持用户及token对应信息,所以叫 有状态。
试想一下,如何在数据库中不保持用户状态也可以登录。
第一种方法: 前端直接传 user_id 给服务端
缺点也特别特别明显,容易被用户篡改成任意 user_id,权限设置形同虚设。不过思路正确,接着往下走。
改进: 对 user_id 进行对称加密
服务端对 user_id 进行对称加密后,作为 token 返回客户端,作为用户状态凭证。比上边略微强点,但由于对称加密,选择合适的算法以及密钥比较重要
改进: 对 user_id 不需要加密,只需要进行签名,保证不被篡改
这便是 jwt 的思想:user_id,加密算法和签名组成 token 一起存储到客户端,每当客户端请求接口时携带 token,服务器根据 token 解析出加密算法与 user_id 来判断签名是否一致。
Json Web Token
jwt 根据 Header
,Payload
以及 Signature
三个部分由 .
拼接而成。
Header
Header 由非对称加密算法和类型组成,如下
const header = {
// 加密算法
alg: 'HS256',
type: 'jwt'
}
Payload
Payload 中由 Registered Claim 以及需要通信的数据组成。这些数据字段也叫 Claim
。
Registered Claim
中比较重要的是 "exp" Claim
表示过期时间,在用户登录时会设置过期时间。
const payload = {
// 表示 jwt 创建时间
iat: 1532135735,
// 表示 jwt 过期时间
exp: 1532136735,
// 用户 id,用以通信
user_id: 10086
}
Signature
Signature
由 Header
,Payload
以及 secretOrPrivateKey
计算而成。secretOrPrivateKey
作为敏感数据存储在服务器端,可以考虑使用 vault secret
或者 k8s secret
对于 secretOrPrivateKey
,如果加密算法采用 HMAC
,则为字符串,如果采用 RSA
或者 ECDSA
,则为 PrivateKey。
// 由 HMACSHA256 算法进行签名,secret 不能外泄
const sign = HMACSHA256(base64.encode(header) + '.' + base64.encode(payload), secret)
// jwt 由三部分拼接而成
const jwt = base64.encode(header) + '.' + base64.encode(payload) + '.' + sign
从生成 jwt 规则可知客户端可以解析出 payload,因此不要在 payload 中携带敏感数据,比如用户密码
校验过程
在生成规则中可知,jwt 前两部分是对 header 以及 payload 的 base64 编码。
当服务器收到客户端的 token 后,解析前两部分得到 header 以及 payload,并使用 header 中的算法与 secretOrPrivateKey 进行签名,判断与 jwt 中携带的签名是否一致。
带个问题,如何判断 token 过期?
应用
由上可知,jwt 并不对数据进行加密,而是对数据进行签名,保证不被篡改。除了在登录中可以用到,在进行邮箱校验,图形验证码和短信验证码时也可以用到。
图形验证码
在登录时,输入密码错误次数过多会出现图形验证码。
图形验证码的原理是给客户端一个图形,并且在服务器端保存与这个图片配对的字符串,以前也大都通过 session 来实现。
可以把验证码配对的字符串作为 secret,进行无状态校验。
const jwt = require('jsonwebtoken')
// 假设验证码为字符验证码,字符为 ACDE,10分钟失效
const token = jwt.sign({}, secrect + 'ACDE', { expiresIn: 60 * 10 })
const codeImage = getImageFromString('ACDE')
// 给前端的响应
const res = {
// 验证码图片的 token,从中可以校验前端发送的验证码
token,
// 验证码图片
codeImage,
}
短信验证码与图形验证码同理
邮箱校验
现在网站在注册成功后会进行邮箱校验,具体做法是给邮箱发一个链接,用户点开链接校验成功。
// 把邮箱以及用户id绑定在一起
const code = jwt.sign({ email, userId }, secret, { expiresIn: 60 * 30 })
// 在此链接校验验证码
const link = `https://example.com/code=${code}`
无状态 VS 有状态
关于无状态和有状态,在其它技术方向也有对比,比如 React 的 stateLess component
以及 stateful component
,函数式编程中的副作用可以理解为状态,http 也是一个无状态协议,需要靠 header 以及 cookie 携带状态。
在用户认证这里,有无状态是指是否依赖外部数据存储,如 mysql,redis 等。
案例
思考以下几个关于登录的问题如何使用 session 以及 jwt 实现,来更加清楚 jwt
的使用场景
当用户注销时,如何使该 token 失效
因为 jwt 无状态,不保存用户设备信息,没法单纯使用它完成以上问题,可以再利用数据库保存一些状态完成。
session
: 只需要把 user_id 对应的 token 清掉即可jwt
: 使用 redis,维护一张黑名单,用户注销时把该 token 加入黑名单,过期时间与 jwt 的过期时间保持一致。
如何允许用户只能在一个设备登录,如微信
session
: 使用 sql 类数据库,对用户数据库表添加 token 字段并加索引,每次登陆重置 token 字段,每次请求需要权限接口时,根据 token 查找 user_idjwt
: 假使使用 sql 类数据库,对用户数据库表添加 token 字段(不需要添加索引),每次登陆重置 token 字段,每次请求需要权限接口时,根据 jwt 获取 user_id,根据 user_id 查用户表获取 token 判断 token 是否一致。另外也可以使用计数器的方法,如下一个问题。
对于这个需求,session 稍微简单些,毕竟 jwt 也需要依赖数据库。
如何允许用户只能在最近五个设备登录,如诸多播放器
session
: 使用 sql 类数据库,创建 token 数据库表,有 id, token, user_id 三个字段,user 与 token 表为 1:m 关系。每次登录添加一行记录。根据 token 获取 user_id,再根据 user_id 获取该用户有多少设备登录,超过 5 个,则删除最小 id 一行。jwt
: 使用计数器,使用 sql 类数据库,在用户表中添加字段 count,默认值为 0,每次登录 count 字段自增1,每次登录创建的 jwt 的 Payload 中携带数据 current_count 为用户的 count 值。每次请求权限接口时,根据 jwt 获取 count 以及 current_count,根据 user_id 查用户表获取 count,判断与 current_count 差值是否小于 5
对于这个需求,jwt 略简单些,而使用 session 还需要多维护一张 token 表。
如何允许用户只能在最近五个设备登录,而且使某一用户踢掉除现有设备外的其它所有设备,如诸多播放器
session
: 在上一个问题的基础上,删掉该设备以外其它所有的token记录。jwt
: 在上一个问题的基础上,对 count + 5,并对该设备重新赋值为新的 count。
如何显示该用户登录设备列表 / 如何踢掉特定用户
session
: 在 token 表中新加列 devicejwt
: 需要服务器端保持设备列表信息,做法与 session 一样,使用 jwt 意义不大
总结
从以上问题得知,如果不需要控制登录设备数量以及设备信息,无状态的 jwt 是一个不错的选择。一旦涉及到了设备信息,就需要对 jwt 添加额外的状态支持,增加了认证的复杂度,此时选用 session 是一个不错的选择。
jwt 不是万能的,是否采用 jwt,需要根据业务需求来确定。
jwt 实践应用以及特殊案例思考的更多相关文章
- springsecurity+jwt实践和学习
1.参考资料: https://blog.csdn.net/qq924862077/article/details/83038031 https://blog.csdn.net/sxdtzhaoxin ...
- laravel jwt实践
laravel版本为5.5 1.使用 composer 安装 composer require tymon/jwt-auth 1.*@rc 2.发布配置文件 # 这条命令会在 config 下增加一个 ...
- C项目实践之通讯录管理案例
1.功能需求分析 通讯录管理案例主要实现对联系人的信息进行添加.显示.查找.删除.更新和保存功能.主要功能需求描述如下: (1)系统主控平台: 充许用户选择想要进行的操作,包括添加联系人信息,显示.查 ...
- Redis入门与实践(附项目真实案例代码)
我是3y,一年CRUD经验用十年的markdown程序员常年被誉为优质八股文选手 今天继续更新austin项目,如果还没看过该系列的同学可以点开我的历史文章回顾下,在看的过程中不要忘记了点赞哟!建议 ...
- TOP100summit 2017:小米唐沐等大咖精心挑选的100个年度研发案例实践
2017年,机器学习.大数据.人工智能等词汇成为软件研发行业的主流,大前端.DevOps.区块链等技术方式成为热点方向:2017年,智能硬件开始成为新的焦点,这一年更被称为智能音箱井喷的一年:2017 ...
- 精彩分享 | 欢乐游戏 Istio 云原生服务网格三年实践思考
作者 吴连火,腾讯游戏专家开发工程师,负责欢乐游戏大规模分布式服务器架构.有十余年微服务架构经验,擅长分布式系统领域,有丰富的高性能高可用实践经验,目前正带领团队完成云原生技术栈的全面转型. 导语 欢 ...
- 用Vue自己造个组件轮子,以及实践背后带来的思考
前言 首先,向大家说声抱歉.由于之前的井底之蛙,误认为Vue.js还远没有覆盖到二三线城市的互联网小厂里.现在我错了,从我司的前端技术选型之路便可见端倪.以太原为例,已经有不少公司陆续开始采用Vue. ...
- TOP100summit 2017:【案例分享】魅族持续交付平台建设实践
本篇文章内容来自第10期魅族开放日魅族运维架构师林钟洪的现场分享.编辑:Cynthia 一.自动化建设历程1.1 魅族互联网发展的时间线 2003-2008年被称之为“互联网1.0时代”.2003年, ...
- 理解JWT(JSON Web Token)认证及python实践
原文:https://segmentfault.com/a/1190000010312468?utm_source=tag-newest 几种常用的认证机制 HTTP Basic Auth HTTP ...
随机推荐
- hydra暴力破解
hydra,是一个非常好用的暴力破解工具,而且名字也很cool. 下面是官网上的介绍: AFP, Cisco AAA, Cisco auth, Cisco enable, CVS, Firebird, ...
- Jenkinsfile与Json的转换
前段时间调研了下青云的kubesphere,意外的发现了一个插件,pipeline-model-definition-plugin,用了将jenkins的pipeline.json互相转换的,以前可能 ...
- 【Spring Cloud】服务注册与发现组件——Eureka(二)
一.Eureka原理 1.架构图 首先来看eureka的官方结构图 所有应用作为Eureka Client和Eureka Server交互,服务提供者启动时向Eureka Server注册自己的IP. ...
- 百万年薪python之路 -- 递归
递归(每当有一个函数被递归调用,就应该要有一个返回值,才能正常把递归的返回值'归'回来) 一个正经的递归: 1.不断调用自己本身 2.有明确的结束条件 递归注重于"一递 一归&quo ...
- LeetCode 5024 除数博弈 --(简单博弈论)
今天在LeetCode的看到一到题目 这道题目有点坑,没有思路的话容易钻牛角. 刚刚开始时,我想的是直接用while循环来模拟计算,后来觉得这么好麻烦,也有复制的逻辑在里面.后面我推导了一下 以下是我 ...
- (day30)GIL + 线程相关知识点
目录 昨日内容 进程互斥锁 队列 进程间通信 生产者与消费者模型 线程 什么是线程 为什么使用线程 创建线程的两种方式 线程对象的属性 线程互斥锁 今日内容 GIL全局解释器锁 多线程的作用 计算密集 ...
- Vue入坑第一篇
写在前面的话:文章是个人学习过程中的总结,为方便以后回头在学习.文章中会参考官方文档和其他的一些文章,示例均为亲自编写和实践,若有写的不对的地方欢迎大家和我一起交流. 一.前言 本篇作为vue入门的一 ...
- window10系统下,彻底删除卸载mysql
本文介绍,在Windows10系统下,如何彻底删除卸载MySQL...1>停止MySQL服务开始->所有应用->Windows管理工具->服务,将MySQL服务停止.2> ...
- 九大Java性能调试工具,必备至少一款
九款Java性能调试工具,有什么更好.更多的工具,欢迎补充. NetBeans Profiler NetBeans中可以找到NetBeans Profiler. NetBeans分析器是NetBean ...
- C/C++顺序数据结构——动态数组测试
这是一篇顺序表数据结构——动态数组的测试, 实现 //初始化数组 //插入 //根据位置删除 //根据值删除 //查找 //打印 //释放动态数组的内存 //清空数组 //获得动态数组容量 //获得动 ...