python中JWT用户认证的实现


Posted in Python onMay 18, 2020

在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了。于是我们的程序就不知道谁是谁,就要再验证一次。所以为了保证系统安全,我们就需要验证用户否处于登录状态。

一、传统方式

前后端分离通过Restful API进行数据交互时,如何验证用户的登录信息及权限。在原来的项目中,使用的是最传统也是最简单的方式,前端登录,后端根据用户信息生成一个token,并保存这个token 和对应的用户id到数据库或Session中,接着把token 传给用户,存入浏览器 cookie,之后浏览器请求带上这个cookie,后端根据这个cookie值来查询用户,验证是否过期。

但这样做问题就很多,如果我们的页面出现了 XSS 漏洞,由于 cookie 可以被 JavaScript 读取,XSS 漏洞会导致用户 token 泄露,而作为后端识别用户的标识,cookie 的泄露意味着用户信息不再安全。尽管我们通过转义输出内容,使用 CDN 等可以尽量避免 XSS 注入,但谁也不能保证在大型的项目中不会出现这个问题。

在设置 cookie 的时候,其实你还可以设置 httpOnly 以及 secure项。设置 httpOnly后 cookie 将不能被 JS 读取,浏览器会自动的把它加在请求的 header 当中,设置 secure的话,cookie 就只允许通过 HTTPS 传输。secure 选项可以过滤掉一些使用 HTTP 协议的 XSS 注入,但并不能完全阻止。

httpOnly 选项使得 JS 不能读取到 cookie,那么 XSS 注入的问题也基本不用担心了。但设置 httpOnly就带来了另一个问题,就是很容易的被 XSRF,即跨站请求伪造。当你浏览器开着这个页面的时候,另一个页面可以很容易的跨站请求这个页面的内容。因为 cookie 默认被发了出去。

另外,如果将验证信息保存在数据库中,后端每次都需要根据token查出用户id,这就增加了数据库的查询和存储开销。若把验证信息保存在session中,有加大了服务器端的存储压力。那我们可不可以不要服务器去查询呢?如果我们生成token遵循一定的规律,比如我们使用对称加密算法来加密用户id形成token,那么服务端以后其实只要解密该token就可以知道用户的id是什么了。不过呢,我只是举个例子而已,要是真这么做,只要你的对称加密算法泄露了,其他人可以通过这种加密方式进行伪造token,那么所有用户信息都不再安全了。恩,那用非对称加密算法来做呢,其实现在有个规范就是这样做的,就是我们接下来要介绍的 JWT。

二、Json Web Token(JWT)

WT 是一个开放标准(RFC 7519),它定义了一种用于简洁,自包含的用于通信双方之间以 JSON 对象的形式安全传递信息的方法。JWT 可以使用 HMAC 算法或者是 RSA 的公钥密钥对进行签名。它具备两个特点:

  • 简洁(Compact)

可以通过URL, POST 参数或者在 HTTP header 发送,因为数据量小,传输速度快

  • 自包含(Self-contained)

负载中包含了所有用户所需要的信息,避免了多次查询数据库

JWT 组成

Header 头部

头部包含了两部分,token 类型和采用的加密算法

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

它会使用 Base64 编码组成 JWT 结构的第一部分,如果你使用Node.js,可以用Node.js的包base64url来得到这个字符串。
------------ Base64是一种编码,也就是说,它是可以被翻译回原来的样子来的。它并不是一种加密过程。

Payload 负载

负载就是存放有效信息的地方。这些有效信息包含三个部分:
----标准中注册声明
----公共的声明
----私有的声明

公共的声明:

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

私有的声明:

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

{
  "iss": "lion1ou JWT",
  "iat": 1441593502,
  "exp": 1441594722,
  "aud": "www.example.com",
  "sub": "lion1ou@163.com"
}

// 包括需要传递的用户信息;
{ 
 "iss": "Online JWT Builder", 
 "iat": 1416797419, 
 "exp": 1448333419, 
 "aud": "www.gusibi.com", 
 "sub": "uid", 
 "nickname": "goodspeed", 
 "username": "goodspeed", 
 "scopes": [ "admin", "user" ] 
}
  • iss: 该JWT的签发者,是否使用是可选的;
  • sub: 该JWT所面向的用户,是否使用是可选的;
  • aud: 接收该JWT的一方,是否使用是可选的;
  • exp(expires): 什么时候过期,这里是一个Unix时间戳,是否使用是可选的;
  • iat(issued at): 在什么时候签发的(UNIX时间),是否使用是可选的;

其他还有:

  • nbf (Not Before):如果当前时间在nbf里的时间之前,则Token不被接受;一般都会留一些余地,比如几分钟;,是否使用是可选的;
  • jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
  • 同样的,它会使用 Base64 编码组成 JWT 结构的第二部分。
  • Signature 签名

前面两部分都是使用 Base64 进行编码的,即前端可以解开知道里面的信息。Signature 需要使用编码后的 header 和 payload 以及我们提供的一个密钥,然后使用 header 中指定的签名算法(HS256)进行签名。签名的作用是保证 JWT 没有被篡改过。

// 根据alg算法与私有秘钥进行加密得到的签名字串;
// 这一段是最重要的敏感信息,只能在服务端解密;
HMACSHA256( 
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  SECREATE_KEY
)

三个部分通过.连接在一起就是我们的 JWT 了,它可能长这个样子,长度貌似和你的加密算法和私钥有关系。

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6IjU3ZmVmMTY0ZTU0YWY2NGZmYzUzZGJkNSIsInhzcmYiOiI0ZWE1YzUwOGE2NTY2ZTc2MjQwNTQzZjhmZWIwNmZkNDU3Nzc3YmUzOTU0OWM0MDE2NDM2YWZkYTY1ZDIzMzBlIiwiaWF0IjoxNDc2NDI3OTMzfQ.PA3QjeyZSUh7H0GfE0vJaKW4LjKJuC3dVLQiY4hii8s

其实到这一步可能就有人会想了,HTTP 请求总会带上 token,这样这个 token 传来传去占用不必要的带宽啊。如果你这么想了,那你可以去了解下 HTTP2,HTTP2 对头部进行了压缩,相信也解决了这个问题。

签名的目的

最后一步签名的过程,实际上是对头部以及负载内容进行签名,防止内容被窜改。如果有人对头部以及负载的内容解码之后进行修改,再进行编码,最后加上之前的签名组合形成新的JWT的话,那么服务器端会判断出新的头部和负载形成的签名和JWT附带上的签名是不一样的。如果要对新的头部和负载进行签名,在不知道服务器加密时用的密钥的话,得出来的签名也是不一样的。

信息暴露

在这里大家一定会问一个问题:Base64是一种编码,是可逆的,那么我的信息不就被暴露了吗?

是的。所以,在JWT中,不应该在负载里面加入任何敏感的数据。在上面的例子中,我们传输的是用户的User ID。这个值实际上不是什么敏感内容,一般情况下被知道也是安全的。但是像密码这样的内容就不能被放在JWT中了。如果将用户的密码放在了JWT中,那么怀有恶意的第三方通过Base64解码就能很快地知道你的密码了。

因此JWT适合用于向Web应用传递一些非敏感信息。JWT还经常用于设计用户认证和授权系统,甚至实现Web应用的单点登录。

token 生成好之后,接下来就可以用token来和服务器进行通讯了。

三、JWT 使用

下图是client 使用 JWT 与server 交互过程:

 python中JWT用户认证的实现

1.这里在第三步我们得到 JWT 之后,需要将JWT存放在 client,之后的每次需要认证的请求都要把JWT发送过来。(请求时可以放到 header 的 Authorization )
首先,前端通过Web表单将自己的用户名和密码发送到后端的接口。这一过程一般是一个HTTP POST请求。建议的方式是通过SSL加密的传输(https协议),从而避免敏感信息被嗅探。

2.后端核对用户名和密码成功后,将用户的id等其他信息作为JWT Payload(负载),将其与头部分别进行Base64编码拼接后签名,形成一个JWT。形成的JWT就是一个形同lll.zzz.xxx的字符串。

3.后端将JWT字符串作为登录成功的返回结果返回给前端。前端可以将返回的结果保存在localStorage或sessionStorage上,退出登录时前端删除保存的JWT即可。

4.前端在每次请求时将JWT放入HTTP Header中的Authorization位。(解决XSS和XSRF问题)

5.后端检查是否存在,如存在验证JWT的有效性。例如,检查签名是否正确;检查Token是否过期;检查Token的接收方是否是自己(可选)。

6.验证通过后后端使用JWT中包含的用户信息进行其他逻辑操作,返回相应结果。

四、JWT 使用场景

WT的主要优势在于使用无状态、可扩展的方式处理应用中的用户会话。服务端可以通过内嵌的声明信息,很容易地获取用户的会话信息,而不需要去访问用户或会话的数据库。在一个分布式的面向服务的框架中,这一点非常有用。

但是,如果系统中需要使用黑名单实现长期有效的token刷新机制,这种无状态的优势就不明显了。

优点
快速开发
不需要cookie
JSON在移动端的广泛应用
不依赖于社交登录
相对简单的概念理解

缺点
Token有长度限制
Token不能撤销
需要token有失效时间限制(exp)

五、和Session方式存储id的差异

Session方式存储用户id的最大弊病在于Session是存储在服务器端的,所以需要占用大量服务器内存,对于较大型应用而言可能还要保存许多的状态。一般而言,大型应用还需要借助一些KV数据库和一系列缓存机制来实现Session的存储。

而JWT方式将用户状态分散到了客户端中,可以明显减轻服务端的内存压力。除了用户id之外,还可以存储其他的和用户相关的信息,例如该用户是否是管理员、用户所在的分组等。虽说JWT方式让服务器有一些计算压力(例如加密、编码和解码),但是这些压力相比磁盘存储而言可能就不算什么了。具体是否采用,需要在不同场景下用数据说话。

单点登录

Session方式来存储用户id,一开始用户的Session只会存储在一台服务器上。对于有多个子域名的站点,每个子域名至少会对应一台不同的服务器,例如:www.taobao.com,nv.taobao.com,nz.taobao.com,login.taobao.com。所以如果要实现在login.taobao.com登录后,在其他的子域名下依然可以取到Session,这要求我们在多台服务器上同步Session。使用JWT的方式则没有这个问题的存在,因为用户的状态已经被传送到了客户端。

六、总结

JWT的主要作用在于:
(一)可附带用户信息,后端直接通过JWT获取相关信息。
(二)使用本地保存,通过HTTP Header中的Authorization位提交验证。

七、附加,python使用JWT

python 中djagno rest framework要使用jwt,可以使用以下这个模块:githubs文档有使用说明
https://github.com/GetBlimp/django-rest-framework-jwt

pip install djangorestframework-jwt

不是使用django的话,我们可以使用 pyjwt:
https://github.com/jpadilla/pyjwt/
使用比较方便,下边是我在应用中使用的例子:

import jwt
import time

# 使用 sanic 作为restful api 框架 
def create_token(request):
  grant_type = request.json.get('grant_type')
  username = request.json['username']
  password = request.json['password']
  if grant_type == 'password':
    account = verify_password(username, password)
  elif grant_type == 'wxapp':
    account = verify_wxapp(username, password)
  if not account:
    return {}
  payload = {
    "iss": "gusibi.com",
     "iat": int(time.time()),
     "exp": int(time.time()) + 86400 * 7,
     "aud": "www.gusibi.com",
     "sub": account['_id'],
     "username": account['username'],
     "scopes": ['open']
  }
  token = jwt.encode(payload, 'secret', algorithm='HS256')
  return True, {'access_token': token, 'account_id': account['_id']}
  

def verify_bearer_token(token):
  # 如果在生成token的时候使用了aud参数,那么校验的时候也需要添加此参数
  payload = jwt.decode(token, 'secret', audience='www.gusibi.com', algorithms=['HS256'])
  if payload:
    return True, token
  return False, token

到此这篇关于python中JWT用户认证的实现的文章就介绍到这了,更多相关python JWT用户认证内容请搜索三水点靠木以前的文章或继续浏览下面的相关文章希望大家以后多多支持三水点靠木! 

Python 相关文章推荐
Python实现把utf-8格式的文件转换成gbk格式的文件
Jan 22 Python
举例讲解Python中is和id的用法
Apr 03 Python
Python常用内置模块之xml模块(详解)
May 23 Python
python调用百度地图WEB服务API获取地点对应坐标值
Jan 16 Python
python画图的函数用法以及技巧
Jun 28 Python
python实时检测键盘输入函数的示例
Jul 17 Python
Python检查 云备份进程是否正常运行代码实例
Aug 22 Python
使用pandas的box_plot去除异常值
Dec 10 Python
python实现logistic分类算法代码
Feb 28 Python
Python通过正则库爬取淘宝商品信息代码实例
Mar 02 Python
Python爬虫之爬取某文库文档数据
Apr 21 Python
python字符串的一些常见实用操作
Apr 06 Python
python 实现读取csv数据,分类求和 再写进 csv
May 18 #Python
python 实现分组求和与分组累加求和代码
May 18 #Python
Django ORM实现按天获取数据去重求和例子
May 18 #Python
如何实现更换Jupyter Notebook内核Python版本
May 18 #Python
python mysql自增字段AUTO_INCREMENT值的修改方式
May 18 #Python
Pycharm安装并配置jupyter notebook的实现
May 18 #Python
Django中的AutoField字段使用
May 18 #Python
You might like
与数据库连接
2006/10/09 PHP
PHP MPDF中文乱码的解决方式
2015/12/08 PHP
Codeigniter中集成smarty和adodb的方法
2016/03/04 PHP
PHP与Ajax相结合实现登录验证小Demo
2016/03/16 PHP
Laravel中的Blade模板引擎示例详解
2017/10/10 PHP
PHP数组常用函数实例小结
2018/08/20 PHP
兼容多浏览器的iframe自适应高度(ie8 、谷歌浏览器4.0和 firefox3.5.3)
2009/11/04 Javascript
jQuery实现切换页面布局使用介绍
2011/10/09 Javascript
js中document.getElementByid、document.all和document.layers区分介绍
2011/12/08 Javascript
Jquery多选框互相内容交换的实例代码
2013/07/04 Javascript
JavaScript实现点击按钮后变灰避免多次重复提交
2013/07/15 Javascript
判断输入的字符串是否是日期格式的简单方法
2016/07/11 Javascript
vue实现留言板todolist功能
2017/08/16 Javascript
javascript修改浏览器title方法 JS动态修改浏览器标题
2017/11/30 Javascript
详解Angular路由之路由守卫
2018/05/10 Javascript
JS使用队列对数组排列,基数排序算法示例
2019/03/02 Javascript
微信小程序文字显示换行问题
2019/07/28 Javascript
解决LayUI数据表格复选框不居中显示的问题
2019/09/25 Javascript
编写Python脚本使得web页面上的代码高亮显示
2015/04/24 Python
Python中return语句用法实例分析
2015/08/04 Python
PyCharm 常用快捷键和设置方法
2017/12/20 Python
基于python 爬虫爬到含空格的url的处理方法
2018/05/11 Python
python中ASCII码字符与int之间的转换方法
2018/07/09 Python
Python values()与itervalues()的用法详解
2019/11/27 Python
python实现双色球随机选号
2020/01/01 Python
Pytorch学习之torch用法----比较操作(Comparison Ops)
2020/06/28 Python
python语音识别指南终极版(有这一篇足矣)
2020/09/09 Python
用python进行视频剪辑
2020/11/02 Python
CSS3制作翻转效果_动力节点Java学院整理
2017/07/11 HTML / CSS
毕业生求职自荐信怎么写
2014/01/08 职场文书
优秀教师事迹简介
2014/02/02 职场文书
大学军训感言600字
2014/02/25 职场文书
七年级生物教学反思
2016/02/20 职场文书
详解CSS中的特指度和层叠问题
2021/07/15 HTML / CSS
使用 MybatisPlus 连接 SqlServer 数据库解决 OFFSET 分页问题
2022/04/22 SQL Server
vue二维数组循环嵌套方式 循环数组、循环嵌套数组
2022/04/24 Vue.js