jwt(JSON Web Tokens),是一种开发的行业标准 RFC 7519
,用于安全的表示双方之间的声明。目前,jwt广泛应用在系统的用户认证方面,特别是现在前后端分离项目。
session
在django中,如果使用session进行认证,会在django_session表中存储用户登录记录,随着用户增加,数据库中的记录也会越来越多,增加了服务器压力
token
传统token
用户登录成功后,服务端生成一个随机token给用户,并且在服务端(数据库或缓存)中保存一份token,以后用户再来访问时需携带token
,服务端接收到token之后,去数据库或缓存中进行校验token的是否超时、是否合法。
jwt 形式
用户登录成功后,服务端通过jwt生成一个随机token给用户(服务端无需保留token),以后用户再来访问时需携带token,服务端 接收到token之后,通过jwt对token进行校验是否超时、是否合法。
jwt
是一段由.(点)
组成的三段式密文
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
第一段称为头部(header)
头部存储了两部分信息,分别是类别和加密算法。加密算法通常使用sha256(这里指整体加密时采用的算法),将头部进行base64url编码得到一段内容
# 密文
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
# 解密
{
"alg": "HS256",
"typ": "JWT"
}
第二段称为payload(载荷)
payload 里面包含用户有部分数据,比如用户id和用户名等。第二段内容也是通过base64url进行加密,所以内容中不能包含敏感数据
# 密文
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ
# 解密
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
第三段为签名(signature)
把前两段的base64url
密文通过.
拼接起来,并加入秘钥,然后对其(两段密文和盐)进行HS256
加密(header中定义的类别),再然后对整体密文进行base64url
加密,最终得到token的第三段。
base64url(
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
your-256-bit-secret (秘钥加盐)
)
)
安装
pip install pyjwt
在django系统中使用pyjwt来实现jwt认证。
>>> import jwt
>>> key = "secret"
>>> encoded = jwt.encode({"some": "payload"}, key, algorithm="HS256")
>>> print(encoded)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzb21lIjoicGF5bG9hZCJ9.4twFt5NiznN84AWoo1d7KO1T_yoc0Z6XOpOVswacPZg
>>> jwt.decode(encoded, key, algorithms="HS256")
{'some': 'payload'}
The JWT specification defines some registered claim names and defines how they should be used. PyJWT supports these registered claim names:
“exp” (Expiration Time) Claim
“nbf” (Not Before Time) Claim
“iss” (Issuer) Claim
“aud” (Audience) Claim
“iat” (Issued At) Claim
exp:指定过期时间
payload = {
"id":result.id,
"username":result.username,
'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=1) # 超时时间,其中exp是固定写法
}
在进行decode时,会对该值进行校验token是否过期
以django为例
路由接口
urlpatterns = [
path('login',views.LoginView.as_view()), # 生成token,并返回给用户
path('order',views.OrderView.as_view()) # 验证token
]
视图书写
# 登录视图类
class LoginView(APIView):
def post(self,request):
username = request.POST.get('username')
password = request.POST.get('password')
user_obj = models.UserInfo.objects.filter(username=username,password=password).first()
if not user_obj:
return JsonResponse({"code":"201","msg":"用户名或密码错误"})
payload = {"uid":user_obj.id,"username":user_obj.username}
token = jwt_auth.creata_token(payload)
return JsonResponse({"code":"200","msg":"post successful","token":token,})
# 订单类
class OrderView(APIView):
def get(self,request):
token = request.query_params.get("token")
result = jwt_auth.verify_token(token)
return JsonResponse(result)
token创建&认证函数
# utils/jwt_auth
import jwt
import datetime
from django.conf import settings
def creata_token(payload,):
"""
生成token
:param payload: 用于生成token的部分用户信息
:return: 生成的token
"""
# 1.构造headers
headers = {
'typ': 'jwt',
'alg': 'HS256'
}
# 2.构造payload
payload['exp'] = datetime.datetime.utcnow() + datetime.timedelta(minutes=1) # 指定过期时间为1分钟
# 3.生成token并返回
token = jwt.encode(payload=payload, key=settings.SECRET_KEY, algorithm="HS256")
return token
def verify_token(token):
"""
验证token的有效性
:param token:
:return:
"""
result = {"code": "202"}
try:
# true 表示集成了对时间等校验
verified_payload = jwt.decode(token, settings.SECRET_KEY, algorithms="HS256")
print(verified_payload)
result['msg'] = verified_payload
result['code'] = 200
except jwt.exceptions.ExpiredSignatureError:
result['error'] = "身份信息已失效,请重新登录"
except jwt.DecodeError:
result['error'] = "Token认证失败"
except jwt.InvalidTokenError:
result['error'] = "非法token"
return result
实验测试结果
自定义认证类
# utils/jwt_auth.py
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class JwtGlobalAuth(BaseAuthentication):
def authenticate(self, request):
token = request.query_params.get("token")
result = {"code": "202"}
try:
# true 表示集成了对时间等校验
verified_payload = jwt.decode(token, settings.SECRET_KEY, algorithms="HS256")
result['msg'] = verified_payload
result['code'] = 200
except jwt.exceptions.ExpiredSignatureError:
result['error'] = "身份信息已失效,请重新登录"
raise AuthenticationFailed(result)
except jwt.DecodeError:
result['error'] = "Token认证失败"
raise AuthenticationFailed(result)
except jwt.InvalidTokenError:
result['error'] = "非法token"
raise AuthenticationFailed(result)
# drf 认证类可以返回三种类型值
# 1.抛出异常
# 2.返回一个元组
return (verified_payload,token)
# 3.返回None
视图中加载用户认证 - 局部认证
# api/views.py
class OrderView(APIView):
authentication_classes = [jwt_auth.JwtGlobalAuth,]
def get(self,request):
ret = "这里是订单详情"
return JsonResponse(ret,safe=False)
全局token认证
# settings.py
import api
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES":['api.utils.jwt_auth.JwtGlobalAuth'] # 默认认证类,全局有效
}
视图类
# api/views.py
class LoginView(APIView):
authentication_classes = [] # 解除登录时的token认证
def post(self,request):
username = request.POST.get('username')
password = request.POST.get('password')
user_obj = models.UserInfo.objects.filter(username=username,password=password).first()
if not user_obj:
return JsonResponse({"code":"201","msg":"用户名或密码错误"})
payload = {"uid":user_obj.id,"username":user_obj.username}
token = jwt_auth.creata_token(payload)
return JsonResponse({"code":"200","msg":"post successful","token":token,})
class OrderView(APIView):
def get(self,request):
ret = "这里是订单详情"
return JsonResponse(ret,safe=False)