理解什么是JWT(Json web token)及Python实现
理解什么是JWT(Json web token)及Python实现
JWT 的官方文档: https://jwt.io/introduction/
https://www.cnblogs.com/lowmanisbusy/p/10930856.html
深入了解Json Web令牌之概念篇 :https://www.freebuf.com/articles/web/180874.html
参考博客:https://segmentfault.com/a/1190000010312468?utm_source=tag-newest
Json web token (JWT), 是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准((RFC 7519).该token被设计为紧凑且安全的,特别适用于分布式站点的单点登录(SSO)场景。JWT的声明一般被用来在身份提供者和服务提供者间传递被认证的用户身份信息,以便于从资源服务器获取资源,也可以增加一些额外的其它业务逻辑所必须的声明信息,该token也可直接被用于认证,也可被加密。
使用传统Session进行身份验证?
使用 Session 进行身份认证的话,实现**单点登录**,需要我们把用户的 Session 信息保存在一台电脑上,并且还会遇到常见的 **Cookie 跨域**的问题。但是,使用 token 进行认证的话, token 被保存在客户端,不会存在这些问题。
Token的认证流程
-
用户输入其登录信息
-
服务器验证信息是否正确,并返回已签名的token
-
token储在客户端,例如存在local storage或cookie中
-
之后的HTTP请求都将token添加到请求头里
-
服务器解码JWT,并且如果令牌有效,则接受请求
-
一旦用户注销,令牌将在客户端被销毁,不需要与服务器进行交互一个关键是,令牌是无状态的。后端服务器不需要保存令牌或当前session的记录
JWT 特点
- 体积小,因而传输速度快
- 传输方式多样,可以通过URL/POST参数/HTTP头部等方式传输
- 严格的结构化。它自身(在 payload 中)就包含了所有与用户相关的验证消息,如用户可访问路由、访问有效期等信息,服务器无需再去连接数据库验证信息的有效性,并且 payload 支持为你的应用而定制化。
- 支持跨域验证,可以应用于单点登录。
认识JWT
JWT是由三段信息构成的,将这三段信息文本用.
链接一起就构成了Jwt字符串。就像这样:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2NzgyNzQuNzkzODQ5NywibmFtZSI6Imxvd21hbiJ9.lFKwrllY62sFyWAjmFHMQX2QM09wKVSpkl9xDHjLFJ4
JWT的组成
一个通常你看到的jwt,由以下三部分组成,分别分别是:
1.header:主要声明了JWT的类型、签名算法;
2.payload:主要承载了各种声明并传递明文数据;
3.signature:拥有该部分的JWT被称为JWS,也就是签了名的JWS;没有该部分的JWT被称为不安全的JWT也就是不安全的JWT,此时报头中的声明的签名算法为none。
header:
-
声明类型,这里是jwt
-
声明加密的算法 通常直接使用 HMAC SHA256
-
然后将头部进行base64加密(该加密是可以对称解密的),构成了第一部分。
//包括类别(typ)、加密算法(alg);
{
"alg": "HS256",
"typ": "JWT"
}
载荷(payload)
载荷就是存放有效信息的地方。这些有效信息包含三个部分:
- 标准中注册声明
- 公共的声名
- 私有的声明
公共的声明 :
公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密。
私有的声明 :
私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
下面是一个例子:
// 包括需要传递的用户信息;
{ "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,从而回避重放攻击。
将上面的JSON对象进行base64编码
可以得到下面的字符串。这个字符串我们将它称作JWT的Payload(载荷)。
签名(signature)
// 根据alg算法与私有秘钥进行加密得到的签名字串;
// 这一段是最重要的敏感信息,只能在服务端解密;
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
SECREATE_KEY
)
jwt的第三部分是一个签证信息,这个签证信息由三部分组成:
- header (base64后的)
- payload (base64后的)
- secret
将上面的两个编码后的字符串都用.连接在一起(头部在前)最后,我们将上面拼接完的字符串用HS256算法进行加密。在加密的时候,我们还需要提供一个密钥(secret)。如果我们用 secret
作为密钥的话,那么就可以得到我们加密后内容
将这三部分用.连接成一个完整的字符串,构成了最终的jwt:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2MTA2NzgyNzQuNzkzODQ5NywibmFtZSI6Imxvd21hbiJ9.lFKwrllY62sFyWAjmFHMQX2QM09wKVSpkl9xDHjLFJ4
签名的目的
:签名实际上是对头部以及载荷内容进行签名。所以,如果有人对头部以及载荷的内容解码之后进行修改,再进行编码的话,那么新的头部和载荷的签名和之前的签名就将是不一样的。而且,如果不知道服务器加密的时候用的密钥的话,得出来的签名也一定会是不一样的。
这样就能保证token不会被篡改。
token 生成好之后,接下来就可以用token来和服务器进行通讯了。
这里在第三步我们得到 JWT 之后,需要将JWT存放在 client,之后的每次需要认证的请求都要把JWT发送过来。(请求时可以放到 header 的 Authorization )
JWT 使用场景
JWT的主要优势在于使用无状态、可扩展的方式处理应用中的用户会话。服务端可以通过内嵌的声明信息,很容易地获取用户的会话信息,而不需要去访问用户或会话的数据库。在一个分布式的面向服务的框架中,这一点非常有用。
但是,如果系统中需要使用黑名单实现长期有效的token刷新机制,这种无状态的优势就不明显了。
优点
快速开发
不需要cookie
JSON在移动端的广泛应用
不依赖于社交登录
相对简单的概念理解
缺点
Token有长度限制
Token不能撤销
需要token有失效时间限制(exp)
python代码实现
我们可以使用 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