使用python实现后台系统的JWT认证

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
密钥管理服务KMS,1000个密钥,100个凭据,1个月
云解析 DNS,旗舰版 1个月
简介:

今天的文章介绍一种适用于restful+json的API认证方法,这个方法是基于jwt,并且加入了一些从oauth2.0借鉴的改良。

1. 常见的几种实现认证的方法

首先要明白,认证和鉴权是不同的。认证是判定用户的合法性,鉴权是判定用户的权限级别是否可执行后续操作。这里所讲的仅含认证。认证有几种方法:

1.1 basic auth

这是http协议中所带带基本认证,是一种简单为上的认证方式。原理是在每个请求的header中添加用户名和密码的字符串(格式为“username:password”,用base64编码)。

这种方式相当于将“用户名:密码”绑定为一个开放式证书,这会有几个问题:①每次请求都需要用户名密码,如果此连接未使用SSL/TLS,或加密被破解,用户名密码基本就暴露了;②无法注销用户的登录状态;③证书不会过期,除非修改密码。

总体来说,这种方法的特点就是,简单但不安全。

1.2 cookie

将认证的结果存在客户端的cookie中,通过检查cookie中的身份信息来作为认证结果。这种方式的特点是便捷,且只需要一次认证,多次可用;也可以注销登录状态和设置过期时间;甚至也有办法(比如设置httpOnly)来避免XSS攻击。但它的缺点十分明显,使用cookie那便是有状态的服务了。

1.3 token

JWT协议似乎已经应用十分广泛,JSON Web Token——一种基于token的json格式web认证方法。基本的原理是,第一次认证通过用户名密码,服务端签发一个json格式的token。后 续客户端的请求都携带这个token,服务端仅需要解析这个token,来判别客户端的身份和合法性。而JWT协议仅仅规定了这个协议的格式(RFC7519),它的序列生成方法在JWS协议中描述(https://tools.ietf.org/html/rfc7515),分为三个部分:

1.3.1 header头部

①声明类型,这里是jwt

②声明加密的算法 通常直接使用 HMAC SHA256,一种常见的头部是这样的:

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

再将其进行base64编码。

1.3.2 payload载荷

payload是放置实际有效使用信息的地方。JWT定义了几种内容,包括:

①标准中注册的声明,如签发者,接收者,有效时间(exp),时间戳(iat,issued at)等;为官方建议但非必须;

②公共声明;

③私有声明;

一个常见的payload是这样的:

{'user_id': 123456, 'user_role': admin, 'iat': 1467255177}

事实上,payload中的内容是自由的,按照自己开发的需要加入。 Ps.有个小问题。使用itsdangerous包的TimedJSONWebSignatureSerializer进行token序列生成的结果,exp是在头部里的。这里似乎违背了jwt的协议规则。

1.3.3 signature

存储了序列化的secreate key和salt key。这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密 方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

2. 认证需求

目标场景是一个前后端分离的后端系统,用于运维工作,虽在内网使用,也有一定的保密性要求。

①API为restful+json的无状态接口,要求认证也是相同模式

②可横向扩展

③较低数据库压力

④证书可注销

⑤证书可自动延期

⑥选择JWT。

3. JWT实现

3.1 如何生成token

这里使用python模块itsdangerous,这个模块能做很多编码工作,其中一个是实现JWS的token序列。genTokenSeq 这个函数用于生成token。其中使用的是TimedJSONWebSignatureSerializer进行序列的生成,这里secret_key密 钥、salt盐值从配置文件中读取,当然也可以直接写死在这里。expires_in是超时时间间隔,这个间隔以秒记,可以直接在这里设置,我选择将其设 为方法的形参(因为这个函数也用在了解决下提到的问题2)。

# serializer for JWTfrom itsdangerous import TimedJSONWebSignatureSerializer as Serializer """
 token is generated as the JWT protocol.
 JSON Web Tokens(JWT) are an open, industry standard RFC 7519 method
 """
 def genTokenSeq(self, expires):
 s = Serializer(
 secret_key=app.config['SECRET_KEY'],
 salt=app.config['AUTH_SALT'],
 expires_in=expires)
 timestamp = time.time() return s.dumps(
 {'user_id': self.user_id, 'user_role': self.role_id, 'iat': timestamp}) # The token contains userid, user role and the token generation time.
 # u can add sth more inside, if needed.
 # 'iat' means 'issued at'. claimed in JWT.

使用这个Serializer可以帮我们处理好header、signature的问题。我们只需要用s.dumps将payload的内容写进来。这里我准备在每个token中写入三个值:用户id、用户角色id和当前时间(‘iat’是JWT标准注册声明中的一项)。

假设我所写入的信息是

{ "iat": 1467271277.131803, "user_id": "46501228343b11e6aaa6a45e60ed5ed5f973ba0fcf783bb8ade34c7b492d9e55", "user_role": 3}

采用以上的方法所生成的token为

eyJhbGciOiJIUzI1NiIsImV4cCI6MTQ2NzM0MTQ3NCwiaWF0IjoxNDY3MzM3ODc0fQ.eyJpYXQiOjE0NjczMzc4NzQuNzE3MDYzLCJ1c2VyX2lkIjoiNDY1MDEyMjgzNDNiMTFlNmFhYTZhNDVlNjBlZDVlZDVmOTczYmEwZmNmNzgzYmI4YWRlMzRjN2I0OTJkOWU1NSIsInVzZXJfcm9sZSI6M30.23QD0OwLjdioKu5BgbaH2gHT2GoMz90n8VZcpvdyp7U

它是由“header.payload.signature”构成的。

3.2 如何解析token

解析需要使用到同样的serializer,配置一样的secret key和salt,使用loads方法来解析token。itsdangerous提供了各种异常处理类,用起来也很方便,如果是SignatureExpired,则可以直接返回过期;如果是BadSignature,则代表了所有其他签名错误的情况,于是又分为:

①能读取到payload:那么这个消息是一个内容被篡改、消息体加密过程正确的消息,secret key和salt很可能泄露了;

②不能读取到payload: 消息体直接被篡改,secret key和salt应该仍然安全。

以上内容写成一个函数,用于验证用户token。如果实现在python flask,可以考虑将此函数改为一个decorator修饰漆,将修饰器@到所有需要验证token的方法前面,则代码可以更加优雅。

# serializer for JWTfrom itsdangerous import TimedJSONWebSignatureSerializer as Serializer# exceptions for JWTfrom itsdangerous import SignatureExpired, BadSignature, BadData# Class xxx# after definition of your class, here goes the auth method:
 def tokenAuth(token):
 # token decoding
 s = Serializer(
 secret_key=api.app.config['SECRET_KEY'],
 salt=api.app.config['AUTH_SALT']) try:
 data = s.loads(token) # token decoding faild
 # if it happend a plenty of times, there might be someone
 # trying to attact your server, so it should be a warning.
 except SignatureExpired:
 msg = 'token expired'
 app.logger.warning(msg) return [None, None, msg] except BadSignature, e:
 encoded_payload = e.payload if encoded_payload is not None: try:
 s.load_payload(encoded_payload) except BadData: # the token is tampered.
 msg = 'token tampered'
 app.logger.warning(msg) return [None, None, msg]
 msg = 'badSignature of token'
 app.logger.warning(msg) return [None, None, msg] except:
 msg = 'wrong token with unknown reason'
 app.logger.warning(msg) return [None, None, msg] if ('user_id' not in data) or ('user_role' not in data):
 msg = 'illegal payload inside'
 app.logger.warning(msg) return [None, None, msg]
 msg = 'user(' + data['user_id'] + ') logged in by token.'# app.logger.info(msg)
 userId = data['user_id']
 roleId = data['user_role'] return [userId, roleId, msg]

检查和判定的机制如下:

1、使用加密的类,再用来解密(用上之前的密钥和盐值),得到结果存入data;

2、如果捕获到SignatureExpired异常,则代表根据token中的expired设置,token已经超时失效,返回‘token expired’;

3、如果是其他BadSignature异常,又要分为:
4、如果payload还完整,则解析payload,如果捕获BadData异常,则代表token已经被篡改,返回‘token tampered’;
5、如果payload不完整,直接返回‘badSignature of token’;

6、如果以上异常都不对,那只能返回未知异常‘wrong token with unknown reason’;

7、最后,如果data能正常解析,则将payload中的数据取出来,验证payload中是否有合法信息(这里是user_id和 user_role键值的json数据),如果数据不合法,则返回‘illegal payload inside’。一旦出现这种情况,则代表密钥和盐值泄露的可能性很大。


4. 优化

上述的方法可以做到基本的JWT认证,但在实际开发过程中还有其他问题:

token在生成之后,是靠expire使其过期失效的。签发之后的token,是无法收回修改的,因此涉及token的有效期的更改是个难题,它体现在以下两个问题:

8481c8f592b7f349aa84a1de5c171db681516edf 问题1.用户登出
8481c8f592b7f349aa84a1de5c171db681516edf 问题2.token自动延期

如何解决更改token有效期的问题,网上看到很多讨论,主要集中在以下内容:

8481c8f592b7f349aa84a1de5c171db681516edfJWT是一次性认证完毕加载信息到token里的,token的信息内含过期信息。过期时间过长则被重放攻击的风险太大,而过期时间太短则请求端体验太差(动不动就要重新登录)

8481c8f592b7f349aa84a1de5c171db681516edf把token存进库里,很自然能想到的是把每个token存库,设置一个valid字段,一旦注销了就valid=0;设置有效期字段,想要延期 就增加有效期时间。openstack keystone就是这么做的。这个做法虽方便,但对数据库的压力较大,甚至在访问量较大,签发token较多的情况下,是对数据库的一个挑战。况且这也 有悖于JWT的初衷。

8481c8f592b7f349aa84a1de5c171db681516edf为了使用户不需要经常重新登录,客户端将用户名密码保存起来(cookie),然后使用用户名密码验证,但那还得考虑防御CSRF攻击的问题。

这里,笔者借鉴了第三方认证协议Oauth2.0(RFC6749),它采取了另一种方法:refresh token,一个用于更新令牌的令牌。在用户首次认证后,签发两个token:

8481c8f592b7f349aa84a1de5c171db681516edf一个为access token,用于用户后续的各个请求中携带的认证信息

8481c8f592b7f349aa84a1de5c171db681516edf另一个是refresh token,为access token过期后,用于申请一个新的access token。

由此可以给两类不同token设置不同的有效期,例如给access token仅1小时的有效时间,而refresh token则可以是一个月。api的登出通过access token的过期来实现(前端则可直接抛弃此token实现登出),在refresh token的存续期内,访问api时可执refresh token申请新的access token(前端可存此refresh token,access token过其实进行更新,达到自动延期的效果)。refresh token不可再延期,过期需重新使用用户名密码登录。

这种方式的理念在于,将证书分为三种级别:

8481c8f592b7f349aa84a1de5c171db681516edfaccess token 短期证书,用于最终鉴权

8481c8f592b7f349aa84a1de5c171db681516edfrefresh token 较长期的证书,用于产生短期证书,不可直接用于服务请求

8481c8f592b7f349aa84a1de5c171db681516edf用户名密码 几乎永久的证书,用于产生长期证书和短期证书,不可直接用于服务请求

通过这种方式,使证书功效和证书时效结合考虑。
ps.前面提到创建token的时候将expire_in(jwt的推荐字段,超时时间间隔)作为函数的形参,是为了将此函数用于生成access token和refresh token,而两者的expire_in时间是不同的。

5. 总结一下

我们做了一个JWT的认证模块:
(access token在以下代码中为'token',refresh token在代码中为'rftoken')

8481c8f592b7f349aa84a1de5c171db681516edf首次认证

client --用户名密码---> server

client <--token、rftoken-- server

8481c8f592b7f349aa84a1de5c171db681516edfaccess token存续期内的请求

client --请求(携带token)--> server

client <---结果----server

8481c8f592b7f349aa84a1de5c171db681516edfaccess token超时

client ---请求(携带token)--> server

client <----msg:token expired-- server

8481c8f592b7f349aa84a1de5c171db681516edf重新申请access token

client -请求新token(携带rftoken)-> server

client <-----新token----- server

8481c8f592b7f349aa84a1de5c171db681516edfrftoken token超时

client -请求新token(携带rftoken)-> server

client <----msg:rftoken expired--- server

如果设计一个针对此认证的前端,需要:

8481c8f592b7f349aa84a1de5c171db681516edf 存储access token、refresh token
8481c8f592b7f349aa84a1de5c171db681516edf 访问时携带access token,自动检查access token超时,超时则使用refresh token更新access token;状态延期用户无感知

8481c8f592b7f349aa84a1de5c171db681516edf用户登出直接抛弃access token与refresh token


原文发布时间为:2017-04-10

本文作者:茶客furu声

本文来自云栖社区合作伙伴“Python中文社区”,了解相关信息可以关注“Python中文社区”微信公众号

相关文章
|
23天前
|
机器学习/深度学习 算法 TensorFlow
动物识别系统Python+卷积神经网络算法+TensorFlow+人工智能+图像识别+计算机毕业设计项目
动物识别系统。本项目以Python作为主要编程语言,并基于TensorFlow搭建ResNet50卷积神经网络算法模型,通过收集4种常见的动物图像数据集(猫、狗、鸡、马)然后进行模型训练,得到一个识别精度较高的模型文件,然后保存为本地格式的H5格式文件。再基于Django开发Web网页端操作界面,实现用户上传一张动物图片,识别其名称。
53 1
动物识别系统Python+卷积神经网络算法+TensorFlow+人工智能+图像识别+计算机毕业设计项目
|
22天前
|
机器学习/深度学习 人工智能 算法
植物病害识别系统Python+卷积神经网络算法+图像识别+人工智能项目+深度学习项目+计算机课设项目+Django网页界面
植物病害识别系统。本系统使用Python作为主要编程语言,通过收集水稻常见的四种叶片病害图片('细菌性叶枯病', '稻瘟病', '褐斑病', '稻瘟条纹病毒病')作为后面模型训练用到的数据集。然后使用TensorFlow搭建卷积神经网络算法模型,并进行多轮迭代训练,最后得到一个识别精度较高的算法模型,然后将其保存为h5格式的本地模型文件。再使用Django搭建Web网页平台操作界面,实现用户上传一张测试图片识别其名称。
73 21
植物病害识别系统Python+卷积神经网络算法+图像识别+人工智能项目+深度学习项目+计算机课设项目+Django网页界面
|
22天前
|
机器学习/深度学习 人工智能 算法
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
鸟类识别系统。本系统采用Python作为主要开发语言,通过使用加利福利亚大学开源的200种鸟类图像作为数据集。使用TensorFlow搭建ResNet50卷积神经网络算法模型,然后进行模型的迭代训练,得到一个识别精度较高的模型,然后在保存为本地的H5格式文件。在使用Django开发Web网页端操作界面,实现用户上传一张鸟类图像,识别其名称。
71 12
鸟类识别系统Python+卷积神经网络算法+深度学习+人工智能+TensorFlow+ResNet50算法模型+图像识别
|
22天前
|
机器学习/深度学习 算法 TensorFlow
交通标志识别系统Python+卷积神经网络算法+深度学习人工智能+TensorFlow模型训练+计算机课设项目+Django网页界面
交通标志识别系统。本系统使用Python作为主要编程语言,在交通标志图像识别功能实现中,基于TensorFlow搭建卷积神经网络算法模型,通过对收集到的58种常见的交通标志图像作为数据集,进行迭代训练最后得到一个识别精度较高的模型文件,然后保存为本地的h5格式文件。再使用Django开发Web网页端操作界面,实现用户上传一张交通标志图片,识别其名称。
47 6
交通标志识别系统Python+卷积神经网络算法+深度学习人工智能+TensorFlow模型训练+计算机课设项目+Django网页界面
|
18天前
|
机器学习/深度学习 人工智能 算法
【新闻文本分类识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台
文本分类识别系统。本系统使用Python作为主要开发语言,首先收集了10种中文文本数据集("体育类", "财经类", "房产类", "家居类", "教育类", "科技类", "时尚类", "时政类", "游戏类", "娱乐类"),然后基于TensorFlow搭建CNN卷积神经网络算法模型。通过对数据集进行多轮迭代训练,最后得到一个识别精度较高的模型,并保存为本地的h5格式。然后使用Django开发Web网页端操作界面,实现用户上传一段文本识别其所属的类别。
42 1
【新闻文本分类识别系统】Python+卷积神经网络算法+人工智能+深度学习+计算机毕设项目+Django网页界面平台
|
1天前
|
数据可视化 测试技术 Linux
基于Python后端构建多种不同的系统终端界面研究
【10月更文挑战第10天】本研究探讨了利用 Python 后端技术构建多样化系统终端界面的方法,涵盖命令行界面(CLI)、图形用户界面(GUI)及 Web 界面。通过分析各种界面的特点、适用场景及关键技术,展示了如何使用 Python 标准库和第三方库(如 `argparse`、`click`、`Tkinter` 和 `PyQt`)实现高效、灵活的界面设计。旨在提升用户体验并满足不同应用场景的需求。
|
27天前
|
前端开发 JavaScript 关系型数据库
基于Python+Vue开发的大学竞赛报名管理系统
基于Python+Vue开发的大学竞赛报名管理系统(前后端分离),这是一项为大学生课程设计作业而开发的项目。该系统旨在帮助大学生学习并掌握Python编程技能,同时锻炼他们的项目设计与开发能力。通过学习基于Python的大学竞赛报名管理系统项目,大学生可以在实践中学习和提升自己的能力,为以后的职业发展打下坚实基础。
35 3
基于Python+Vue开发的大学竞赛报名管理系统
|
28天前
|
安全 Java 数据安全/隐私保护
|
11天前
|
数据采集 存储 XML
构建高效的Python爬虫系统
【9月更文挑战第30天】在数据驱动的时代,掌握如何快速高效地获取网络信息变得至关重要。本文将引导读者了解如何构建一个高效的Python爬虫系统,从基础概念出发,逐步深入到高级技巧和最佳实践。我们将探索如何使用Python的强大库如BeautifulSoup和Scrapy,以及如何应对反爬措施和提升爬取效率的策略。无论你是初学者还是有经验的开发者,这篇文章都将为你提供宝贵的知识和技能,帮助你在信息收集的海洋中航行得更远、更深。
32 6
|
19天前
|
机器学习/深度学习 数据挖掘 测试技术
自学Python的系统策略与步骤
通过遵循这些步骤和策略,你可以系统地自学Python,并有效地构建和深化你的编程知识和技能。
18 6