认证权限
认证
现在我们写的接口,会发现一个问题,就是任何人都可以创建数据,都可以修改数据。这样肯定是不行的,我们希望只有数据的创建者才能有权限修改数据。如果不是,只能有读取权限。
还是先创建一个模型类
我们设计一个模型类,让这个模型类与用户关联。
from django.db import models
# Create your models here.
class Game(models.Model):
name = models.CharField(verbose_name='游戏名字', max_length=10)
desc = models.CharField(verbose_name='描述', max_length=20)
user = models.ForeignKey('auth.User', on_delete=models.CASCADE)
然后开始迁移
python manage.py makemigrations
python manage.py migrate
序列化
from rest_framework import serializers
from .models import Game
class GameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
#fields = '__all__'
exclude = ('user',)
视图
from django.shortcuts import render
# Create your views here.
from .models import Game
from rest_framework import generics
from .serializers import GameSerializer
class GameList(generics.ListCreateAPIView):
queryset = Game.objects.all()
serializer_class = GameSerializer
# 重写 创建的时候提供当前登录的用户
def perform_create(self, serializer):
serializer.save(user=self.request.user)
class GameDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Game.objects.all()
serializer_class = GameSerializer
他自己帮我们写好了验证
所以,是用户自己给他们赋的值,赋值完成之后,DRF里面的验证就回去校验数据,合法的话,他把值返回给request,我们再通过request去取值
路由
应用及路由
from django.contrib import admin
from django.urls import path, include
from . import views
urlpatterns = [
path('games/', views.GameList.as_view(), name='game-list'), # 获取或创建
path('games/<int:pk>/', views.GameDetail.as_view(), name='game-detail'), # 查找、更新、删除
]
根及路由
urlpatterns = [
path('admin/', admin.site.urls),
# path('api/',include(route.urls)),
# #这个加上api之后的路由就是http://127.0.0.1:8000/api/students
# path('api/',include('app02.urls'))
#path('api/',include(('app03.urls','app03'),namespace='app03'))
path('api/',include(('app04.urls','app04'),namespace='app04'))
]
去跑程序
解决方法
我们可以在序列化里面添加额外字段
from rest_framework import serializers
from .models import Game
class GameSerializer(serializers.ModelSerializer):
class Meta:
model = Game
#fields = '__all__'
exclude = ('uesr',)
extra_kwargs = {
'user':{'read_only':True}
#user这个字段创建数据的时候就隐藏,但是获取数据的时候会展示
}
这个时候获取数据的时候 就会展示用户
序列化里面有很多字段,要善于使用
权限
现在我们创建数据虽然成功了,但是还有一个问题,
在修改数据的时候会发现其他人认证也可以修改数据,甚至不认证也可以修改成功
这个时候,我们就需要完善一下,设置一个权限:谁创建的这条数据谁就去修改
这个权限类,drf也帮助我们写了
先看一下源码
drf里面的源码
class GameList(generics.ListCreateAPIView):
查看ListCreateAPIView的源码
进去之后再点进去看Genneric…的源码
然后再看源码
点进去检查权限的源码之后
再点进去
是一个列表推导式,再点进去
点进去看默认值是什么
这个权限表示对任何人开发,所以我们现在要自己写一个权限
解决不认证就能修改的问题
from rest_framework import permissions
class GameDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Game.objects.all()
serializer_class = GameSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly]
#进去源码会发现,这个是先看验证,然后再看user有没有值,如果没有的话,只有只读权限
解决谁认证都可以修改的问题
那我们需要自己写一个类
创建一个permissions.py,写如下代码:
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in permissions.SAFE_METHODS:
return True
return obj.user == request.user
这些代码照着源码写,IsAuthenticatedOrReadOnly就看着这个里面的改改
上面代码的意思代表判断当前的数据创建用户是否跟当前登录用户是否一个人。request就表示当前的数据。
并在更新视图上加上该权限
from .permissions import *
class GameDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Game.objects.all()
serializer_class = GameSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly,IsOwnerOrReadOnly]
#进去源码会发现,这个是先看验证,然后再看user有没有值,如果没有的话,只有只读权限
在视图里面加上这个权限
在源码里面,有两个方法
has_permission这个是对数据的获取的一个方法
has_object_permission这个是对数据有没有更新和删除的权限
路由里面显示登录的代码
path('api-auth/', include('rest_framework.urls', namespace='rest_framework'))
这个会在界面显示登录,Log in
DRF提供的四种认证
- BasicAuthentication:此身份验证方案使用HTTP基本身份验证,根据用户的用户名和密码进行签名。基本身份验证通常仅适用于测试。
- TokenAuthentication: 此身份验证方案使用基于令牌的简单HTTP身份验证方案。令牌认证适用于客户端 - 服务器设置,例如本机桌面和移动客户端。
- SessionAuthentication: 此身份验证方案使用Django的默认会话后端进行身份验证。会话身份验证适用于与您的网站在同一会话上下文中运行的AJAX客户端。
- RemoteUserAuthentication:此身份验证方案允许您将身份验证委派给Web服务器,该服务器设置
REMOTE_USER
环境变量。
在drf里面默认的是使用BasicAuthentication 和 SessionAuthentication
BasicAuthentication
先写一个接口
直接写一个视图
from django.shortcuts import render
from rest_framework.views import APIView
from django.http import JsonResponse
# Create your views here.
#APIVie可以更好的扩展功能
class CartView(APIView):
def get(self,request,*args,**kwargs):
ctx = {
"code":1,
"msg":"ok",
"data":{
"goods":[
{
"name": "苹果",
"price": 12
},
{
"name": "苹果111",
"price": 1234
},
]
}
}
return JsonResponse(ctx)
配一个路由
from django.contrib import admin
from django.urls import path, include
from . import views
urlpatterns = [
path('carts/', views.CartView.as_view(), name='cart-list'),
]
跑一下
接口通了
现在假如这个页面需要一个浏览器的验证,要怎么做
from django.shortcuts import render
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BasicAuthentication
from rest_framework.permissions import IsAuthenticated
#判断是否登录
# Create your views here.
#APIVie可以更好的扩展功能
class CartView(APIView):
#看APIView的源码,我们可以重写一下authentication_classes
authentication_classes = [BasicAuthentication]
#让这个接口只支持浏览器验证
#然后再设置一个权限,判断它有没有登录,如果登录的话,再让他访问
permission_classes = [IsAuthenticated]
#可以进去源码看看,他是可以检查权限,你是不是登录过的
def get(self,request,*args,**kwargs):
ctx = {
"code":1,
"msg":"ok",
"data":{
"goods":[
{
"name": "苹果",
"price": 12
},
{
"name": "苹果111",
"price": 1234
},
]
}
}
return JsonResponse(ctx)
然后运行
要有数据啊,没有的创建一个
在pycharm中创建数据
python manage.py createsuperuser
创建一个
然后再去运行
对BasicAuthentication身份认证的总结
Http Basic 是一种比较简单的身份认证方式。 在 Http header 中添加键值对 Authorization: Basic xxx (xxx 是 username:passowrd base64 值)。而Base64 的解码是非常方便的,如果不使用 Https ,相当于是帐号密码直接暴露在请求中。
GET /auth/basic/ HTTP/1.1
Host: xxxxx
Authorization: Basic em1rOjEyMzQ1Ng==
TokenAuthentication
使用这个方法需要配置
TokenAuthentication的配置
# settings.py
INSTALLED_APPS = (
...
'rest_framework.authtoken'
)
这个配置完成之后要进行迁移
直接迁移就行,因为他要生成数据库表文件
python manage.py makemigrations
python manage.py migrate
迁移完成之后,还得来一个全局配置
这个看自己情况而定,如果是登录的页面很多,那你可能就需要一个全局配置
在setting里面写上就可以
# 全局配置
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.TokenAuthentication',
),
}
如果只有一个的话,大可不必
如果写上了全局,但是类视图又不需要验证怎么办?
authentication_classes = []
可以在视图的类里面把这个置为空列表,就不需要验证
TokenAuthentication验证
设置好之后,就在视图里面写代码
from django.shortcuts import render
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BasicAuthentication,TokenAuthentication
from rest_framework.permissions import IsAuthenticated
#判断是否登录
# Create your views here.
#APIVie可以更好的扩展功能
class CartView(APIView):
#看APIView的源码,我们可以重写一下authentication_classes
authentication_classes = [BasicAuthentication,TokenAuthentication]
#让这个接口只支持浏览器验证,现在我们把TokenAuthentication也加上
#然后再设置一个权限,判断它有没有登录,如果登录的话,再让他访问
permission_classes = [IsAuthenticated]
#可以进去源码看看,他是可以检查权限,你是不是登录过的
def get(self,request,*args,**kwargs):
ctx = {
"code":1,
"msg":"ok",
"data":{
"goods":[
{
"name": "苹果",
"price": 12
},
{
"name": "苹果111",
"price": 1234
},
]
}
}
return JsonResponse(ctx)
TokenAuthentication这个验证是由路由生成的,所以我们还得在路由里面写点东西
from rest_framework.authtoken.views import obtain_auth_token
path('api-token-auth/', obtain_auth_token)
我是把这两行代码写到了根及路由下面,当然也可以在应用及下
然后开始运行
这个要在postman里面运行,因为它不支持get方法
创建一个数据,send之后
会返回一个token
然后这个token会返回给前端,前端拿到之后,需要访问这个接口(用get方式去访问)
我们在访问的时候,需要把token再传回去 ,看TokenAuthentication源码,有一个格式
Authorization: Token 401f7ac837da42b97f613d789819ff93537bee6a
是这样的格式,(注意:其实Token前面有两个空格的,博客上不明显)
那我们把刚刚拿到的token复制出来 “token”: “b4af60f5612db993f9c5d74cd88d8385a44c7d4a”
把格式里面的值换成我们的再传进去就好了
send之后
但是换成别的就访问不了了
TokenAuthentication的缺点
token值在分布式系统中会有问题产生,
并且在数据库里面的数据没有过期时间,一旦被窃取,任何人都可以使用
SessionAuthentication
session验证是怎么生成的?
from django.contrib.auth import login
可以点进去看一下源码,login里面会生成session值
也就是说我们必须调动这个函数去生成session
####### 我们先在view视图当中加上这个验证
from django.shortcuts import render
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BasicAuthentication,TokenAuthentication,SessionAuthentication
from rest_framework.permissions import IsAuthenticated
#判断是否登录
# Create your views here.
#APIVie可以更好的扩展功能
class CartView(APIView):
#看APIView的源码,我们可以重写一下authentication_classes
authentication_classes = [BasicAuthentication,TokenAuthentication,SessionAuthentication]
#让这个接口只支持浏览器验证,现在我们把TokenAuthentication也加上
#然后再设置一个权限,判断它有没有登录,如果登录的话,再让他访问
permission_classes = [IsAuthenticated]
#可以进去源码看看,他是可以检查权限,你是不是登录过的
def get(self,request,*args,**kwargs):
ctx = {
"code":1,
"msg":"ok",
"data":{
"goods":[
{
"name": "苹果",
"price": 12
},
{
"name": "苹果111",
"price": 1234
},
]
}
}
return JsonResponse(ctx)
我们可以去访问后台,访问后台一定会调用login,登入进去之后,再去访问文章页就可以访问了。
F12,cookie就会有一个csrftoken值,拿出来之后,在postman上面测试一下。
但是在postman上测试的时候呢,你会发现你把cookie里面csrftooken里面的值换了,换成别的也照样额可以访问,这是因为csrf验证只针对post、delete这些方法,当把视图中的方法改成post再去测试一篇,就会发现
验证无效,这个时候只有正确的csrftoken才可以访问。但是在postman上不好生成,用Ajax比较好生成。
而且这个方法只有在post方法上面才会验证,get方法不需要
要调用的话,一定要传一个正确的csrftoken
自定义一个验证
模型
class User(models.Model):
username = models.CharField(max_length=32, unique=True)
password = models.CharField(max_length=64)
class UserToken(models.Model):
user = models.OneToOneField('User', models.CASCADE)
token = models.CharField(max_length=64)
迁移生成ssqlite数据库表格
python manage.py makemigrations
python manage.py migrate
这两个模型类,一般是一对一的,一个user对应一个token。
我们先写一个登录视图,登录成功之后再去验证返回一个token.
逻辑
这块的逻辑是,去登陆成功之后之后,先去获取数据验证,如果根据账号密码查找出来了用户,就把密码加密,并且生成一个token返回给用户。如果没有用户,就返回,账户或者密码错误。
视图(加密密码)
from django.shortcuts import render
from rest_framework.views import APIView
from django.http import JsonResponse
from rest_framework.authentication import BasicAuthentication,TokenAuthentication,SessionAuthentication
from rest_framework.permissions import IsAuthenticated
#判断是否登录
from .models import User,UserToken
import time
import hashlib
# Create your views here.
def get_md5(user):
ctime = str(time.time())
m = hashlib.md5(bytes(user, encoding='utf-8'))
m.update(bytes(ctime, encoding='utf-8'))
return m.hexdigest()
class LodinView(APIView):
def post(self,request,*args,**kwargs):
ret = {'code': 1, 'msg': None, 'data': {}}
user = request.POST.get('username')
pwd = request.POST.get('password')
obj = User.objects.filter(username=user, password=pwd).first()
if not obj:
ret['code'] = -1
ret['msg'] = "用户名或密码错误"
token = get_md5(user)
UserToken.objects.update_or_create(user=obj, defaults={'token': token})
ret['token'] = token
return JsonResponse(ret)
#APIVie可以更好的扩展功能
class CartView(APIView):
#看APIView的源码,我们可以重写一下authentication_classes
authentication_classes = [BasicAuthentication,TokenAuthentication,SessionAuthentication]
#让这个接口只支持浏览器验证,现在我们把TokenAuthentication也加上
#然后再设置一个权限,判断它有没有登录,如果登录的话,再让他访问
permission_classes = [IsAuthenticated]
#可以进去源码看看,他是可以检查权限,你是不是登录过的
def get(self,request,*args,**kwargs):
ctx = {
"code":1,
"msg":"ok",
"data":{
"goods":[
{
"name": "苹果",
"price": 12
},
{
"name": "苹果111",
"price": 1234
},
]
}
}
return JsonResponse(ctx)
路由
from django.contrib import admin
from django.urls import path, include
from . import views
urlpatterns = [
path('carts/', views.CartView.as_view(), name='cart-list'),
path('login/', views.LodinView.as_view(), name='login'),
]
这个之后一定要自己去sqlite里面传值。因为我们使用的是Django里面的用户系统,但是现在我们自己写了一个用户系统。这样的话,我们生成的那个表里面就不会有数据,一定要自己手动的创建一个数据。
然后再去访问登录接口,就会返回一个token。
之后我们把token传入到请求头里面就可以访问购物车页了,但是我们得先写一个验证。自己写一个验证。
否则谁都可以访问修改,我们写一个token验证,验证通过之后再给权限。
在视图里面添加自己写的验证以及导入自己的权限包
from django.shortcuts import render
from rest_framework.views import APIView
from django.http import JsonResponse
from .permission import MyPermission
from rest_framework.authentication import BasicAuthentication,TokenAuthentication,SessionAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework import exceptions
#判断是否登录
from .models import User,UserToken
import time
import hashlib
# Create your views here.
def get_md5(user):
ctime = str(time.time())
m = hashlib.md5(bytes(user, encoding='utf-8'))
m.update(bytes(ctime, encoding='utf-8'))
return m.hexdigest()
class LodinView(APIView):
def post(self,request,*args,**kwargs):
ret = {'code': 1, 'msg': None, 'data': {}}
user = request.POST.get('username')
pwd = request.POST.get('password')
obj = User.objects.filter(username=user, password=pwd).first()
if not obj:
ret['code'] = -1
ret['msg'] = "用户名或密码错误"
token = get_md5(user)
UserToken.objects.update_or_create(user=obj, defaults={'token': token})
ret['token'] = token
return JsonResponse(ret)
class MyAuthentication(BasicAuthentication):
def authenticate(self, request):
#一定要重写这个方法,不然报错
token = request.META.get('HTTP_TOKEN')#取token这个值,注意括号里面的写法,大写
obj = UserToken.objects.filter(token=token).first()
if not obj:
raise exceptions.AuthenticationFailed('验证不通过')
else:
return (obj.user,obj)
#return返回的一定是个元组。!!!!!这个记住了。
#第一个返回的是user,原因IsAuthenticated里面判断是user,所以你得给他值,让他去判断,
#第一个值就赋值给了request
#第二个值就是token
#APIVie可以更好的扩展功能
class CartView(APIView):
#看APIView的源码,我们可以重写一下authentication_classes
#authentication_classes = [BasicAuthentication,TokenAuthentication,SessionAuthentication]
#让这个接口只支持浏览器验证,现在我们把TokenAuthentication也加上
#把上面的都注释了
#使用我们自己定义的验证
authentication_classes = [MyAuthentication]
#然后再设置一个权限,判断它有没有登录,如果登录的话,再让他访问
#permission_classes = [IsAuthenticated]
#可以进去源码看看,他是可以检查权限,你是不是登录过的
#不使用上面的权限的原因是,它里面request.user.is_authenticated这个属性我们没有没有办法判断
#所以除了验证,我们还得需要重新写一个权限
permission_classes = [MyPermission]
def get(self,request,*args,**kwargs):
ctx = {
"code":1,
"msg":"ok",
"data":{
"goods":[
{
"name": "苹果",
"price": 12
},
{
"name": "苹果111",
"price": 1234
},
]
}
}
return JsonResponse(ctx)
自己写的权限包,也就是上面视图里面导入的那个
from rest_framework.permissions import BasePermission
class MyPermission(BasePermission):
def has_permission(self, request, view):
if not request.user:
return False
return True
为什么要自己写权限上面的视图代码里有提到。
访问购物车页
我们试试改一下token的值