1. 安装
pip3 install pyjwt==1.7.1
2. 签发token
import jwt
import datetime
from jwt import exceptions
from django.conf import settings
def create_token(user, day=14, SALT=settings.SECRET_KEY):
"""
:param user: 用户对象2
:param day: 日期。单位天 ,默认14天
:param key: 密钥,用于加密/解密第三段
:return: 生成的token
"""
# 构造header
headers = {
'typ': 'jwt',
'alg': 'HS256'
}
# 构造payload,根据需要自定义用户内容
payload = {
'user_id': user.id, # 自定义用户ID
'username': user.username, # 自定义用户名
'exp': datetime.datetime.utcnow() + datetime.timedelta(days=day) # 默认14天有效
}
# 密钥
SALT = SALT
token = jwt.encode(payload=payload, key=SALT, algorithm="HS256", headers=headers).decode('utf-8')
return token
3. 使用(基于drf)
3.1 重写认证类
import jwt
from jwt import exceptions
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.authentication import BaseAuthentication
from app03 import models
from django.conf import settings
class JWTAuthentication(BaseAuthentication):
def authenticate(self, request):
# 密钥,必须跟签发token的一样
salt = settings.SECRET_KEY
# 从请求头中获取token
# 放的格式 Authorization:JWT xxxxxxxx
token = request.META.get('HTTP_AUTHORIZATION')
if not token:
raise AuthenticationFailed('没有携带token')
try:
# jwt提供了通过三段token,取出payload的方法,并且有校验功能
# 这个是我们签发时,封装的payload字典
verified_payload = jwt.decode(jwt=token, key=salt, verify=True)
except exceptions.ExpiredSignatureError:
raise AuthenticationFailed('token已失效')
except jwt.DecodeError:
raise AuthenticationFailed('token认证失败')
except jwt.InvalidTokenError:
raise AuthenticationFailed('非法的token')
except Exception as e:
# 所有异常都会走到这
raise AuthenticationFailed(str(e))
# 去数据库查出用户
user = models.New.objects.get(pk=verified_payload.get('user_id'))
# 认证通过,返回token
return user, token # request.user/auth
3.2 settings配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'app03.auth2.JWTAuthentication',
],
}
3.3 view(login)
from app03.auth import create_token
class Login(APIView):
# 局部禁用
authentication_classes = []
def post(self, request, *args, **kwargs):
user_obj = models.Topic.objects.get(pk=1)
token = create_token(user=user_obj)
return Response({'status': 200, 'token': token})
3.4 接口
from app03.auth2 import JWTAuthentication
class AuctionListView(APIView):
authentication_classes = [JWTAuthentication, ]
def get(self, request, *args, **kwargs):
return Response('ok')
4. jwt介绍
4.1 header
-
声明类型,这里是jwt
-
声明加密的算法 通常直接使用 HMAC SHA256
然后将头部进行base64.b64encode()加密(该加密是可以对称解密的),构成了第一部分.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
4.2 payload
载荷就是存放有效信息的地方。这个名字像是特指飞机上承载的货品,这些有效信息可以存放下面三个部分信息。
-
标准中注册的声明
-
公共的声明
-
私有的声明
标准中注册的声明 (建议但不强制使用) :
-
iss: jwt签发者
-
sub: jwt所面向的用户
-
aud: 接收jwt的一方
-
exp: jwt的过期时间,这个过期时间必须要大于签发时间
-
nbf: 定义在什么时间之前,该jwt都是不可用的.
-
iat: jwt的签发时间
-
jti: jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。
以上是JWT 规定的7个官方字段,供选用
公共的声明 : 公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.
私有的声明 : 私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息。
4.3 signature
JWT的第三部分是一个签证信息,这个签证信息由三部分组成:
-
header (base64后的)
-
payload (base64后的)
-
secret密钥
这个部分需要base64加密后的header和base64加密后的payload使用.
连接组成的字符串,然后通过header中声明的加密方式进行加盐secret
组合加密,然后就构成了jwt的第三部分。
// javascript
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
将这三部分用.
连接成一个完整的字符串,构成了最终的jwt:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
4.4 注意:
secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了。
4.5 jwt的优点:
1. 实现分布式的单点登陆非常方便
2. 数据实际保存在客户端,所以我们可以分担服务器的存储压力
3. JWT不仅可用于认证,还可用于信息交换。善用JWT有助于减少服务器请求数据库的次数,jwt的构成非常简单,字节占用很小,所以它是非常便于传输的。
4.6 jwt的缺点:
1. 数据保存在了客户端,我们服务端只认jwt,不识别客户端。
2. jwt可以设置过期时间,但是因为数据保存在了客户端,所以对于过期时间不好调整。#secret_key轻易不要改,一改所有客户端都要重新登录
5. base64转码补充
import base64
str1 = 'admin'
str2 = str1.encode()
b1 = base64.b64encode(str2) #数据越多,加密后的字符串越长
b2 = base64.b64decode(b1) #admin
各个语言中都有base64加密解密的功能,所以我们jwt为了安全,需要配合第三段加密