JWT认证
JWT
JSON Web Tokens,是一种开发的行业标准RFC 7519,用于安全的表示双方之间的声明;特别适用于分布式站点的单点登录(SSO)场景。目前jwt广泛应用在系统的用户认证方面,特别是现在前后端分离项目。
jwt认证流程
项目开发中,一般会按照上图所示的过程进行认证,即:用户登录成功之后,服务端给用户浏览器返回一个token,以后用户浏览器需要携带token再去向服务端发送请求,服务端校验token的合法性,合法则给用户看数据,否则,返回一些错误信息。
传统token方式与jwt在认证方面的差异
-
传统token方式
用户登录成功后,服务端生成一个随机token给用户,并且在服务端(数据库或缓存)中保存一份token,以后用户再来访问时需携带token,服务端接收到token之后,去数据库或缓存中进行校验token是否超时,是否合法
-
jwt方式
用户登录成功之后,服务端通过jwt生成一个随机token给用户(服务端无需保留token),以后用户再来访问时需携带token,服务端接收到token之后,通过jwt对token进行校验是否超时、是否合法。
jwt创建token
-
原理
jwt的生成token格式如下:即由.连接的三段字符串组成
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ikpva G4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
生成规则如下:
-
第一段HEADER部分,固定包含算法和token类型,对此json进行base64url加密
{ "alg": "HS256", "typ": "JWT" }
-
第二段PAYLOAD部分,包含一些数据,对此json进行base64url加密
{ "sub": "1234567890", "name": "John Doe", "iat": 1516239022 ... }
-
第三段SIGNATURE部分,把前两段的base密文通过.拼接起来,然后对其进行HS256加密,再然后对HS256密文进行base64url加密,最终得到token的第三段
base64url( HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), your-256-bit-secret (ᑃᰬےፉ) ) )
最后将三段字符串通过.拼接起来就生成了jwt的token
注意:base64加密是先做base64加密,然后再将-替代+及 _替代/
-
-
代码实现
基于python的pyjwt模块创建jwt的token
-
安装
pip install pyjwt
-
实现
import jwt import datetime from jwt import exceptions SALT = 'iv%x6xo7l7_u9bf_u!9#g#m*)*=ej@bek5)(@u3kh*72+unjv=' def create_token(): # ᭜header headers = { 'typ': 'jwt', 'alg': 'HS256' } # ᭜payload payload = { 'user_id': 1, # ᛔਧԎአಁID 'username': 'wupeiqi', # ᛔਧԎአಁݷ 'exp': datetime.datetime.utcnow() + datetime.timedelta(minutes=5) # ᩻ᳵ } result = jwt.encode(payload=payload, key=SALT, algorithm="HS256", headers=headers) return result if __name__ == '__main__': token = create_token() print(token)
-
jwt校验token
一般在认证成功后,把jwt生成的token返回给用户,以后用户再次访问的时候需要携带token,此时jwt需要对token进行超时及合法性校验
获取token之后,会按照以下步骤进行校验:
-
将token分割成header_segment、payload_segment、crypto_segment三部分
jwt_token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6 IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_ adQssw5c" signing_input, crypto_segment = jwt_token.rsplit(b'.', 1) header_segment, payload_segment = signing_input.split(b'.', 1)
-
对第一部分header_segment进行base64url解密,得到header
-
对第二部分payload_segment进行base64url解密,得到payload
-
对第三部分signature部分数据进行合法性校验
-
拼接前两段密文,即signing_input
-
从第一段明文中获取加密算法,默认HS256
-
使用算法+盐对signing_input进行加密,将得到的结果和signature密文进行比较
import jwt import datetime from jwt import exceptions def get_payload(token): """ ໑ഝtoken឴ݐpayload :param token: :return: """ try: # tokenӾ឴ݐpayload̓ӧ໊ḵݳဩ̈́ # unverified_payload = jwt.decode(token, None, False) // 高版本参数发生了变化 # print(unverified_payload) # tokenӾ឴ݐpayload໊̓ḵݳဩ̈́ verified_payload = jwt.decode(token, SALT, algorithms=['HS256']) return verified_payload except exceptions.ExpiredSignatureError: print('token૪०ප') except jwt.DecodeError: print('tokenᦊᦤ०ᨳ') except jwt.InvalidTokenError: print('ᶋဩጱtoken') if __name__ == '__main__': token = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE1NzM1NTU1NzksInVzZXJuYW1lIjoi d3VwZWlxaSIsInVzZXJfaWQiOjF9.xj-7qSts6Yg5Ui55-aUOHJS4KSaeLq5weXMui2IIEJU" payload = get_payload(token)
-
DRF-jwt
官网
http://getblimp.github.io/django-rest-framework-jwt/
安装
pip install djangorestframework-jwt
使用:user/urls.py
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
测试接口:post请求
"""
postman发生post请求
接口:http://api.luffy.cn:8000/user/login/
数据:
{
"username":"admin",
"password":"admin"
}
"""
dry-jwt开发
配置信息:JWT_AUTH到dev.py中
import datetime
JWT_AUTH = {
# 过期时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
# 自定义认证结果:见下方序列化user和自定义response
'JWT_RESPONSE_PAYLOAD_HANDLER': 'user.utils.jwt_response_payload_handler',
}
序列化uesr:uesr/serializers.py(自己创建)
from rest_framework import serializers
from . import models
class UserModelSerializers(serializers.ModelSerializer):
class Meta:
model = models.User
fields = ['username']
自定义response:uesr/utils.py
from .serializers import UserModelSerializers
def jwt_response_payload_handler(token, user=None, request=None):
return {
'status': 0,
'msg': 'ok',
'data': {
'token': token,
'user': UserModelSerializers(user).data
}
}
基于drf-jwt的全局认证:uesr/authentications.py(自己创建)
import jwt
from rest_framework.exceptions import AuthenticationFailed
from rest_framework_jwt.authentication import jwt_decode_handler
from rest_framework_jwt.authentication import get_authorization_header
from rest_framework_jwt.authentication import BaseJSONWebTokenAuthentication
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
def authenticate(self, request):
jwt_value = get_authorization_header(request)
if not jwt_value:
raise AuthenticationFailed('Authorization 字段是必须的')
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
raise AuthenticationFailed('签名过期')
except jwt.InvalidTokenError:
raise AuthenticationFailed('非法用户')
user = self.authenticate_credentials(payload)
return user, jwt_value
全局启用:settings/dev.py
REST_FRAMEWORK = {
# 认证模块
'DEFAULT_AUTHENTICATION_CLASSES': (
'user.authentications.JSONWebTokenAuthentication',
),
}
局部启用禁用:任何一个cbv类首行
# 局部禁用
authentication_classes = []
# 局部启用
from user.authentications import JSONWebTokenAuthentication
authentication_classes = [JSONWebTokenAuthentication]
多方式登录:uesr/utils.py
import re
from .models import User
from django.contrib.auth.backends import ModelBackend
class JWTModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
try:
if re.match(r'^1[3-9]\d{9}$', username):
user = User.objects.get(mobile=username)
else:
user = User.objects.get(username=username)
except User.DoesNotExist:
return None
if user.check_password(password) and self.user_can_authenticate(user):
return user
配置多方式登录:settings/dev.py
AUTHENTICATION_BACKENDS = ['user.utils.JWTModelBackend']