一、权限
权限概念:
在实际开发中,项目中都有后台运营站点,运营站点里面会存在多个管理员,
那么不同的管理员会具备不同的任务和能力,那么要实现这样的管理员功能,那么就需要了解权限机制了。
在开发中, 一般现在流行的权限机制有2种: RBAC[基于角色(分组)的权限认证]和Auth认证[授权认证机制]
我们使用的django框架内部集成的Auth模块实际上就是基于RBAC开发出来的权限认证机制。
- RBAC: 用户表 | 角色表 | 权限表
- Auth: 规则表 | 用户表 | 用户明细表
在开发中,实现RBAC权限机制,一般有2种不同的设计方式:分别是3表和5表,django扩展出6表
基于角色的权限访问控制(Role-Based Access Control)
三表、五表、六表模型如下:
其实我们通过跨表查询完全能够实现查询,但是跨的表太多,会耗费数据库的资源,影响代码的执行效率,因此需要这种多表关系。
二、认证的发展(cookie,session,jwt)
基础的cookie认证:
利用集群redis的认证:
jwt认证:
三、jwt
3.1、简介
1、组成:
header.payload.signature 头.载荷.签名2、距离:
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6Im93ZW4iLCJleHAiOjE1NTgzMDM1NDR9.4j5QypLwufjpqoScwUB9LYiuhYcTw1y4dPrvnv7DUyo3:介绍:
- header:一般存放如何处理token的方式:加密的算法、是否有签名等,头是固定的。
- payload:数据的主体部分:用户信息、发行者、过期时间等
- signature:签名:将header、payload再结合密码盐整体处理一下,以便下次认证。
3.2、工作原理
1) jwt = base64(头部).base64(载荷).hash256(base64(头部).base(载荷).密钥)
2) base64是可逆的算法、hash256是不可逆的算法
3) 密钥是固定的字符串,保存在服务器(django的在配置文件中,有一个SCRECT_KEY)
3.3、drf-jwt
官网:https://github.com/jpadilla/django-rest-framework-jwt
安装:pip install djangorestframework-jwt
使用:
from django.urls import path
# 返回token
from rest_framework_jwt.views import obtain_jwt_token
urlpatterns = [
path('login/', obtain_jwt_token),
]
我在路由里配了这个,jwt就会签发token。可以用postman测试接口。
上面只是自己返回了一个token,如果我们想按照restful规范,那就需要自定义返回的接口数据,需要重写jwt_response_payload_handler()方法
首先在配置文件中配置,自定义认证结果就是你的认证方法所在的文件:
import datetime
JWT_AUTH = {
# 过期时间
'JWT_EXPIRATION_DELTA': datetime.timedelta(days=1),
# 自定义认证结果:见下方序列化user和自定义response
'JWT_RESPONSE_PAYLOAD_HANDLER': 'user.utils.jwt_response_payload_handler',
}
在返回数据时,需要序列化数据:
from rest_framework import serializers
from .models import User
class UserModelSerializer(serializers.ModelSerializer):
"""用户信息序列化器"""
class Meta:
model = User
fields = ["username", "mobile"]
再来看自定义签发token的返回结果:
from .serializers import UserModelSerializer
# rest_framework_jwt中的jwt_response_payload_handler用于返回token的,我们可以重写这个方法自定义序列化返回的信息
def jwt_response_payload_handler(token, user=None, request=None):
return {
'token': token,
# 序列化后的数据
'user': UserModelSerializer(user).data
}
# restful 规范
# return {
# 'status': 0,
# 'msg': 'OK',
# 'data': {
# 'token': token,
# 'username': user.username
# }
# }
现在已经可以返回token了,我们的某些功能需要登录后才能访问,那么就该做一个登录的认证。当用户访问的时候,判断用户的token是否有,是否合法,是否过期等。
# 由于源码的认证有缺陷,因此需要我们自定义
# -*- coding: utf-8 -*-
# @Author : Sunhaojie
# @Time : 2019/7/18 20:51
import jwt
from rest_framework_jwt.settings import api_settings
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):
# 采用drf获取token字段 HTTP_AUTHORIZATION - Authorization
token = get_authorization_header(request)
# 自定义从请求头的某个字段得到认证的token
# token = request.META.get('HTTP_TOKEN', b'')
if not token:
raise AuthenticationFailed('Authorization字段是必需的')
# 可以添加反爬措施,原功能是token有前缀
jwt_value_list = token.split()
if len(jwt_value_list) != 2:
raise AuthenticationFailed('传值长了或短了')
# JWT_AUTH_HEADER_PREFIX在dev.py中配置了,所以找自己的
if jwt_value_list[0].lower().decode() != api_settings.JWT_AUTH_HEADER_PREFIX.lower():
raise AuthenticationFailed('没头,认证失败')
token = jwt_value_list[1]
# drf-jwt认证校验算法
try:
payload = jwt_decode_handler(token)
except jwt.ExpiredSignature:
raise AuthenticationFailed('签名过期,认证失败')
except jwt.InvalidTokenError:
raise AuthenticationFailed('非法用户')
user = self.authenticate_credentials(payload)
# 将认证结果丢给该drf:通过认证的request.user拿到用户对象,request.auth拿到token,是bytes类型
return user, token
在配置文件可以配置全局启用;
REST_FRAMEWORK = {
# 认证模块
'DEFAULT_AUTHENTICATION_CLASSES': (
'user.authentications.JSONWebTokenAuthentication',
),
}
在要访问的视图类可以局部启用或禁用:
# 局部禁用
authentication_classes = []
# 局部启用
from user.authentications import JSONWebTokenAuthentication
authentication_classes = [JSONWebTokenAuthentication]
有些时候,我们会有用户名,邮箱,手机号登录的需求。可以使用正则匹配,继承ModelBackend类,重写authenticate方法,在内部返回user,交给前面的jwt去签发token
# 有时候需要 用户名/邮箱/电话号码 都可以登录,就该用正则进行匹配
from django.contrib.auth.backends import ModelBackend
from .models import User
import re
class JWTModelBackend(ModelBackend):
def authenticate(self, request, username=None, password=None, **kwargs):
"""
:param request:
:param username: 前台传入的用户名
:param password: 前台传入的密码
:param kwargs:
:return:
"""
try:
if re.match(r'^1[3-9]\d{9}$', username):
user = User.objects.get(mobile=username)
elif re.match(r'.*@.*', username):
user = User.objects.get(email=username)
else:
user = User.objects.get(username=username)
except User.DoesNotExist:
return None # 认证失败就返回None即可,jwt就无法删除token
# 用户存在,密码校验通过,是活着的用户 is_active字段为1
if user and user.check_password(password) and self.user_can_authenticate(user):
return user # 认证通过返回用户,交给jwt生成token
将我们的文件位置配置到settings配置文件中:
AUTHENTICATION_BACKENDS = ['user.utils.JWTModelBackend']