【Vue+DRF生鲜电商】12.用户登录之DRF Token认证登录原理和使用方法

欢迎访问我的博客专题

源码可访问 Github 查看

在购买商品时,需要用户登录才能操作,所以就必须要增加登录和注册功能。

由于在Django中配置了path('api-auth/', include('rest_framework.urls')), # drf 认证url的URL,所以在DRF中可以直接使用认证功能

比如退出登录后,此时的链接为: http://127.0.0.1:8000/api-auth/login/?next=/goods/

image.png

在这也有csrf做表单的安全验证。但是在前后端分离的系统中是无法使用的。

前端会运行到其他站点,相当于是跨站了,无法使用csrf验证。

DRF Token认证登陆原理

访问 https://www.django-rest-framework.org/api-guide/authentication/#tokenauthentication 查看使用方法

此身份验证方案使用一个简单的基于令牌的HTTP身份验证方案。令牌身份验证适用于客户机-服务器设置,例如本机桌面和移动客户机。

要使用TokenAuthentication方案,需要将身份验证类配置为包含TokenAuthentication,另外还包括rest_framework.authtokenINSTALLED_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的表

image.png

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
**

image.png
image.png

向 http://127.0.0.1:8000/api-token-auth/ 接口 POST usernamepassword

image.png

然后这个接口就返回一个token,这时候刷新数据库,就可以看到数据库中已经创建该用户关联的key

image.png

拿到这个key后,对于要进行身份验证的客户机,令牌密钥应该包含在授权HTTP头中。键应该以字符串文字“Token”作为前缀,并用空格分隔两个字符串。例如:

Authorization: Token ff2634eac32ffa4a057ef1f864e738c9e17890e0

使用token进行访问

然后将其填入到请求header中,用来请求所有商品: http://127.0.0.1:8000/goods/

怎么查看是否是通过认证的用户呢

image.png

由于以下视图不好打断点

class GoodsListView(generics.ListAPIView):
    """
    显示所有的商品列表
    """
    queryset = Goods.objects.all()
    serializer_class = GoodsSerializer
    pagination_class = GoodsPagination

就在mixins.ListModelMixin中打上断点。

image.png

就可以看到已经可以取出user的值了。

通过以上配置后,数据表中的token是永久有效的,它没有被设置过期时间,只要其他人拿到了这个token的值,那么就可以用这个token来访问网站,实质上是极不安全的。

viewsets配置DRF Token认证类

由于已经在 settings.py 中配置了全局token认证:'rest_framework.authentication.TokenAuthentication',对token进行认证,如果验证失败,就会产生异常。

测试,如果传入了一个错误的token,然后去请求: http://127.0.0.1:8000/goods/ 那么,他就会产生错误

image.png

但是,商品列表页这种公开的数据,用户不登陆或者登录错误的情况下都应该是可以访问的。另外用户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/

image.png

可以在调试结果中获取到usertoken的值

image.png

如果取消视图中的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的值了

image.png

你可能感兴趣的:(【Vue+DRF生鲜电商】12.用户登录之DRF Token认证登录原理和使用方法)