JWT(Json WEB Token)

1. JWT 简介

JWTJson WEB Token)是一种采用 Json 方式安装传输信息的方式。 JWT 有针对各种开发语言的库。 python 中使用的是 PyJWT,它是 PythonJWT 的实现。

https://pypi.python.org/pypi/PyJWT/1.5.3
文档 https://pyjwt.readthedocs.io/en/latest/

2. JWT 实现机制

服务端往往需要一个 ID 来表示客户端的身份,那么不使用 session 也可以创建一个 ID 返回给客户端。但是,要保证客户端不可篡改。实现机制:

  • 服务端生成一个标识,并使用某种算法对标识签名,并发送给客户端。
  • 服务端收到客户端发来的标识,需要检查签名。

这种技术称作 JWTJson WEB Token)。
这种方案的缺点是,加密、解密需要消耗 CPU 计算资源,无法让浏览器自己主动检查过期的数据以清除。

3. JWT 应用场景

3.1. 认证

常应用于服务端认证接口的 无 session 方案 ,这是 Jwt 最常用的场景,一旦用户登录成功,就会得到 Jwt ,然后请求中就可以带上这个 Jwt 。服务器中 Jwt 验证通过,就可以被允许访问资源。甚至可以在不同域名中传递,在单点登录(Single Sign On)中应用广泛。

3.2. 数据交换

Jwt 可以防止数据被篡改,它还可以使用公钥、私钥,确保请求的发送者是可信的

4. JWT 安装

pip install pyjwt

5. JWT 原理

5.1. 数据体结构

jwt 生成的 token 分为三部分

  1. header: 由数据类型、加密算法构成
  2. payload: 负载就是要传输的数据,一般来说放入python对象即可,会被 json 序列化
  3. signature: 签名部分。是前面2部分数据分别 base64 编码后 使用点号连接后,加密算法使用 key 计算好一个结果,再被 base64 编码,得到签名

5.2. 提示

所有数据都是明文传输的,只是做了base64,如果是敏感信息,请不要使用 jwt
数据签名的目的不是为了隐藏数据,而是保证数据不被篡改。如果数据篡改了,发回到服务器端,服务器使用自己的 key 再计算一遍,然后进行签名比对,一定对不上签名。

5.3. Base64URL

  • HeaderPayload 串型化的算法是 Base64URL。这个算法跟 Base64 算法基本类似,但有一些小的不同。
  • JWT 作为一个令牌(token),有些场合可能会放到 URL(比如 api.example.com/?token=xxx)。Base64 有三个字符 +/=,在 URL 里面有特殊含义,所以要被替换掉:= 被省略、+ 替换成 -/ 替换成 _ 。这就是 Base64URL 算法。

6. JWT 使用

6.1. JWT 的使用方式

客户端收到服务器返回的 JWT,可以储存在 Cookie 里面,也可以储存在 localStorage

此后,客户端每次与服务器通信,都要带上这个 JWT

  • 可以把 JWT 放在 Cookie 里面自动发送,但是这样不能跨域。
  • 更好的做法是把 JWT 放在 HTTP 请求的头信息 Authorization 字段里面。
    Authorization: Bearer <token>
    
  • 另一种做法是,跨域的时候,JWT 就放在 POST 请求的数据体里面。

6.2. JWT 方法说明

方法 说明
jwt.encode(payload, key, algorithm='HS256', headers=None, json_encoder=None) 生成 jwt, bytes 类型的 base64 编码
**jwt.decode(jwt, key='', verify=True, algorithms=None, options=None, kwargs) 解码 jwt 为 bytes 类型的 json
jwt.get_unverified_header(jwt) 返回一个未验证的 jwt 头部信息

jwt.encode 参数说明

    def encode(self,
               payload,  # type: Union[Dict, bytes]
               key,  # type: str
               algorithm='HS256',  # type: str
               headers=None,  # type: Optional[Dict]
               json_encoder=None  # type: Optional[Callable]
               ):

jwt.decode 参数说明

    def decode(self,
               jwt,  # type: str
               key='',   # type: str
               verify=True,  # type: bool
               algorithms=None,  # type: List[str]
               options=None,  # type: Dict
               **kwargs):

jwt.get_unverified_header() 参数说明

    def get_unverified_header(self, jwt):
        """Returns back the JWT header parameters as a dict()

        Note: The signature is not verified so the header parameters
        should not be fully trusted until signature verification is complete
        """
        headers = self._load(jwt)[2]
        self._validate_headers(headers)

        return headers

6.3. jwt.encode() 方法使用

import jwt
password = '123456'
# 生成bytes类型的token
token = jwt.encode({"payload":'this is my payload'}, password, 'HS256')

print('    token:',token)
head, payload, signature = token.split(b'.')  # 将成成的token(是bytes类型)切割成三段: head, payload, signature
print('     head:',head)
print('  payload:',payload)
print('signature:',signature)
------------------------------结果--------------------------------------
    token: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXlsb2FkIjoidGhpcyBpcyBteSBwYXlsb2FkIn0.bPGtksZQ-ncY5mya0bF7EmpO82nlN1ohk9L2Dl2D4RA'
     head: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9'
  payload: b'eyJwYXlsb2FkIjoidGhpcyBpcyBteSBwYXlsb2FkIn0'
signature: b'bPGtksZQ-ncY5mya0bF7EmpO82nlN1ohk9L2Dl2D4RA'

6.4. jwt.decode() 方法使用

import jwt
import base64
password = '123456'
# 生成bytes类型的token
token = jwt.encode({"payload":'this is my payload'}, password, 'HS256')

# 此函数是为了避免转码时候 base64 编码结尾处缺少 b'=',而报错.
def addpad(x):
    suffix = 4 - len(x) % 4
    return x + b'=' * suffix

head, payload, signature = token.split(b'.')  # 将成成的token(是bytes类型)切割成三段: head, payload, signature

# 将 token (bytes 类型)转换为原来的类型
token_decode = jwt.decode(token, password, algorithms=['HS256'])
head_decode = base64.urlsafe_b64decode(addpad(head))
payload_decode = base64.urlsafe_b64decode(addpad(payload))
signature_decode = base64.urlsafe_b64decode(addpad(signature))

print('    token_decode:',token_decode, type(token_decode))
print('     head_decode:',head_decode, type(head_decode))
print('  payload_decode:',payload_decode, type(payload_decode))
print('signature_decode:',signature_decode, type(signature_decode))

------------------------------结果--------------------------------------
    token_decode: {'payload': 'this is my payload'} <class 'dict'>
     head_decode: b'{"typ":"JWT","alg":"HS256"}' <class 'bytes'>
  payload_decode: b'{"payload":"this is my payload"}' <class 'bytes'>
signature_decode: b'l\xf1\xad\x92\xc6P\xfaw\x18\xe6l\x9a\xd1\xb1{\x12jN\xf3i\xe57Z!\x93\xd2\xf6\x0e]\x83\xe1\x10' <class 'bytes'>

6.5. jwt.decode() 方法实现过期验证

import jwt
import datetime
import threading

event = threading.Event()

passwd = '123456'

token = jwt.encode({
    'a':'test',
    'exp': int(datetime.datetime.now().timestamp() + 5)
    }, passwd, 'HS256')

print(token)
print('===============================')
print('start at about:     ',int(datetime.datetime.now().timestamp()))
while True:
    if not event.wait(1):
        try:
            print(jwt.decode(token, passwd))
        except Exception as e:
            print(e)
            break

执行结果

b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJhIjoidGVzdCIsImV4cCI6MTU1MzQ0MDc5OH0.jzwOxmsp3zqVTXi38hsmu8K7c47j4NV8nPxQe9cQ3E0'
===============================
start at about:      1553440793
{'a': 'test', 'exp': 1553440798}
{'a': 'test', 'exp': 1553440798}
{'a': 'test', 'exp': 1553440798}
{'a': 'test', 'exp': 1553440798}
{'a': 'test', 'exp': 1553440798}
Signature has expired

6.6. jwt.get_unverified_header(jwt) 方法使用

import jwt
password = '123456'
token = jwt.encode({"payload":'this is my payload'}, password, 'HS256')

print('     token:',token)
print('jwt_header:',jwt.get_unverified_header(token))
------------------------------结果--------------------------------------
     token: b'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJwYXlsb2FkIjoidGhpcyBpcyBteSBwYXlsb2FkIn0.bPGtksZQ-ncY5mya0bF7EmpO82nlN1ohk9L2Dl2D4RA'
jwt_header: {'typ': 'JWT', 'alg': 'HS256'}

7. JWT 的几个特点

  1. JWT 默认是不加密,但也是可以加密的。生成原始 Token 以后,可以用密钥再加密一次。
  2. JWT 不加密的情况下,不能将秘密数据写入 JWT
  3. JWT 不仅可以用于认证,也可以用于交换信息。有效使用 JWT,可以降低服务器查询数据库的次数。
  4. JWT 的最大缺点是,由于服务器不保存 session 状态,因此无法在使用过程中废止某个 token,或者更改 token 的权限。也就是说,一旦 JWT 签发了,在到期之前就会始终有效,除非服务器部署额外的逻辑。
  5. JWT 本身包含了认证信息,一旦泄露,任何人都可以获得该令牌的所有权限。为了减少盗用,JWT 的有效期应该设置得比较短。对于一些比较重要的权限,使用时应该再次对用户进行认证。
  6. 为了减少盗用,JWT 不应该使用 HTTP 协议明码传输,要使用 HTTPS 协议传输。
posted @ 2019-03-24 23:37  scm1911  阅读(3452)  评论(0编辑  收藏  举报
/** ####################################################################### */