自学python没有证书_[Python自学] DRF (3) (认证组件)

一、实现登录验证

1.创建User和Token表

User表用作用户名密码认证,Token表用于存放用户每次成功登陆后的随机Token。

在models.py中添加以下两张表:

#用户表

classUser(models.Model):

username= models.CharField(max_length=32)

password= models.CharField(max_length=32)#token表

classToken(models.Model):

user= models.OneToOneField("User", on_delete=models.CASCADE)

token= models.CharField(max_length=128)

执行命令,生成数据库表:

python manage.py makemigrations

python manage.py migrate

2.实现登录验证操作

添加路由条目:

urlpatterns =[

path('admin/', admin.site.urls),

re_path('^publishes/$', views.PublishView.as_view(), name="publish"),

re_path('^publishes/(?P\d+)/$', views.PublishDetailView.as_view(), name="publishdetail"),

re_path('^books/$', views.BookView.as_view(), name="book"),

re_path('^books/(?P\d+)/$', views.BookDetailView.as_view(), name="bookdetail"),

re_path('^authors/$', views.AuthorViewSet.as_view({"get": "list", "post": "create"}), name="author"),

re_path('^authors/(?P\d+)/$', views.AuthorViewSet.as_view(

{"get": "retrieve", "put": "update", "patch": "partial_update", "delete": "destroy"}), name="authordetail"),

re_path('^login/$', views.LoginView.as_view(), name="login"),

]

实现视图类LoginView:

#导入User和Token的model类

from .models importUserfrom .models importToken#生成一个随机token,username和ctime的MD5加密值

defget_random_str(user):importhashlibimporttime#获取当前时间

ctime =str(time.time())#username的md5

md5 = hashlib.md5(bytes(user, encoding='utf-8'))#加上ctime

md5.update(bytes(ctime, encoding='utf-8'))returnmd5.hexdigest()classLoginView(APIView):defpost(self, request):

res= {'code': 1000, "msg": None}try:#从post请求中获取用户提交的用户名和密码

username = request.data.get("username")

password= request.data.get("password")#判断数据库中的数据是否匹配

user_obj = User.objects.filter(username=username, password=password).first()#如果不匹配,返回登录失败

if notuser_obj:

res['code'] = 1001res['msg'] = "用户名或密码错误"

else:#如果匹配,则生成一个随机token

token =get_random_str(username)#如果token已经存在,则更新,如果不存在,则创建

Token.objects.update_or_create(user=user_obj, defaults={'token': token})

res["token"] =tokenexceptException as e:

res['code'] = 1002res['msg'] =ereturn HttpResponse(json.dumps(res))

二、实现token认证

1.实现token认证

要实现认证,只需要在需要认证的视图类中添加 authentication_classes 列表。restframe认证组件会自动去该列表中寻找认证使用的类(类由我们来定义)。例如BookView视图类中:

classBookView(APIView):

authentication_classes=[TokenAuth,]pass

查看restframework调用authentication_classes中类的源码,可以看到TokenAuth中必须实现 authenticate方法,以及 authenticate_header 方法:

classTokenAuth(object):#认证token过程

defauthenticate(self, request):

token= request.GET.get("token")

token_obj= Token.objects.filter(token=token).first()if nottoken_obj:raise exceptions.AuthenticationFailed("验证失败")return(token_obj.user, token_obj)defauthenticate_header(self, request):return None

或者,继承 BaseAuthentication也可以:

from rest_framework.authentication importBaseAuthenticationclassTokenAuth(BaseAuthentication):#认证token过程

defauthenticate(self, request):

token= request.GET.get("token")

token_obj= Token.objects.filter(token=token).first()if nottoken_obj:raise exceptions.AuthenticationFailed("验证失败")return (token_obj.user, token_obj)

然后应用于BookView:

classBookView(APIView):

authentication_classes=[TokenAuth]defget(self, request):

book_list=Book.objects.all()

bs= BookModelSerializers(book_list, many=True, context={'request': request})returnResponse(bs.data)defpost(self, request):

bs= BookModelSerializers(data=request.data)ifbs.is_valid():

bs.save()returnResponse(bs.data)else:return Response(bs.errors)

这样,我们想要通过GET请求获取book数据的时候,就需要先访问login页面,获取token,然后在GET请求中附带token,才能正确获取book数据:

2.测试

POST请求访问http://127.0.0.1:8000/login/,附带用户名和密码,进行登录验证:

获得返回值:

{"code": 1000, "msg": null, "token": "91dc33a308cd4e8b04e14bb3d23d492b"}

然后GET请求访问http://127.0.0.1:8000/books/?token=91dc33a308cd4e8b04e14bb3d23d492b:

获得返回结果:

[{"id":8,"publish":"http://127.0.0.1:8000/publishes/1/","title":"Python2标准库3","price":99,"pub_date":"2012-11-20T13:03:33Z","authors":[1,2]},{"id":9,"publish":"http://127.0.0.1:8000/publishes/1/","title":"Python2标准库4","price":99,"pub_date":"2012-11-20T13:03:33Z","authors":[1,2]},{"id":10,"publish":"http://127.0.0.1:8000/publishes/1/","title":"Python2标准库5","price":99,"pub_date":"2012-11-20T13:03:33Z","authors":[1,2]},{"id":11,"publish":"http://127.0.0.1:8000/publishes/1/","title":"Python2标准库6","price":99,"pub_date":"2012-11-20T13:03:33Z","authors":[1,2]},{"id":12,"publish":"http://127.0.0.1:8000/publishes/1/","title":"Python2标准库7","price":99,"pub_date":"2012-11-20T13:03:33Z","authors":[1,2]},{"id":13,"publish":"http://127.0.0.1:8000/publishes/1/","title":"Python2标准库","price":99,"pub_date":"2012-11-20T13:03:33Z","authors":[1,2]},{"id":14,"publish":"http://127.0.0.1:8000/publishes/1/","title":"Python3","price":99,"pub_date":"2020-01-20T13:03:04Z","authors":[3]},{"id":15,"publish":"http://127.0.0.1:8000/publishes/1/","title":"JAVA","price":99,"pub_date":"2020-01-20T13:03:04Z","authors":[3]},{"id":16,"publish":"http://127.0.0.1:8000/publishes/1/","title":"JAVA","price":99,"pub_date":"2020-01-20T13:03:04Z","authors":[3]},{"id":17,"publish":"http://127.0.0.1:8000/publishes/1/","title":"JAVA","price":99,"pub_date":"2020-01-20T13:03:04Z","authors":[3]},{"id":18,"publish":"http://127.0.0.1:8000/publishes/1/","title":"hello","price":99,"pub_date":"2020-01-20T13:03:04Z","authors":[3]}]

如果未携带token,或携带的token错误:

返回结果:

{"detail":"验证失败"}

三、restframework配置

1.引子

在第二节中,我们实现了token的生成和认证,在认证时,我们使用自定义的TokenAuth类来进行认证,但是如果在每个视图类中都加上 authentication_classes 列表,比较冗余。

我们观察restframe的源码,可以看到,当我们不添加 authentication_classes 列表变量时,APIView中 authentication_classes 变量会读取一个默认值:

classAPIView(View):#The following policies may be set at either globally, or per-view.

renderer_classes =api_settings.DEFAULT_RENDERER_CLASSES

parser_classes=api_settings.DEFAULT_PARSER_CLASSES

authentication_classes=api_settings.DEFAULT_AUTHENTICATION_CLASSES

throttle_classes=api_settings.DEFAULT_THROTTLE_CLASSES

...

...

继续查看api_settings所属类的源码:

classAPISettings:def __init__(self, user_settings=None, defaults=None, import_strings=None):ifuser_settings:

self._user_settings= self.__check_user_settings(user_settings)

self.defaults= defaults orDEFAULTS

self.import_strings= import_strings orIMPORT_STRINGS

self._cached_attrs=set()

...

...

这里的DEFAULTS就是restframework的默认配置:

DEFAULTS ={#Base API policies

'DEFAULT_RENDERER_CLASSES': ['rest_framework.renderers.JSONRenderer','rest_framework.renderers.BrowsableAPIRenderer',

],'DEFAULT_PARSER_CLASSES': ['rest_framework.parsers.JSONParser','rest_framework.parsers.FormParser','rest_framework.parsers.MultiPartParser'],'DEFAULT_AUTHENTICATION_CLASSES': ['rest_framework.authentication.SessionAuthentication','rest_framework.authentication.BasicAuthentication'],'DEFAULT_PERMISSION_CLASSES': ['rest_framework.permissions.AllowAny',

],'DEFAULT_THROTTLE_CLASSES': [],'DEFAULT_CONTENT_NEGOTIATION_CLASS': 'rest_framework.negotiation.DefaultContentNegotiation','DEFAULT_METADATA_CLASS': 'rest_framework.metadata.SimpleMetadata','DEFAULT_VERSIONING_CLASS': None,#Generic view behavior

'DEFAULT_PAGINATION_CLASS': None,'DEFAULT_FILTER_BACKENDS': [],#Schema

'DEFAULT_SCHEMA_CLASS': 'rest_framework.schemas.openapi.AutoSchema',#Throttling

'DEFAULT_THROTTLE_RATES': {'user': None,'anon': None,

},'NUM_PROXIES': None,#Pagination

'PAGE_SIZE': None,#Filtering

'SEARCH_PARAM': 'search','ORDERING_PARAM': 'ordering',#Versioning

'DEFAULT_VERSION': None,'ALLOWED_VERSIONS': None,'VERSION_PARAM': 'version',#Authentication

'UNAUTHENTICATED_USER': 'django.contrib.auth.models.AnonymousUser','UNAUTHENTICATED_TOKEN': None,#View configuration

'VIEW_NAME_FUNCTION': 'rest_framework.views.get_view_name','VIEW_DESCRIPTION_FUNCTION': 'rest_framework.views.get_view_description',#Exception handling

'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler','NON_FIELD_ERRORS_KEY': 'non_field_errors',#Testing

'TEST_REQUEST_RENDERER_CLASSES': ['rest_framework.renderers.MultiPartRenderer','rest_framework.renderers.JSONRenderer'],'TEST_REQUEST_DEFAULT_FORMAT': 'multipart',#Hyperlink settings

'URL_FORMAT_OVERRIDE': 'format','FORMAT_SUFFIX_KWARG': 'format','URL_FIELD_NAME': 'url',#Input and output formats

'DATE_FORMAT': ISO_8601,'DATE_INPUT_FORMATS': [ISO_8601],'DATETIME_FORMAT': ISO_8601,'DATETIME_INPUT_FORMATS': [ISO_8601],'TIME_FORMAT': ISO_8601,'TIME_INPUT_FORMATS': [ISO_8601],#Encoding

'UNICODE_JSON': True,'COMPACT_JSON': True,'STRICT_JSON': True,'COERCE_DECIMAL_TO_STRING': True,'UPLOADED_FILES_USE_URL': True,#Browseable API

'HTML_SELECT_CUTOFF': 1000,'HTML_SELECT_CUTOFF_TEXT': "More than {count} items...",#Schemas

'SCHEMA_COERCE_PATH_PK': True,'SCHEMA_COERCE_METHOD_NAMES': {'retrieve': 'read','destroy': 'delete'},

}

View Code

前面代码中,使用 api_settings.DEFAULT_AUTHENTICATION_CLASSES ,api_settings没有这个属性,所以会自动调用 APISettings 的__getattr__()方法:

def __getattr__(self, attr):if attr not inself.defaults:raise AttributeError("Invalid API setting: '%s'" %attr)try:#Check if present in user settings

val =self.user_settings[attr]exceptKeyError:#Fall back to defaults

val =self.defaults[attr]#Coerce import strings into classes

if attr inself.import_strings:

val=perform_import(val, attr)#Cache the result

self._cached_attrs.add(attr)

setattr(self, attr, val)return val

__getattr__()方法先判断DEFAULTS中是否存在 DEFAULT_AUTHENTICATION_CLASSES ,如果不存在则报错。然后去user_settings中获取 DEFAULT_AUTHENTICATION_CLASSES 的值,user_settings是一个属性方法:

@propertydefuser_settings(self):if not hasattr(self, '_user_settings'):

self._user_settings= getattr(settings, 'REST_FRAMEWORK', {})return self._user_settings

这段代码会先去django的settings中查看是否存在名为"REST_FRAMEWORK"的配置项。所以我们要使用自定义的认证类,可以在django的settings中配置REST_FRAMEWORK来指定。

首先,将TokenAuth类从views.py移到单独的一个模块,例如utils.py:

#utils.py

from rest_framework importexceptionsfrom .models importTokenfrom rest_framework.authentication importBaseAuthenticationclassTokenAuth(BaseAuthentication):#认证token过程

defauthenticate(self, request):

token= request.GET.get("token")

token_obj= Token.objects.filter(token=token).first()if nottoken_obj:raise exceptions.AuthenticationFailed("验证失败")return (token_obj.user, token_obj)

然后在django的settings中添加配置:

REST_FRAMEWORK ={"DEFAULT_AUTHENTICATION_CLASSES": ["demo.utils.TokenAuth"]

}

这样,我们的所有视图类在被访问时都会使用TokenAuth类来对token进行验证,但是在访问/login/页面时,由于还没有登录认证,所以不能进行token验证。

可以在LoginView视图类中,加上一个空的 authentication_classes 列表来处理:

classLoginView(APIView):

authentication_classes=[]

...

...

这样,访问/login/的时候不会验证token,而访问其他资源的时候会验证token。

ღ♋

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值