博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Django实现API Token认证机制 --- Django+restframework+JWT
阅读量:3924 次
发布时间:2019-05-23

本文共 8757 字,大约阅读时间需要 29 分钟。

Django 实现 API Token认证机制

项目地址:https://github.com/ylpxzx/django_jwt_example

Session鉴权与Token鉴权的区别

传统session认证

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

流程:

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

基于session认证所显露的问题:

  • 每个用户经过认证之后,服务端都要做一次记录(保存他的会话状态),以方便用户下次请求的鉴别,通常而言session都是保存在内存中,而随着认证用户的增多,服务端的开销会明显增大。
  • 扩展性: 用户认证之后,服务端做认证记录,如果认证的记录被保存在内存中的话,这意味着用户下次请求必须请求同一台服务器才能拿到授权的资源,这样在分布式的应用上,相应的限制了负载均衡器的能力。这也意味着限制了应用的扩展能力。
Token认证

Token的主要目的是为了鉴权,同时又不需要考虑CSRF防护以及跨域的问题,多用于第三方提供API的情况下,客户端请求无论是浏览器发起还是其他程序发起都能很好的支持。目前基于Token的鉴权机制几乎已经成了前后端分离架构或者对外提供API访问的鉴权标准。

相较于session的区别:
基于token的鉴权机制类似于http协议也是无状态的,它**不需要在服务端去保留用户的认证信息或者会话信息。**这就意味着基于token认证机制的应用不需要去考虑用户在哪一台服务器登录,为应用的扩展提供了便利。
流程:

  1. 注册账号
  2. 用户使用用户名和密码请求服务器
  3. 服务器进行验证用户的信息
  4. 服务器通过验证后,发送给用户一个token
  5. 客户端存储token,并在每次请求时在请求头附上这个token值
  6. 服务端验证token值,并返回数据

Django 用于实现Token认证机制的第三方库

pip install aliyun-python-sdk-core-v3 # 阿里云短信服务sdkpip install djangopip install djangorestframeworkpip install djangorestframework-jwt

实现步骤

本例子的项目名为:login_jwt

应用名为:users

扩展系统AbstractUser用户表
  • users/models.py
    自定义的phone_numbers字段用于后续短信验证,相关阿里云短信服务实现可参考
from django.db import modelsfrom 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
  • login_jwt/settings.py
AUTH_USER_MODEL = 'users.LoginUser'  # 扩展系统的用户表后记得添加此行
定义序列器

用于对提交数据进行序列化和验证

  • 在名为users的应用app创建serializers.py文件
import refrom rest_framework import serializersfrom rest_framework.exceptions import ValidationErrorfrom django.contrib.auth.hashers import make_passwordfrom .models import LoginUserfrom django.core.cache import cachedef re_phone(phone):	# 检验手机号是否符合标准格式    ret = re.match(r"^1[1-8]\d{9}$", phone)    if ret:        return True    return Falseclass 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 phoneclass 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 attrsclass OrderSerializer(serializers.Serializer):	'''	该序列器用于测试	'''    title = serializers.CharField()    name = serializers.CharField()
定义api路径
  • 自行创建urls.py文件:users/urls.py
from django.conf.urls import urlfrom .views import *from rest_framework_jwt.views import obtain_jwt_tokenurlpatterns = [        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的认证接口(路径可自定义任意命名)]
  • login_jwt/urls.py
from django.contrib import adminfrom django.urls import path,includeurlpatterns = [    path('admin/', admin.site.urls),    path('api/', include('users.urls')),]
定义APIView视图
  • users/views.py
import randomfrom rest_framework.views import APIViewfrom .serializers import *from rest_framework.response import Response# from .models import UserTokenfrom rest_framework_jwt.settings import api_settingsfrom conf.aliyun_api import AliYunSmsclass 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)
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',]
  • 配置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 } },}
  • 全局认证配置
# 全局认证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 datetimeJWT_AUTH = {
'JWT_EXPIRATION_DELTA': datetime.timedelta(seconds=300), # 设置 JWT Token 的有效时间 'JWT_AUTH_HEADER_PREFIX': 'JWT', # 设置 请求头中的前缀}
数据库迁移
python manage.py makemigrationspython manage.py migrate
运行
python manage.py runserver
请求过程演示

采用postman对api进行请求测试

将下面示例图中的请求链接{
{url}}替换为http://127.0.0.1:8000/

  • 短信发送api
    发送成功后,会获取到code字段,用于注册api注册验证。在生产环境下,记得在返回的数据中去除该字段
    在这里插入图片描述
  • 注册api
    在这里插入图片描述
  • 获取token
    在这里插入图片描述
  • 测试:请求头有无加token的情况
  1. 请求头不加token,请求失败
    在这里插入图片描述
  2. 请求头附加token
    格式
    key:authorization
    value:jwt空格token
    在这里插入图片描述

转载地址:http://pgugn.baihongyu.com/

你可能感兴趣的文章
Java实现文件拷贝的4种方法
查看>>
在pb11中将C/S程序转换到B/S的步骤
查看>>
PowerDesigner教程系列(二)概念数据模型
查看>>
从PowerDesigner概念设计模型(CDM)中的3种实体关系说起
查看>>
SQL Server 2000中查询表名
查看>>
第一个go程序连接mysql读取数据
查看>>
一个小示例,对比下go和java
查看>>
struts2 上传excel文件
查看>>
开篇背景
查看>>
进程、线程、纤程
查看>>
代码详解のJava面向对象特性之多态
查看>>
互金平台使用redis分布式锁的场景分析
查看>>
代码详解のArrayList
查看>>
代码详解のLinkedList原码分析和ArrayList比较
查看>>
代码详解のTreeMap实现原理
查看>>
缓存架构设计细节之“淘汰缓存”还是“更新缓存”
查看>>
Java实现两个线程交替打印问题
查看>>
Java序列化的几种特殊情景
查看>>
JSP热部署的实现原理
查看>>
class卸载、热替换和Tomcat的热部署的分析
查看>>