(12条消息) Django实现API Token认证机制 --- Django+restframework+JWT_轩之的博客-CSDN博客_django接口鉴权

Django 实现 API Token认证机制
项目地址:https://github.com/ylpxzx/django_jwt_example

Session鉴权与Token鉴权的区别
传统session认证
HTTP协议是无状态的,而session的主要目的就是给无状态的HTTP协议添加状态保持,通常在浏览器作为客户端的情况下比较通用,需要在服务端去保留用户的认证信息或者会话信息。
流程:

注册账号
登录页面输入账号密码提交表单后,发送请求给服务器
服务器对账号密码进行验证鉴权,验证鉴权通过后,把用户信息记录在服务器端(django_session表中),同时返回给浏览器一个sessionid用来唯一标识这个用户
浏览器将sessionid保存在cookie中,之后浏览器的每次请求都一并将sessionid发送给服务器
服务器根据sessionid与记录的信息做对比以验证身份。
基于session认证所显露的问题:

每个用户经过认证之后,服务端都要做一次记录(保存他的会话状态),以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求必须请求同一台服务器才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
Token认证
Token的主要目的是为了鉴权,同时又不需要考虑CSRF防护以及跨域的问题,多用于第三方提供API的情况下,客户端请求无论是浏览器发起还是其他程序发起都能很好的支持。目前基于Token的鉴权机制几乎已经成了前后端分离架构或者对外提供API访问的鉴权标准。
相较于session的区别:
基于token的鉴权机制类似于http协议也是无状态的,它**不需要在服务端去保留用户的认证信息或者会话信息。**这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录,为应用的扩展提供了便利。
流程:

注册账号
用户使用用户名和密码请求服务器
服务器进行验证用户的信息
服务器通过验证后,发送给用户一个token
客户端存储token,并在每次请求时在请求头附上这个token值
服务端验证token值,并返回数据
Django 用于实现Token认证机制的第三方库
pip install aliyun-python-sdk-core-v3 # 阿里云短信服务sdk
pip install django
pip install djangorestframework
pip install djangorestframework-jwt
1
2
3
4
实现步骤
本例子的项目名为:login_jwt
应用名为:users

扩展系统AbstractUser用户表
users/models.py
自定义的phone_numbers字段用于后续短信验证,相关阿里云短信服务实现可参考上篇文章
from django.db import models
from django.contrib.auth.models import AbstractUser

#继承AbstractUser,对原有的User表进行扩展,记得在setting中修改为AUTH_USER_MODEL = 'users.LoginUser'
class LoginUser(AbstractUser):
'''
用户表
'''
phone_numbers = models.CharField(verbose_name='手机号', unique=True,max_length=11, default='')

def __str__(self):
return self.username
1
2
3
4
5
6
7
8
9
10
11
12
login_jwt/settings.py
AUTH_USER_MODEL = 'users.LoginUser' # 扩展系统的用户表后记得添加此行
1
定义序列器
用于对提交数据进行序列化和验证

在名为users的应用app创建serializers.py文件
import re
from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from django.contrib.auth.hashers import make_password
from .models import LoginUser
from django.core.cache import cache

def re_phone(phone):
# 检验手机号是否符合标准格式
ret = re.match(r"^1[1-8]\d{9}$", phone)
if ret:
return True
return False

class SmsSerializer(serializers.ModelSerializer):
'''
在获取短信验证码前,需对提交的phone字段进行检查,查看是否已注册或格式不正确
'''
phone = serializers.CharField(required=True)
class Meta:
model = LoginUser
fields = ('phone',)
def validate_phone(self,phone):
'''
手机号验证
:return:
'''
# validate_phone格式为validate_字段,检验指定字段
if LoginUser.objects.filter(phone_numbers=phone).count():
raise ValidationError('手机号码已经注册')
if not re_phone(phone):
raise ValidationError('手机号码格式错误')
return phone

class RegisterSerializer(serializers.ModelSerializer):
'''
手机获取到验证码后,可进行注册操作,该序列器规定了所要提交的字段fields,并对提交数据进行检查
'''
phone_numbers = serializers.CharField(required=True)
pwd2 = serializers.CharField(max_length=256,min_length=4,write_only=True)
code = serializers.CharField(required=True)

class Meta:
model = LoginUser
# 'username', 'password'是系统用户表中已经存在的,系统会自动对用户输入的username进行检查
fields = ('username', 'password', 'pwd2', 'phone_numbers', 'code')

def validate(self, attrs):
# validate对所有字段attrs进行自定义检验
print(attrs['code'])
if not re_phone(attrs['phone_numbers']):
raise ValidationError('手机号码格式错误')

# 获取redis的数据
sms_code = cache.get(attrs['phone_numbers'])
if str(sms_code) != attrs['code']:
raise ValidationError('验证码错误或过期')
if attrs['pwd2'] != attrs['password']:
raise ValidationError('两次密码输入不一致')
del attrs['pwd2']
del attrs['code']
attrs['password'] = make_password(attrs['password'])
return attrs

class OrderSerializer(serializers.Serializer):
'''
该序列器用于测试
'''
title = serializers.CharField()
name = serializers.CharField()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
定义api路径
自行创建urls.py文件:users/urls.py
from django.conf.urls import url
from .views import *
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
url(r'^sms/',SmsView.as_view()), # 短信发送api
url(r'^register/',RegisterView.as_view()), # 账号注册api
url(r'^index/',Order.as_view()), # 测试api
url(r'^api-jwt-auth/',obtain_jwt_token), # jwt的认证接口(路径可自定义任意命名)
]
1
2
3
4
5
6
7
8
9
login_jwt/urls.py
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('api/', include('users.urls')),
]
1
2
3
4
5
6
定义APIView视图
users/views.py
import random
from rest_framework.views import APIView
from .serializers import *
from rest_framework.response import Response
# from .models import UserToken
from rest_framework_jwt.settings import api_settings
from conf.aliyun_api import AliYunSms

class SmsView(APIView):
'''
发送验证码
'''
authentication_classes = [] # 因为后续会在settings.py中设置全局鉴权,而且发送短信验证码不需要登录认证,所以这里设置为[],跳过鉴权
permission_classes = [] # 同理
def post(self,request, *args, **kwargs):
serializer = SmsSerializer(data=request.data)
if serializer.is_valid():
code = (random.randint(1000, 100000))

response = {
'msg':'手机号格式正确,已发送验证码,注意查收',
'next_url':{
'url':'api/register',
'methond':'POST',
'form-data':{
'username':'用户名',
'phone':'手机号',
'password':'密码',
'password2':'确认密码',
'code':'验证码'
}
}
}
phone = serializer.data['phone']
response['phone'] = phone
response['code'] = code # 记得后续删除该行,避免泄露验证码,目前只用于方便查看验证码

cache.set(phone, code, 150)

# # 发送短信验证
# params = "{'code':%d}" % code
# sms_obj = AliYunSms(phone, params)
# res_obj = sms_obj.send()
# print('发送结果:',res_obj)

return Response(response,status=200)
return Response(serializer.errors,status=400)

class RegisterView(APIView):
authentication_classes = []
permission_classes = []

def post(self,request, *args, **kwargs):
serializer = RegisterSerializer(data=request.data)
if serializer.is_valid():
serializer.save() # 保留注册数据,即在数据库中创建了用户

response = {
'msg':'用户注册成功',
'next_url':{
'url':'api/api-jwt-auth/',
'form-data':{
'username': '用户名',
'password': '密码'
}
}
}
return Response(response,status=200)
return Response(serializer.errors,status=400)

class Order(APIView):
# 访问Order视图时时,要加上请求头,请求头的键为:authorization,值为:jwt空格token
def get(self, request):
ret = {'code': 1000, 'msg': '成功GET进来了', 'data': None}
ret['data'] = '欢迎使用本系统'
return Response(ret)

def post(self,request):
order = OrderSerializer(data=request.data)
if order.is_valid():
print(order)
ret = {'code': 1000, 'msg': '成功POST进来了', 'data': order.data}
return Response(ret)
return Response(order.errors,status=400)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
settings.py配置
配置INSTALLED_APPS
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'rest_framework', # 添加该行
'users',
]
1
2
3
4
5
6
7
8
9
10
配置Redis缓存
用于验证码的存活期不长,所以存入Redis,保存时间过期后删除。
# 配置redis缓存,短时间存储手机验证码
CAHES = {
"default": {
"BACKEND": "django_redis.cache.RedisCache",
"LOCATION": 'redis://127.0.0.1:6379',
"OPTIONS": {
"CLIENT_CLASS": "django_redis.client.DefaultClient",
# "PASSWORD": "密码",
"DECODE_RESPONSES":True
}
},
}
1
2
3
4
5
6
7
8
9
10
11
12
全局认证配置
# 全局认证
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
# 设置访问权限为只读
'rest_framework.permissions.IsAuthenticatedOrReadOnly',
# 设置访问权限为必须是用户
'rest_framework.permissions.IsAuthenticated',
),

'DEFAULT_AUTHENTICATION_CLASSES': (
# 自上而下认证
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
),
}

import datetime
JWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300), # 设置 JWT Token 的有效时间
'JWT_AUTH_HEADER_PREFIX': 'JWT', # 设置 请求头中的前缀
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
数据库迁移
python manage.py makemigrations
python manage.py migrate
1
2
运行
python manage.py runserver
1
请求过程演示
采用postman对api进行请求测试
将下面示例图中的请求链接{{url}}替换为http://127.0.0.1:8000/

短信发送api
发送成功后,会获取到code字段,用于注册api注册验证。在生产环境下,记得在返回的数据中去除该字段

注册api

获取token

测试:请求头有无加token的情况
请求头不加token,请求失败

请求头附加token
格式
key:authorization
value:jwt空格token

————————————————
版权声明:本文为CSDN博主「y°x」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42278240/article/details/106379572

发表评论

邮箱地址不会被公开。 必填项已用*标注