欢迎访问我的博客专题
源码可访问 Github 查看
在购买商品时,需要用户登录才能操作,所以就必须要增加登录和注册功能。
由于在Django中配置了path('api-auth/', include('rest_framework.urls')), # drf 认证url
的URL,所以在DRF中可以直接使用认证功能
比如退出登录后,此时的链接为: http://127.0.0.1:8000/api-auth/login/?next=/goods/
在这也有csrf做表单的安全验证。但是在前后端分离的系统中是无法使用的。
前端会运行到其他站点,相当于是跨站了,无法使用csrf验证。
DRF Token认证登陆原理
访问 https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication 查看使用方法
此身份验证方案使用一个简单的基于令牌的HTTP身份验证方案。令牌身份验证适用于客户机-服务器设置,例如本机桌面和移动客户机。
要使用TokenAuthentication
方案,需要将身份验证类配置为包含TokenAuthentication
,另外还包括rest_framework.authtoken
在INSTALLED_APPS
设置中。
https://www.django-rest-framework.org/api-guide/authentication/#setting-the-authentication-scheme 中介绍到:可以使用DEFAULT_AUTHENTICATION_CLASSES
设置全局设置缺省身份验证方案。也就是默认为:
# 文章示例
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
要使用TokenAuthentication
认证们还需要增加'rest_framework.authentication.SessionAuthentication',
配置全局TokenAuthentication
在这个项目中配置为:
# DRF配置
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 'PAGE_SIZE': 5,
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication', # 上面两个用于DRF基本验证
'rest_framework.authentication.TokenAuthentication', # TokenAuthentication
)
}
然后添加rest_framework.authtoken
到APPS
INSTALLED_APPS = [
# 'simpleui', # 第三方后台,使用https://github.com/newpanjing/simpleui
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
# 添加drf应用
'rest_framework',
'rest_framework.authtoken',
'django_filters',
# Django跨域解决
'corsheaders',
# 注册富文本编辑器ckeditor
'ckeditor',
# 注册富文本上传图片ckeditor_uploader
'ckeditor_uploader',
'users.apps.UsersConfig',
'goods.apps.GoodsConfig',
'trade.apps.TradeConfig',
'user_operation.apps.UserOperationConfig',
]
配置完成后执行数据库迁移
manage.py@DjangoOnlineFreshSupermarket > makemigrations
manage.py@DjangoOnlineFreshSupermarket > migrate
执行完查看数据库会增加一个authtoken_token
的表
user_id
是一个外键,指向现有的用户表。如果之前已经创建了用户,在这个token表是没有记录的。这个表和用户表是一一对应的。创建用户表后需要手动创建token表。也就是当用户注册时,需要调用token = Token.objects.create(user=...)
来生成token。
对于要进行身份验证的客户机,令牌密钥应该包含在授权HTTP头中。键应该以字符串文字“Token”作为前缀,并用空格分隔两个字符串。例如:Authorization: Token 9944b09199c62bcf9418ad846dd0e4bbdfc6ee4b
添加api-token-auth URL
在使用TokenAuthentication
时,可能希望为客户端提供一种机制,以获得给定用户名和密码的令牌。REST框架提供了一个内置视图来提供这种行为。要使用它,请将obtain_auth_token
视图添加到您的URLconf:
from rest_framework.authtoken import views
urlpatterns = [
path('admin/', admin.site.urls),
path('api-auth/', include('rest_framework.urls')), # drf 认证url
path('api-token-auth/', views.obtain_auth_token), # drf token获取的url
path('ckeditor/', include('ckeditor_uploader.urls')), # 配置富文本编辑器url
path('', include(router.urls)), # API url现在由路由器自动确定。
# DRF文档
path('docs/', include_docs_urls(title='DRF文档')),
]
测试获取token的API
当使用表单数据或JSON将有效的用户名和密码字段提交到视图时,obtain_auth_token
视图将返回一个JSON响应,测试如下:
这儿使用了一个Google浏览器插件**Restlet Client - REST API Testing
**
向 http://127.0.0.1:8000/api-token-auth/ 接口 POST username
和password
然后这个接口就返回一个token,这时候刷新数据库,就可以看到数据库中已经创建该用户关联的key
拿到这个key后,对于要进行身份验证的客户机,令牌密钥应该包含在授权HTTP头中。键应该以字符串文字“Token”作为前缀,并用空格分隔两个字符串。例如:
Authorization: Token ff2634eac32ffa4a057ef1f864e738c9e17890e0
使用token进行访问
然后将其填入到请求header中,用来请求所有商品: http://127.0.0.1:8000/goods/
怎么查看是否是通过认证的用户呢
由于以下视图不好打断点
class GoodsListView(generics.ListAPIView):
"""
显示所有的商品列表
"""
queryset = Goods.objects.all()
serializer_class = GoodsSerializer
pagination_class = GoodsPagination
就在mixins.ListModelMixin
中打上断点。
就可以看到已经可以取出user的值了。
通过以上配置后,数据表中的token
是永久有效的,它没有被设置过期时间,只要其他人拿到了这个token
的值,那么就可以用这个token
来访问网站,实质上是极不安全的。
viewsets配置DRF Token认证类
由于已经在 settings.py 中配置了全局token认证:'rest_framework.authentication.TokenAuthentication'
,对token进行认证,如果验证失败,就会产生异常。
测试,如果传入了一个错误的token,然后去请求: http://127.0.0.1:8000/goods/ 那么,他就会产生错误
但是,商品列表页这种公开的数据,用户不登陆或者登录错误的情况下都应该是可以访问的。另外用户token
可能会过期的,这时候来访问一个公共的数据也会产生401错误。访问公共数据可以不带token
,但是很多前端在写的时候都是全局带token
的。如果在后端解决这个问题?
取消全局TokenAuthentication
就需要在 settings.py 中全局TokenAuthentication
放在视图中处理,那么 settings.py 就需要进行修改,注释掉TokenAuthentication
# DRF配置
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
# 'PAGE_SIZE': 5,
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication', # 上面两个用于DRF基本验证
# 'rest_framework.authentication.TokenAuthentication', # TokenAuthentication,取消全局token,放在视图中进行
)
}
视图中添加TokenAuthentication
在GoodsListViewSet
视图中进行测试,添加authentication_classes
from rest_framework.authentication import TokenAuthentication
class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
"""
显示商品列表,分页、过滤、搜索、排序
"""
queryset = Goods.objects.all() # 使用get_queryset函数,依赖queryset的值
serializer_class = GoodsSerializer
pagination_class = GoodsPagination
filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) # 将过滤器后端添加到单个视图或视图集
filterset_class = GoodsFilter
authentication_classes = (TokenAuthentication, ) # 只在本视图中验证Token
search_fields = ('name', 'goods_desc', 'category__name') # 搜索字段
ordering_fields = ('click_num', 'sold_num', 'shop_price') # 排序
调试看下能否在继承成类mixins.ListModelMixin
---ListModelMixin
---list
函数中获取到token
,添加断点
使用正确的token
请求 http://127.0.0.1:8000/goods/
可以在调试结果中获取到user
和token
的值
如果取消视图中的authentication_classes
类
class GoodsListViewSet(mixins.ListModelMixin, viewsets.GenericViewSet):
"""
显示商品列表,分页、过滤、搜索、排序
"""
queryset = Goods.objects.all() # 使用get_queryset函数,依赖queryset的值
serializer_class = GoodsSerializer
pagination_class = GoodsPagination
filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) # 将过滤器后端添加到单个视图或视图集
filterset_class = GoodsFilter
# authentication_classes = (TokenAuthentication, ) # 只在本视图中验证Token
search_fields = ('name', 'goods_desc', 'category__name') # 搜索字段
ordering_fields = ('click_num', 'sold_num', 'shop_price') # 排序
再来做同样的请求,就获取不到user
的值了