三大认证
前戏准备
RBAC基于角色权限访问控制
六表
其中的User与Permission关系表主要解决角色表可能不能满足特殊情况下的用户权限
content_type
给Django中的所有模块中的所有表进行编号存储到content_type表中
应用一
django的content_type存放模块名和表名,没增加一张表会在表内多一条记录
应用二
解决什么问题
一个表和多个表进行关联,但具体随着业务的加深,表不断的增加,关联的数量不断的增加,怎么通过一开始通过表的设计后,不在后期在修改表,彻底的解决这个问题呢(
三大认证
三大认证源码位置
def initial(self, request, *args, **kwargs):
"""
Runs anything that needs to occur prior to calling the method handler.
"""
self.format_kwarg = self.get_format_suffix(**kwargs)
# Perform content negotiation and store the accepted info on the request
neg = self.perform_content_negotiation(request)
request.accepted_renderer, request.accepted_media_type = neg
# Determine the API version, if versioning is in use.
version, scheme = self.determine_version(request, *args, **kwargs)
request.version, request.versioning_scheme = version, scheme
#三大认证
# Ensure that the incoming request is permitted
self.perform_authentication(request)
self.check_permissions(request)
self.check_throttles(request)
#认证模块:校验用户是否登录
self.perform_authentication(request)
#权限模块:校验用户是否拥有权限
self.check_permissions(request)
#频率模块:访问接口的次数在设定时间范围是否过快(有多种配置)
self.check_throttles(request)
admin密文密码添加配置
from django.contrib import admin
from . import models
# admin注册自定义User表:密文操作密码
from django.contrib.auth.admin import UserAdmin as AuthUserAdmin
class UserAdmin(AuthUserAdmin):
add_fieldsets = (
(None, {
'classes': ('wide',),
# 添加用户界面可操作的字段
'fields': ('username', 'password1', 'password2', 'mobile', 'email', 'is_staff', 'is_active'),
}),
)
#显示的字段
list_display = ('username', 'mobile', 'email', 'is_staff', 'is_active')
# 明文操作密码,admin可视化添加的用户密码都是明文,登录时用的是密文,所以用户无法登录
# admin.site.register(models.User)
admin.site.register(models.User, UserAdmin)
认证模块
验证
游客:不带token
非法用户:错误token(抛出异常)
登录用户:正确token(返回长度为2的元组,第一个为user对象,第二个为None)
三大认证
默认配置
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication'
],
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny',
],
认证模块
认证模块工作原理
1)继承BaseAuthentication类,重写authenticate方法
2)认证规则(authenticate方法实现体):
没有携带认证信息,直接返回None => 游客
有认证信息,校验失败,抛异常 => 非法用户
有认证信息,校验出User对象 => 合法用户
def authenticate(self, request):
"""
Returns a `User` if the request session currently has a logged in user.
Otherwise returns `None`.
"""
# Get the session-based user from the underlying HttpRequest object
user = getattr(request._request, 'user', None)
# Unauthenticated, CSRF validation not required
if not user or not user.is_active:
return None
#重新启用csrf
self.enforce_csrf(request)
# CSRF passed with authenticated user
return (user, None)
authorization
def authenticate(self, request):
"""
Returns a `User` if a correct username and password have been supplied
using HTTP Basic authentication. Otherwise returns `None`.
"""
#从请求头中获取认证字符串
auth = get_authorization_header(request).split()
#如果没有字符串或者值的key不为basic返回None
if not auth or auth[0].lower() != b'basic':
return None
#只有key,报错
if len(auth) == 1:
msg = _('Invalid basic header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
#大于2报错
elif len(auth) > 2:
msg = _('Invalid basic header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
auth_parts = base64.b64decode(auth[1]).decode(HTTP_HEADER_ENCODING).partition(':')
except (TypeError, UnicodeDecodeError, binascii.Error):
msg = _('Invalid basic header. Credentials not correctly base64 encoded.')
raise exceptions.AuthenticationFailed(msg)
userid, password = auth_parts[0], auth_parts[2]
return self.authenticate_credentials(userid, password, request)
权限模块
权限模块工作原理
1)继承BasePermission类,重写has_permission方法
2)权限规则(has_permission方法实现体):
返回True,代表有权限
返回False,代表无权限
class IsAuthenticated(BasePermission):
"""
Allows access only to authenticated users.
"""
def has_permission(self, request, view):
#有用户信息并且通过认证
return bool(request.user and request.user.is_authenticated)
频率认证模块
自定义频率类
1.定义类继承SimpleRateThrottle,重写get_cache_key方法,设置scope类属性
2.scope就是一个认证字符串,在配置文件中配置scope字符串对应的频率设置
3.get_cache_key的返回值是字符串,该字符串是缓存访问次数的缓存key(注意要设置每个用户有不同的key,否则公用一个)
from rest_framework.throttling import SimpleRateThrottle
class ThreeTimeUserThrottle(SimpleRateThrottle):
scope = 'three'
#使用用户的id使每个用户有不同的key
def get_cache_key(self, request, view):
return 'throttle:user_%s' % (request.user.id)
settings配置
REST_FRAMEWORK = {
# 频率设置
'DEFAULT_THROTTLE_RATES': {
'three': '3/min',
},
}
局部配置
from rest_framework.permissions import IsAuthenticated
from utils.throttles import ThreeTimeUserThrottle
class UserCenterAPIView(APIView):
# 认证全局配置吗,权限局部配置
# authentication_classes = []
permission_classes = [IsAuthenticated]
# 频率模块局部配置
throttle_classes = [ThreeTimeUserThrottle]
def get(self, request, *args, **kwargs):
user = request.user
serializer = serializers.UserModelSerializer(user)
return APIResponse(data=serializer.data)
登录和查询数据权限案例
涉及内容
校验数据生成token
手写认证器(解析token)
手写权限认证
登录模块
view.py
class LoginAPIView(APIView):
# 登录接口一定要做:局部禁用 认证 与 权限 校验,都没登录验证啥
authentication_classes = []
permission_classes = []
def post(self, request, *args, **kwargs):
#这里要注意数据不能存到数据库里面,这是登录
serializer = serializers.LoginModelSerializer(data=request.data)
#校验数据(校验还做了生成token等操作)设置自动抛错
serializer.is_valid(raise_exception=True)
#返回登录信息
return APIResponse(data={
'username': serializer.user.username,
'token': serializer.token
})
serializers.py
注意
fields里面不能使用username和password字段,否则会走系统默认校验,会查询数据库,并抛出数据重复的异常(系统以为你要添加数据)
from django.contrib.auth import authenticate
class LoginModelSerializer(ModelSerializer):
usr = serializers.CharField(write_only=True)
pwd = serializers.CharField(write_only=True)
class Meta:
model = models.User
fields = ('usr', 'pwd')
#全局钩子
def validate(self, attrs):
usr = attrs.get('usr')
pwd = attrs.get('pwd')
#使用auth的登录账号密码封装方法,返回用户对象
try:
user_obj = authenticate(username=usr, password=pwd)
except:
raise ValidationError({'user': '提供的用户信息有误'})
# 拓展名称空间,给views拿数据
self.user = user_obj
# 签发token
self.token = _get_token(user_obj)
return attrs
#user转token
# token:用户名(base64加密).用户主键(base64加密).用户名+用户主键+服务器秘钥(md5加密)
# 签发算法:b64encode(用户名).b64encode(用户主键).md5(用户名+用户主键+服务器秘钥)
拆封token:一段 二段 三段
用户名:b64decode(一段)
用户主键:b64decode(二段)
碰撞解密:md5(用户名+用户主键+服务器秘钥) == 三段
"""
def _get_token(obj):
import base64, json, hashlib
#获取系统加密盐
from django.conf import settings
t1 = base64.b64encode(json.dumps({'username': obj.username}).encode()).decode()
t2 = base64.b64encode(json.dumps({'pk': obj.id}).encode()).decode()
t3_json = json.dumps({
'username': obj.username,
'pk': obj.id,
'key': settings.SECRET_KEY
})
t3 = hashlib.md5(t3_json.encode()).hexdigest()
return '%s.%s.%s' % (t1, t2, t3)
登录验证和权限认证模块
思路
这里就要使用认证模块和权限判断了
authentications.py 手写token认证
from rest_framework.authentication import BaseAuthentication
from rest_framework.exceptions import AuthenticationFailed
class TokenAuthentication(BaseAuthentication):
prefix = 'Token'
#重写认证方法
def authenticate(self, request):
# 拿到前台的token
auth = request.META.get('HTTP_AUTHORIZATION')
# 没有返回None,有进行校验
if not auth:
return None
auth_list = auth.split()
#判断auth_list长度是否为2,并且key为token
if not (len(auth_list) == 2 and auth_list[0].lower() == self.prefix.lower()):
raise AuthenticationFailed('非法用户')
#拿到加密的token值
token = auth_list[1]
# 校验算法,校验失败抛异常
user = _get_obj(token)
# 成功返回(user, token)
return (user, token)
# 校验算法(认证类)与签发算法配套
拆封token:一段 二段 三段
用户名:b64decode(一段)
用户主键:b64decode(二段)
碰撞解密:md5(用户名+用户主键+服务器秘钥) == 三段
import base64, json, hashlib
from django.conf import settings
from api.models import User
def _get_obj(token):
token_list = token.split('.')
#加密字符串是否符合要求
if len(token_list) != 3:
raise AuthenticationFailed('token异常')
#解析出前两段数据username和pk
username = json.loads(base64.b64decode(token_list[0])).get('username')
pk = json.loads(base64.b64decode(token_list[1])).get('pk')
#封装出正确数据第3段数据
md5_dic = {
'username': username,
'pk': pk,
'key': settings.SECRET_KEY
}
#校验第三段数据的正确性
if token_list[2] != hashlib.md5(json.dumps(md5_dic).encode()).hexdigest():
raise AuthenticationFailed('token内容异常')
#查询出真正的登录用户
user_obj = User.objects.get(pk=pk, username=username)
return user_obj
""" 认证类的认证核心规则
def authenticate(self, request):
token = get_token(request)
# 校验token是否正确
try:
user = get_user(token)
except:
raise AuthenticationFailed()
#最后正确,返回登录对象
return (user, token)
"""
permissions.py 权限认证
# 自定义权限类
"""
权限模块工作原理
1)继承BasePermission类,重写has_permission方法
2)权限规则(has_permission方法实现体):
返回True,代表有权限
返回False,代表无权限
"""
from rest_framework.permissions import BasePermission
class SuperUserPermission(BasePermission):
def has_permission(self, request, view):
#return (user, token),user和auth的值是考认证器的
print(request.user)
print(request.auth)
return True
views.py
from utils.authentications import TokenAuthentication
from utils.permissions import SuperUserPermission
class UserListAPIView(ListAPIView):
#配置局部的认证和权限
authentication_classes = [TokenAuthentication]
permission_classes = [SuperUserPermission]
#查询数据
queryset = models.User.objects.filter(is_active=True, is_superuser=False).all()
serializer_class = serializers.UserModelSerializer
def get(self, request, *args, **kwargs):
response = self.list(request, *args, **kwargs)
return APIResponse(data=response.data)