今日内容概要
- jwt原理
- jwt开发流程
- drf-jwt快速使用
- drf-jwt定制返回格式
- drf-jwt自定义用户表签发
- drf-jwt自定义认证类
- drf-jwt的签发源码分析
- 认证源码
今日内容详细
jwt原理
在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制。
使用jwt认证和使用session认证的区别
基于session认证
基于jwt认证
jwt构成-三段式
JWT就是一段字符串,由三段信息构成的,将这三段信息文本用.链接一起就构成了Jwt字符串
1.header :jwt的头部承载两部分信息:
1.声明类型:这里是jwt
2.声明加密的算法:通常直接使用 HMAC SHA256
1.完整的头部:
{
'typ': 'JWT',
'alg': 'HS256'
}
2.然后将头部进行base64编码,构成了第一部分.
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9
2.payload:载荷就是存放有效信息的地方。存储的信息:
1.当前用户信息,用户名,用户id
2.exp: jwt的过期时间,这个过期时间必须要大于签发时间
1.定义一个payload:
{
"exp": "1234567890",
"name": "John Doe",
"user_id":99
}
2.然后将其进行base64编码,得到JWT的第二部分。eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9
3.signature :签证信息,这个签证信息由三部分组成:
1.header (base64后的)
2.payload (base64后的)
3.secret
这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。
"""
var encodedString = base64UrlEncode(header) + '.' + base64UrlEncode(payload);
var signature = HMACSHA256(encodedString, 'secret'); // TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
"""
ps:将这三部分用.连接成一个完整的字符串,构成了最终的jwt:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
jwt本质原理
1. jwt分三段式:头.体.签名 (head.payload.sgin)
2. 头和体是可逆加密,让服务器可以反解出user对象;签名是不可逆加密,保证整个token的安全性的
3.头体签名三部分,都是采用json格式的字符串,进行加密,可逆加密一般采用base64算法,不可逆加密一般采用hash(md5)算法
4.头中的内容是基本信息:公司信息、项目组信息、token采用的加密方式信息
{
"company": "公司信息",
...
}
5.体中的内容是关键信息:用户主键、用户名、签发时客户端信息(设备号、地址)、过期时间
{
"user_id": 1,
...
}
6.签名中的内容时安全信息:头的加密结果 + 体的加密结果 + 服务器不对外公开的安全码 进行md5加密
{
"head": "头的加密字符串",
"payload": "体的加密字符串",
"secret_key": "安全码"
}
7.签发:根据登录请求提交来的 账号 + 密码 + 设备信息 签发 token
1.用基本信息存储json字典,采用base64算法加密得到 头字符串
2.用关键信息存储json字典,采用base64算法加密得到 体字符串
3.用头、体加密字符串再加安全码信息存储json字典,采用hash md5算法加密得到 签名字符串
账号密码就能根据User表得到user对象,形成的三段字符串用 . 拼接成token返回给前台
8.校验:根据客户端带token的请求,反解出user对象
1.将token按 . 拆分为三段字符串,第一段 头加密字符串 一般不需要做任何处理
2.第二段 体加密字符串,要反解出用户主键,通过主键从User表中就能得到登录用户,过期时间和设备信息都是安全信息,确保token没过期,且时同一设备来的
3.再用 第一段 + 第二段 + 服务器安全码 不可逆md5加密,与第三段 签名字符串 进行碰撞
drf开发流程
1.使用jwt认证的开发流程,就是两部分
1.第一部分:签发token的过程,登录做的
用户携带用户名和密码,访问我,我们校验通过,生成token串,返回给前端
2.第二部分:token认证过程,登录认证时使用,其实就是咱们之前讲的认证类,在认证类中完成对token的认证操作
1.用户访问我们需要登陆后才能访问的接口,必须携带我们签发的token串(请求头)
2.我们取出token,验证该token是否过期,是否被篡改,是否是伪造的
3.如果正常,说明荷载中的数据,就是安全的,可以根据荷载中的用户id,查询出当前登录用户,放到request中即可
drf-jwt快速使用
需求:djagno+drf框架中,使用jwt来做登录认证
1.使用第三方:
1.django-rest-framework-jwt:https://github.com/jpadilla/django-rest-framework-jwt
2.djangorestframework-simplejwt:https://github.com/jazzband/djangorestframework-simplejwt
2.我们可以自己封装 :https://gitee.com/liuqingzheng/rbac_manager/tree/master/libs/lqz_jwt
3.安装: pip3.10 install djangorestframework-jwt
4.补充:
1.密码明文一样,每次加密后都不一样,如何做?
动态加盐,动态的盐要保存,跟密码保存在一起
2.有代码,有数据库,没有登录不进去的系统--->超级用户,自动添加用户
3.token被获取,模拟发送请求:不能篡改、不能伪造
5.HMACSHA256算法具有以下特点:
1.安全性:HMACSHA256基于SHA-256哈希函数,该哈希函数在密码学中被广泛认可为安全且具有强大的抗碰撞能力。
2.抗篡改性:HMACSHA256可以检测到消息是否被篡改,因为一旦消息被更改,生成的消息认证码也会不同。
3.可靠性:HMACSHA256提供了一种可靠的方式来进行消息完整性验证和身份认证,而且计算速度相对较快。
4.灵活性:HMACSHA256可以接受任意长度的密钥,并且可以处理任意长度的消息。
总之,HMACSHA256是一种强大的消息认证码算法,通过结合哈希函数和密钥,可以实现消息完整性验证和身份认证的安全机制。它在许多应用中被广泛使用,包括网络通信、数据传输和数字签名等领域。
快速使用
签发
只需要在路由中配置
1.签发过程(快速签发),必须是auth的user表(人家帮你写好了)
登录接口:基于auth的user表签发的
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token), # # 登录接口就有了,并且可以签发token,如果使用auth的user表做登录,不需要写额外的代码了
]
ps:
1.提前创建了超级用户,通过postman发送post请求,json格式,用户名密码正确,返回了token字符串
2.可在配置文件注册INSTALLED_APPS = [ ...,'rest_framework_jwt'],并修改国际化,返回的错误提示信息为中文
认证
在视图类上加
from rest_framework.response import Response
from rest_framework.views import APIView
from rest_framework_jwt.authentication import JSONWebTokenAuthentication
from rest_framework.permissions import IsAuthenticated # 是否认证通过
class BookView(APIView):
authentication_classes = [JSONWebTokenAuthentication] # 认证类,drf-jwt提供的
permission_classes = [IsAuthenticated] # 权限类,drf提供的
def get(self,request):
return Response('你看见我了')
urls.py
from django.contrib import admin
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
from app01.views import BookView
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', obtain_jwt_token), # # 登录接口就有了,并且可以签发token,如果使用auth的user表做登录,不需要写额外的代码了
path('books/', BookView.as_view())
]
注意:
1.在postman的get请求中,headers请求头中key为Authorization,value值为:jwt token串--->空格
总结
1.签发:只需要在路由中配置
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
2.认证:视图类上加
class BookView(APIView):
authentication_classes = [JSONWebTokenAuthentication] # 认证类,drf-jwt提供的
permission_classes = [IsAuthenticated] # 权限类,drf提供的
ps:访问的时候,要在请求头中携带,必须叫:Authorization:jwt token串
drf-jwt定制返回格式
需求:登录签发token的接口,要返回code,msg,username,token等信息
分析
1.入口:from rest_framework_jwt.views import obtain_jwt_token-->obtain_jwt_token
2.obtain_jwt_token = ObtainJSONWebToken.as_view()-->点进ObtainJSONWebToken
3.class ObtainJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
4.点进JSONWebTokenAPIView:-->找post方法
5.class JSONWebTokenAPIView(APIView):
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
if serializer.is_valid(): # 校验
user = serializer.object.get('user') or request.user
token = serializer.object.get('token')
response_data = jwt_response_payload_handler(token, user, request) # 返回格式
response = Response(response_data)
if api_settings.JWT_AUTH_COOKIE:
expiration = (datetime.utcnow() +
api_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(api_settings.JWT_AUTH_COOKIE,
token,
expires=expiration,
httponly=True)
return response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
6.点进去jwt_response_payload_handler(token, user, request) -->jwt_response_payload_handler = api_settings.JWT_RESPONSE_PAYLOAD_HANDLER
7.根据:from .settings import api_settings 找到:'JWT_PAYLOAD_HANDLER':
'rest_framework_jwt.utils.jwt_payload_handler',
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token': token
}
对返回格式进行定制
1.写个函数,函数返回字典格式,返回的格式,会被序列化,前端看到
2.写的函数配置一下
JWT_AUTH = { 'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.jwt_response.common_response', }
def common_response(token,user=None,request=None):
return {
'code':100,
'msg':'登陆成功',
'username':user.username,
'token':token,
}
JWT_AUTH={
'JWT_RESPONSE_PAYLOAD_HANDLER':'app01.jwt_response.common_response',
}
from django.contrib import admin
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
from app01.views import BookView
urlpatterns = [
path('admin/', admin.site.urls),
path('login/', obtain_jwt_token), # # 登录接口就有了,并且可以签发token,如果使用auth的user表做登录,不需要写额外的代码了
path('books/', BookView.as_view())
]
drf-jwt自定义用户表签发
分析
1.入口:from rest_framework_jwt.views import obtain_jwt_token-->obtain_jwt_token
2.obtain_jwt_token = ObtainJSONWebToken.as_view()-->点进ObtainJSONWebToken
3.class ObtainJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
4.点进JSONWebTokenAPIView:
class JSONWebTokenAPIView(APIView):
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class() # self从根上找,ObtainJSONWebToken类中的属性调方法
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs) # 返回
5.点进去ObtainJSONWebToken类中的属性调方法
class ObtainJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
class JSONWebTokenSerializer(Serializer):
def __init__(self, *args, **kwargs):
pass
@property
def username_field(self):
return get_username_field()
def validate(self, attrs): # 全局钩子
credentials = {
self.username_field: attrs.get(self.username_field),
'password': attrs.get('password')
}
if all(credentials.values()):
user = authenticate(**credentials)
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
payload = jwt_payload_handler(user) # 获取payload
return {
'token': jwt_encode_handler(payload), # 获取token
'user': user
}
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "{username_field}" and "password".')
msg = msg.format(username_field=self.username_field)
raise serializers.ValidationError(msg)
自定义用户表签发
创建一个用户表
from django.db import models
class User(models.Model):
username = models.CharField(max_length=32)
password = models.CharField(max_length=32)
age = models.IntegerField()
urls.py
from django.contrib import admin
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
from app01.views import BookView,UserView
urlpatterns = [
path('admin/', admin.site.urls),
path('books/', BookView.as_view()),
path('login/', UserView.as_view({'post':'login'}))
]
views.py
from rest_framework.viewsets import ViewSet
from app01.models import User
from rest_framework_jwt.settings import api_settings
jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER
class UserView(ViewSet):
def login(self,request):
name = request.data.get('username')
pwd = request.data.get('password')
user = User.objects.filter(username=name,password=pwd).first()
if user:
# 1.登录成功,签发token
payload = jwt_payload_handler(user)
token= jwt_encode_handler(payload)
return Response({'code': 100, 'msg': '登录成功', 'username': user.username, 'token': token})
else:
return Response({'code': 101, 'msg': '用户名或密码错误'})
drf-jwt自定义认证类
分析
drf的认证类定义方式,之前学过--->在认证类中,自己写逻辑
1.入口:
class BookView(APIView):
authentication_classes = [JSONWebTokenAuthentication] # 认证类,drf-jwt提供的
permission_classes = [IsAuthenticated] # 权限类,drf提供的
2.点进去JSONWebTokenAuthentication:from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
...
3.发现:JSONWebTokenAuthentication类中没有authenticate方法,在BaseJSONWebTokenAuthentication中找-->点进去
4.BaseJSONWebTokenAuthentication有authenticate方法
class BaseJSONWebTokenAuthentication(BaseAuthentication):
def authenticate(self, request):
jwt_value = self.get_jwt_value(request) # 获取token
if jwt_value is None:
return None
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
user = self.authenticate_credentials(payload)
return (user, jwt_value)
自定义认证类
urls.py
from django.contrib import admin
from django.urls import path
from rest_framework_jwt.views import obtain_jwt_token
from app01.views import BookView,UserView
urlpatterns = [
path('admin/', admin.site.urls),
# path('login/', obtain_jwt_token), # # 登录接口就有了,并且可以签发token,如果使用auth的user表做登录,不需要写额外的代码了
path('books/', BookView.as_view()),
path('login/', UserView.as_view({'post':'login'}))
]
views.py
from .auth import JWTAuthentication
class BookView(APIView):
authentication_classes = [JWTAuthentication] # 认证类,drf-jwt提供的
def get(self,request):
return Response('你看见我了')
auth.py
import jwt
from rest_framework_jwt.utils import jwt_decode_handler
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
from app01.models import User
class JWTAuthentication(BaseAuthentication):
def authenticate(self, request):
token = request.META.get('HTTP_TOKEN')
# 比对
if token is None:
return None
try:
payload = jwt_decode_handler(token) # 反解密之后的payload
# 如果认证通过,payload就可以认为是安全的,我们就可以使用
user_id = payload.get('user_id')
# user= User.objects.get(pk=user_id) # 每个需要登录后,才能访问的接口,都会走这个认证类,一旦走到这个认证类,机会去数据库查询一次数据,会对数据造成压力
# 优化:
user= User(username=payload.get('username'),id = user_id)
except jwt.ExpiredSignature:
msg = ('token超时')
raise AuthenticationFailed(msg)
except jwt.DecodeError:
msg = ('解码失败')
raise AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise AuthenticationFailed()
return (user, token)
drf-jwt的签发源码分析
1.入口:from rest_framework_jwt.views import obtain_jwt_token-->obtain_jwt_token
2.点进obtain_jwt_token
obtain_jwt_token = ObtainJSONWebToken.as_view()
ps:点进ObtainJSONWebToken
3.点进ObtainJSONWebToken
class ObtainJSONWebToken(JSONWebTokenAPIView):
serializer_class = JSONWebTokenSerializer
ps:没有post方法,在父类中找JSONWebTokenAPIView
4.点进JSONWebTokenAPIView
class JSONWebTokenAPIView(APIView):
# 局部禁用掉权限和认证
permission_classes = ()
authentication_classes = ()
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
# serializer=JSONWebTokenSerializer(data=request.data)
if serializer.is_valid(): # 全局钩子在校验用户,生成token
# 从序列化类中取出user
user = serializer.object.get('user') or request.user
# 从序列化类中取出token
"""
object属性是一个字典,其中包含了从JWT中解析出的各种信息,例如token值、用户信息等
"""
token = serializer.object.get('token')
response_data = jwt_response_payload_handler(token, user, request) # 定制返回格式的时候,写的就是这个函数
response = Response(response_data)
if api_settings.JWT_AUTH_COOKIE:
expiration = (datetime.utcnow() +
api_settings.JWT_EXPIRATION_DELTA)
response.set_cookie(api_settings.JWT_AUTH_COOKIE,
token,
expires=expiration,
httponly=True)
return response
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
ps:点进去:get_serializer
5.点进去:get_serializer--->JSONWebTokenAPIView中的方法
def get_serializer(self, *args, **kwargs):
serializer_class = self.get_serializer_class() # JSONWebTokenSerializer()
kwargs['context'] = self.get_serializer_context()
return serializer_class(*args, **kwargs)
7.点进去:get_serializer_class--->JSONWebTokenAPIView中的方法
def get_serializer_class(self):
assert self.serializer_class is not None, (
"'%s' should either include a `serializer_class` attribute, "
"or override the `get_serializer_class()` method."
% self.__class__.__name__)
return self.serializer_class # 从根上找:ObtainJSONWebToken中的serializer_class ---> serializer_class = JSONWebTokenSerializer
8.点进去:JSONWebTokenSerializer
class JSONWebTokenSerializer(Serializer):
def validate(self, attrs): # 全局钩子
# 前端传入的
"""
credentials = {
'username': attrs.get('usernme'),
'password': attrs.get('password')
}
"""
credentials = {
self.username_field: attrs.get(self.username_field),
'password': attrs.get('password')
}
if all(credentials.values()): # 校验credentials中字典的value值是否都不为空
user = authenticate(**credentials)
"""
user=authenticate(username=前端传入的,password=前端传入的)
auth模块的用户名密码认证函数,可以传入用户名密码,去auth的user表中校验用户是否存在
等同于:User.object.filter(username=username,password=加密后的密码).first()
"""
if user:
if not user.is_active:
msg = _('User account is disabled.')
raise serializers.ValidationError(msg)
payload = jwt_payload_handler(user)
return {
'token': jwt_encode_handler(payload),
'user': user
}
else:
msg = _('Unable to log in with provided credentials.')
raise serializers.ValidationError(msg)
else:
msg = _('Must include "{username_field}" and "password".')
msg = msg.format(username_field=self.username_field)
raise serializers.ValidationError(msg)
drf-jwt的认证源码分析
1.入口:from rest_framework_jwt.authentication import JSONWebTokenAuthentication
class BookView(APIView):
authentication_classes = [JSONWebTokenAuthentication] # 认证类,drf-jwt提供的
permission_classes = [IsAuthenticated] # 权限类,drf提供的
2.点进去JSONWebTokenAuthentication,里面没有authenticate方法,在父类中找
class JSONWebTokenAuthentication(BaseJSONWebTokenAuthentication):
www_authenticate_realm = 'api'
def get_jwt_value(self, request):
auth = get_authorization_header(request).split() # ["JWT","token串"]
auth_header_prefix = api_settings.JWT_AUTH_HEADER_PREFIX.lower() # jwt
if not auth:
if api_settings.JWT_AUTH_COOKIE:
return request.COOKIES.get(api_settings.JWT_AUTH_COOKIE)
return None
if smart_text(auth[0].lower()) != auth_header_prefix:
return None
if len(auth) == 1:
msg = _('Invalid Authorization header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid Authorization header. Credentials string '
'should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
return auth[1] # token串
def authenticate_header(self, request):
return '{0} realm="{1}"'.format(api_settings.JWT_AUTH_HEADER_PREFIX, self.www_authenticate_realm) # ('JWT_AUTH_HEADER_PREFIX': 'JWT',api)
3.点进BaseJSONWebTokenAuthentication
class BaseJSONWebTokenAuthentication(BaseAuthentication):
def authenticate(self, request):
jwt_value = self.get_jwt_value(request) # JSONWebTokenAuthentication的对象中有get_jwt_value -->返回token串
if jwt_value is None:
return None
"""
# 如果前端没传,返回None,request.user中就没有当前登录用户
# 如果前端没有携带token,也能进入到视图类的方法中执行,控制不住登录用户
# 所以咱们才加了个权限类,来做控制
"""
try:
payload = jwt_decode_handler(jwt_value)
except jwt.ExpiredSignature:
msg = _('Signature has expired.')
raise exceptions.AuthenticationFailed(msg)
except jwt.DecodeError:
msg = _('Error decoding signature.')
raise exceptions.AuthenticationFailed(msg)
except jwt.InvalidTokenError:
raise exceptions.AuthenticationFailed()
user = self.authenticate_credentials(payload)
return (user, jwt_value)
4.如果用户不携带token,也能认证通过,所以我们必须加个权限类来限制
class IsAuthenticated(BasePermission):
def has_permission(self, request, view):
return bool(request.user and request.user.is_authenticated)