什么是JWT(what)

  • JWT(JSON Web Token)是一个开放标准(RFC 7519),它定义了一种紧凑且自包含的方式,以JSON对象的形式在各方之间安全地传输信息。

  • JWT是一个数字签名,生成的信息是可以验证并被信任的。

  • 使用密钥(使用HMAC算法)或使用RSA或ECDSA的公钥/私钥对JWT进行签名。

  • JWT是目前最流行的跨域认证解决方案

JWT令牌结构

SON Web令牌以紧凑的形式由三部分组成,这些部分由点(.)分隔,分别是:

  • Header

  • Payload

  • Signature

即为:xxxx.yyyy.zzzz

Header

Header通常由两部分组成:令牌的类型(即JWT)和所使用的签名算法(例如HMAC SHA256或RSA)。例如:

{"alg": "HS256","typ": "JWT"
}

Header会被Base64Url编码为JWT的第一部分。即为:

$ echo  -n '{"alg":"HS256","typ":"JWT"}'|base64
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload

Payload是有关实体(通常是用户)和其他数据的声明,它包含三部分:

注册声明

这些是一组预定义的权利要求,不是强制性的,而是建议使用的,以提供一组有用的可互操作的权利要求。其中一些是:iss(JWT的签发者), exp(expires,到期时间), sub(主题), aud(JWT接收者),iat(issued at,签发时间)等。

注意:声明名称都是三个字符

公开声明

公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。

私有声明

私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。

例子:

{ "iat": 1593955943, "exp": 1593955973, "uid": 10, "username": "test", "scopes": [ "admin", "user" ]
}

Payload会被Base64Url编码为JWT的第二部分。即为:

$ echo -n '{"iat":1593955943,"exp":1593955973,"uid":10,"username":"test","scopes":["admin","user"]}'|base64
eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE1OTM5NTU5NDMsInVpZCI6MTAsImV4cCI6MTU5Mzk1NTk3Mywic2NvcGVzIjpbImFkbWluIiwidXNlciJdfQ

注意:对于已签名的令牌,此信息尽管可以防止篡改,但任何人都可以读取。除非将其加密,否则请勿将机密信息放入JWT的有效负载或报头元素中。

Signature

Signature部分的生成需要base64编码之后的Header,base64编码之后的Payload,密钥(secret),Header需要指定签字的算法。

例如,如果要使用HMAC SHA256算法,则将通过以下方式创建签名:

HMACSHA256(base64UrlEncode(header) + "." +base64UrlEncode(payload),secret)

整合在一起

输出是三个由点分隔的Base64-URL字符串,可以在HTML和HTTP环境中轻松传递这些字符串,与基于XML的标准(例如SAML)相比,它更紧凑。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InRlc3QiLCJpYXQiOjE1OTM5NTU5NDMsInVpZCI6MTAsImV4cCI6MTU5Mzk1NTk3Mywic2NvcGVzIjpbImFkbWluIiwidXNlciJdfQ.VHpxmxKVKpsn2Iytqc_6Z1U1NtiX3EgVki4PmA-J3Pg

JWT是无状态授权机制,服务器的受保护路由将Header中检查有效的token,如果存在,则将允许用户访问受保护的资源。如果JWT包含必要的数据,则可以减少查询数据库中某些操作的需求。

什么时候使用JWT(when)

  • 授权:一旦用户登录,每个后续请求将包括JWT,从而允许用户访问该令牌允许的路由,服务和资源。单一登录是当今广泛使用JWT的一项功能,因为它的开销很小并且可以在不同的域中轻松使用。这也是JWT最常见的方案。

  • 信息交换:JSON Web令牌是各方之间安全地传输信息的好办法。对JWT进行签名,所以您可以确保发件人是他们所说的人。由于,签名可以设置有效时长,可以验证内容是否遭到篡改。

如何使用JWT(how)

JWT工作流程

根据下面的这张流程图来分析一下JWT的工作过程

  • 1 用户登录:提供用户名和密码;

  • 2 JWT生成token和refreshtoken,返回客户端;(注意:refreshtoken的过期时间长于token的过期时间

  • 3 客户端保存token和refresh_token,并携带token,请求服务端资源;

  • 4 服务端判断token是否过期,若没有过期,则解析token获取认证相关信息,认证通过后,将服务器资源返回给客户端;

  • 5 服务端判断token是否过期,若token已过期,返回token过期提示;

  • 6 客户端获取token过期提示后,用refresh_token接着继续上一次请求;

  • 7 服务端判断refreshtoken是否过期,若没有过期,则生成新的token和refreshtoken,并返回给客户端,客户端丢弃旧的token,保存新的token;

  • 8 服务端判断refreshtoken是否过期,若refreshtoken已过期,则返回给客户端token过期,需要重新登录的提示。

python+flask+JWT实战

import timefrom functools import wraps
from flask import Flask, request, jsonify
import jwt
from jwt import ExpiredSignatureErrorapp = Flask(__name__)max_time = 60
refresh_max_time = 120
token_secret = "This is a secret"def verify_token(func):@wraps(func)def decorator(*args, **kwargs):try:token = request.headers["token"]print(token)data = jwt.decode(token, token_secret, algorithms=['HS256'])now = int(time.time())time_interval = now - data['time']if time_interval >= max_time:# create new tokentoken, refresh_token = creat_token()return jsonify({"token": token, "refresh_token": refresh_token})except ExpiredSignatureError:return "Token expired"except Exception as ex:print(ex)return "Log in again"return func(*args, **kwargs)return decoratordef creat_token(uid):now = int(time.time())payload = {'uid': uid, 'time': now, 'exp': now + max_time}refresh_payload = {'uid': uid, 'time': now, 'exp': now + refresh_max_time}token = jwt.encode(payload, token_secret, algorithm='HS256')refresh_token = jwt.encode(refresh_payload, token_secret, algorithm='HS256')return token, refresh_token@app.route('/login', methods=["POST"])
def login():user_name = request.values.get('user_name')password = request.values.get('password')# @TODO 根据user_name和password 获取唯一的uiduid = 10token, refresh_token = creat_token(uid=uid)return jsonify({"token": token, "refresh_token": refresh_token})@app.route('/test', methods=['GET'])
@verify_token
def test():return 'hello world'if __name__ == "__main__":app.run(host="0.0.0.0", port=5000)

第三方库-itsdangerous

isdangerous简介

itsdangerous支持JSON Web 签名 (JWS),内部默认使用了HMAC和SHA1来签名,其中类 JSONWebSignatureSerializer内部与JWT一致,也分成三部分(header,payload,signature),查看源码可知:

def dumps(self, obj, salt=None, header_fields=None):"""Like :meth:`.Serializer.dumps` but creates a JSON WebSignature. It also allows for specifying additional fields to beincluded in the JWS header."""header = self.make_header(header_fields)signer = self.make_signer(salt, self.algorithm)return signer.sign(self.dump_payload(header, obj))
def dump_payload(self, header, obj):base64d_header = base64_encode(self.serializer.dumps(header, **self.serializer_kwargs))base64d_payload = base64_encode(self.serializer.dumps(obj, **self.serializer_kwargs))return base64d_header + b"." + base64d_payload
  • obj保存用户相关信息,类似JWT中的payload

  • base64url对obj和header进行编码之后,使用 .拼接

  • 将拼接之后的数据,作为signer的输入以及初始化 __init__中用户定义的secret来生成新的token

感兴趣的朋友可以直接参看github源码,这里不再展开赘述。

python+flask+isdangerous实战

import timefrom functools import wraps
from flask import Flask, request, jsonify
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, SignatureExpiredapp = Flask(__name__)max_time = 60
refresh_max_time = 120
token_secret = "This is a secret"def verify_token(func):@wraps(func)def decorator(*args, **kwargs):try:token = request.headers["token"]print(token)s = Serializer(token_secret)data = s.loads(token)now = int(time.time())time_interval = now - data['time']if time_interval >= max_time:# create new tokentoken, refresh_token = creat_token()return jsonify({"token": token, "refresh_token": refresh_token})except SignatureExpired:return "Token expired"except Exception as ex:print(ex)return "Log in again"return func(*args, **kwargs)return decoratordef creat_token(uid):now = int(time.time())s = Serializer(token_secret, expires_in=max_time)token = s.dumps({"uid": uid, "time": now}).decode("ascii")refresh_s = Serializer(token_secret, expires_in=refresh_max_time)refresh_token = refresh_s.dumps({"uid": uid, "time": now}).decode("ascii")return token, refresh_token@app.route('/token', methods=["POST"])
def token():user_name = request.values.get('user_name')password = request.values.get('password')# @TODO 根据user_name和password 获取唯一的uiduid = 10token, refresh_token = creat_token(uid=uid)return jsonify({"token": token, "refresh_token": refresh_token})@app.route('/test', methods=['GET'])
@verify_token
def test():return 'hello world'if __name__ == "__main__":app.run(host="0.0.0.0")

TimedJSONWebSignatureSerializer相比 JSONWebSignatureSerializer在header中赠加了过期时间,如果过期会抛出 SignatureExpired异常。

问题

用户登出,如何设置token无效?

JWT是无状态的,用户登出设置token无效就已经违背了JWT的设计原则,但是在实际应用场景中,这种功能是需要的,那该如何实现呢?提供几种思路:

  • 用户登出,浏览器端丢弃token

  • 使用redis数据库,用户登出,从redis中删除对应的token,请求访问时,需要从redis库中取出对应的token,若没有,则表明已经登出

使用redis,两个不同的设备,一个设备登出,另外一个设备如何处理?

请思考这样一种场景:

  • 同一个用户从两个设备登陆到服务端(设备1,设备2);

  • 设备1登出,删除redis中的对应的token

  • 设备2再次请求数据时,redis中的数据为空,需要重新登录。

很明显,这种情况是不应该出现的,说一下自己的想法:

  • 每一个设备与用户生成唯一的key,保存在redis中,即设备1的用户登出,只删除对应的token,设备2的token仍然存在

  • 服务器端维护一个版本号,相同用户不同设备登入,版本号加1,这样保持key的唯一性(和上面差不多)

往期推荐

????

  1. 实战|利用机器学习解决一个多分类任务

  2. 某程序员动了公司的祖传代码“屎山”,半年后怒交辞职报告!

  3. 告别刷抖音!30秒一个Python小例子,总有一款适合你

  4. 这个只有1.5M的软件,能让你的网速快3倍

- End -

最后说一个题外话,相信大家有不少人开通了视频号。小詹也开通了一个视频号,会分享互联网那些事、读书心得与副业经验,欢迎各位扫描下方二维码关注。

肝!一文讲解JWT用户认证全过程相关推荐

  1. 请求令牌 接口_时序图说明JWT用户认证及接口鉴权的细节

    JWT用户认证及接口鉴权的细节以及原理 一.回顾JWT的授权及鉴权流程 在笔者的上一篇文章中,已经为大家介绍了JWT以及其结构及使用方法.其授权与鉴权流程浓缩为以下两句话: 授权:使用可信用户信息(用 ...

  2. 前后端分离之JWT用户认证

    前后端分离之JWT用户认证 在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时 ...

  3. [转] 前后端分离之JWT用户认证

    [From] http://www.jianshu.com/p/180a870a308a 在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当 ...

  4. 前后端分离session_前后端分离:基于JWT用户认证分析

    作者:lion1ou https://lion1ou.win/2017/01/18/ 在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们 ...

  5. 微服务商城系统(十) Spring Security Oauth2 + JWT 用户认证

    文章目录 一.用户认证分析 1.认证 与 授权 2.单点登录 3.第三方账号登录 4.第三方认证 5.认证技术方案 6.Security Oauth 2.0 入门 7. 资源服务授权 (1)资源服务授 ...

  6. java jwt 用户认证_jwt身份验证

    http协议是无状态协议,服务端不能从请求中判断用户的身份,用户怎么每次去找到自己对应的信息呢? 1. cookie 这种方式最简单,在用户第一次登陆成功某个网站A,网站A服务端就将你的用户信息(比如 ...

  7. laravel 5.2 Auth用户认证教程

    官方文档:Laravel 5.2文档服务--用户认证 如果你看官方文档不太懂,那么请看下文操作. 说明 框架版本:laravel 5.2 laravel 5.2内置了auth用户认证服务,所以做网站时 ...

  8. SpringtBoot+SpringSecurity+Jwt+MyBatis整合实现用户认证以及权限控制

    文章目录 前言 数据库表结构 项目结构图 核心配置类SecurityConfig 实体类 工具类 用户登录认证 Token令牌验证 获取用户权限 用户权限验证 Service层实现类 统一响应类 Co ...

  9. Spring Boot整合JWT实现用户认证(附源码)

    点击上方"程序IT圈",选择"置顶公众号" 每天早上8点50分进来看看,就是最大的支持 来源:https://dwz.cn/yv1Do6e3 什么是JWT JW ...

最新文章

  1. asp.net input怎么获取值
  2. cisco中的igrp笔记
  3. 单击GridView控件,高亮单击所在的记录行
  4. SQL Server 查询性能优化——覆盖索引(二)
  5. 快速排序算法javascript实现
  6. c语言宏高级用法,C语言宏高级用法 [总结]
  7. 几个好用的makefile 几乎可以不用修改
  8. 精读《手写 SQL 编译器 - 回溯》 1
  9. 中卫市地图arcgis数据shp道路地名县区边界水系2021年(下载说明)
  10. 数字时钟——FPGA
  11. [MATLABSIMULINK] 如何提取并处理Simscape Power System 中powergui的谐波分析数据
  12. 基于matlab的am调制与仿真,基于MATLAB的AM调制及解调系统仿真分解
  13. 数模论文排版—从第三页设置页码,页码格式为page X of Y
  14. i春秋-CTF-web文件上传
  15. ForgivingExceptionHandler: An unexpected connection driver error occured (Exception message: Socket
  16. 电信物联网平台,java后台对接电信北向应用,命令下发到设备
  17. mysql表id的数据类型是_MySQL表中的数据类型
  18. LINUX内核内存屏障
  19. jzxx4015求和2
  20. 分数换算小数补0法_四年级下册—小数的意义

热门文章

  1. mysql大数据分库和分表 php解决方案
  2. Bug接口地址找不到
  3. ext中引用ux_Ext.ux.form.SearchField使用方法
  4. python中gt是什么意思_python--gt;函数基础
  5. mysql8.0.18用什么jdk_基础命令、cake-install、mysql远程登录、JDK安装
  6. php并行运算,php多进程并行执行脚本的代码
  7. 富文本框让最大四百像素_Django2.0.4 结合 KindEditor 4.1.11 富文本编辑器
  8. idea设置默认maven路径(2020版idea)
  9. 华为云阳云计算外包给哪家公司的_长春作为东北中心,华为四大件已经配齐,绝了!...
  10. 超级计算机 26010,全球最强超级计算机搭载的SW26010处理器解析