三十、实战演练之用户登录接口设计、登录功能前后端联调

1. 用户登录接口设计

接口名称:/users/login/

请求方式:POST

参数格式:JSON

请求参数:

参数

变量名

类型

说明

是否必传

用户名

username

字符串

用户名

密码

password

字符串

密码

请求示例:

json格式参数

{
  "username": "daxia",
  "password": "123456" 
}

返回示例:

响应状态码: 200

响应数据:

{
"refresh":
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbiI6InJlZnJlc2giLCJleHAiOjE2MTQ4NTI yODQsImp0aSI6IjkwNWEzOTdiYTQ0ODQ1OTRiZTU1MDI4NmJjNWJiZjIwIiwidXNlcl9pZCI6MX0.WktT D8vABca5nQ0c9RB-KNOsaV-E7_ziQnjezndRzkk",
"token":
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbiI6ImFjY2VzcyIsImV4cCI6MTYxNDc2NjE 4NCwianRpIjoiZDk2MjFmZjhkMWNjNGJhODlkNTExNTQw ·YjdlMTQ5ZjQiLCJ1c2VyX2lkIjoxfQ.IcCR z9kxGlwKTfCeScwGsWEjhoxPBbP6_HT3TMwBZHM"
}

2. token刷新接口设计

接口名称:/users/token/refresh/

请求方式:POST

参数格式:JSON

请求参数:

参数

变量名

类型

说明

是否必传

refresh_token

refresh

字符串

刷新token值

请求示例:

json格式参数

{
"refresh":
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTY zMDkzMzEwMywianRpIjoiNzc3YTg3OWYyZTM4NGExYzhlYjFhYzVhMTA5ZTU4MGQiLCJ1c2VyX2lkIjox fQ.8duDfoZmUJBjqgyyJQqANKl5zcHpZDN8phuNoGFQBm8"
}

返回示例:

响应状态码: 200

响应数据:

{
"token":
"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNjM wOTk1NDY3LCJqdGkiOiI2YTIwZmIwZGZmZDA0YTJiYTAwOWM0YjNjNTQ2NjZkOCIsInVzZXJfaWQiOjF9 .tdCvibq2d8ts_uCbqAvCVr1JOymdbHw7HLB7QckzbUs"
}

3. 验证视图

我们可以先看下restframework自身实现的视图。

simplejwt 已经实现了验证视图,但我们的项目中需要修改返回数据结构(比如说返回的是access,我们需要把access改成token等等),所以要重写视图。

我们先自己看下 TokenObtainPairView 是咋实现的。

我们点击进去看到都是继承的 TokenViewBase,

我们关注一下这个 _serializer_class = api_settings.TOKEN_OBTAIN_SERIALIZER。

我们想看 TOKEN_OBTAIN_SERIALIZER,但是发现ctrl+左键跳转不进去,那我们就去看看 api_settings是啥。

我们看到 api_settings 是这么导入的 from .settings import api_settings

 

 

我们先进入到 api_settings,发现也没找到,我们再进入它继承的类 _api_settings,发现还是找不到。。最后我们只能选择其他办法-就是看官方文档。Customizing token claims — Simple JWT 5.2.2.post14+g8258b5f documentationhttps://django-rest-framework-simplejwt.readthedocs.io/en/latest/customizing_token_claims.html

我们找到这个 TokenObtainPairSerializer序列化器,发现他有data['refresh'] ,data['access'] 。看到这我们就明白了,如果我们想把access换成我们想要的token字段,在这里处理它就ok了。

那我们该怎么处理?我们应该复写 TokenObtainPairSerializer

1. 在 users/下创建 serializers.py模块,编写如下代码:

复写父类中的validate方法

from rest_framework_simplejwt.serializers import TokenObtainPairSerializer, TokenRefreshSerializer

class TokenSerializer(TokenObtainPairSerializer):
	def validate(self, attrs):
        data = super().validate(attrs)
        # 删除键access,赋值给token键
        data['token'] = data.pop('access')
        data['username'] = self.user.username
   		data['id'] = self.user.id
		data['msg'] = '登录成功'
		return data


class MyTokenRefreshSerializer(TokenRefreshSerializer): 
	def validate(self, attrs):
        data = super().validate(attrs)
        # 删除键access,赋值给token键
        data['token'] = data.pop('access')
        return data

2. 在 users/views.py 中编写如下视图: 

from rest_framework_simplejwt.views import TokenObtainPairView, TokenRefreshView 
from rest_framework import viewsets, generics
from users.serializers import MyTokenRefreshSerializer, MyTokenObtainPairSerializer, UserSerializer, UserRegisterSerializer
from users.models import User
from utils.permissions import OnlySuperUser

class LoginView(TokenObtainPairView):
    """
    登录视图
    """
	serializer_class = MyTokenObtainPairSerializer

class MyTokenRefreshView(TokenRefreshView):
    """
    token刷新视图
    """
    serializer_class = MyTokenRefreshSerializer

3. 在 users/下创建 urls.py模块,编写如下路由:

from django.urls import path
from . import views
urlpatterns = [
    path('users/login/', views.LoginView.as_view(), name='login'),
    path('users/token/refresh/', views.MyTokenRefreshView.as_view(), name='refresh') 
]

 在根路由中包含 users.urls

urlpatterns = [
    ...
    path('', include('users.urls'))
]

4. 使用 

要验证Simple JWT是否正常工作,可以使用curl发出几个测试请求:

curl \
-X POST \
-H "Content-Type: application/json" \
-d '{"username": "xinlan", "password": "123456"}' \
http://localhost:8000/users/login/

...
{
"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiY WNjZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNDU2LCJqdGkiOiJmZDJmOWQ1ZTFhN2M0MmU4OT Q5MzVlMzYyYmNhOGJjYSJ9.NHlztMGER7UADHZJlxNG0WSi22a2KaYSfd1S-AuT7lU",
"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjo icmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3 ODg5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4"
}

您可以使用返回的访问令牌来证明受保护视图的身份验证:

curl \
-H "Authorization: Bearer
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiY 29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNDU2LCJqdGkiOiJmZDJmOWQ1ZTFhN2M0MmU4OTQ5MzVlMzYyYmN hOGJjYSJ9.NHlztMGER7UADHZJlxNG0WSi22a2KaYSfd1S-AuT7lU" \
http://localhost:8000/api/some-protected-view/

当此短暂存在的访问令牌到期时,您可以使用较长存在的刷新令牌来获取另一个访问令牌:

curl \
-X POST \
-H "Content-Type: application/json" \
-d
'{"refresh":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoi cmVmcmVzaCIsImNvbGRfc3R1ZmYiOiLimIMiLCJleHAiOjIzNDU2NywianRpIjoiZGUxMmY0ZTY3MDY4NDI3OD g5ZjE1YWMyNzcwZGEwNTEifQ.aEoAYkSJjoWH1boshQAaTkf8G3yn0kapko6HFRt7Rh4"}' \
http://localhost:8000/users/token/refresh/

...
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX3BrIjoxLCJ0b2tlbl90eXBlIjoiYWN jZXNzIiwiY29sZF9zdHVmZiI6IuKYgyIsImV4cCI6MTIzNTY3LCJqdGkiOiJjNzE4ZTVkNjgzZWQ0NTQyYTU0N WJkM2VmMGI0ZGQ0ZSJ9.ekxRxgb9OKmHkfy-zs1Ro_xs1eMLXiR17dIDBVxeT-w"}

5. simple-jwt 配置

Simple JWT的一些行为可以通过settings.py中的设置变量来定制:

默认设置如下:

# Django project settings.py

from datetime import timedelta


SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(minutes=5),  # token过期时间
'REFRESH_TOKEN_LIFETIME': timedelta(days=1),   # 刷新token过期时间
'ROTATE_REFRESH_TOKENS': False,    # 设置为true时,  每次刷新token时都会更新refresh-
token的过期时间
'BLACKLIST_AFTER_ROTATION': True,  # 当设置为True时,  会在每次刷新token后把刷新token添加 进黑名单  (ROTATE_REFRESH_TOKENS设置为True,  并且起用了黑名单)
'UPDATE_LAST_LOGIN': False, # 是否更新最后一次登录时间
'ALGORITHM': 'HS256',  # 令牌的签名算法
'SIGNING_KEY': settings.SECRET_KEY,  # 签名秘钥
'VERIFYING_KEY': None,  # 验证秘钥  如果ALGORITHM设置了哈希密码算法 ,VERIFYING_KEY会被忽 略,  SIGNING_KEY会被使用。  如果ALGORITHM设置了一个RSA算法,  VERIFYING_KEY必须设置为一个RSA公钥
'AUDIENCE': None,  # aud字段声明,  设置为None则解码的payload中不包含aud字段 'ISSUER': None, # 发行人  iss字段,  设置为None则解码的payload中不包含              'JWK_URL': None, # JWK_URL用于动态解析验证令牌签名所需的公钥。
'LEEWAY': 0, # Leeway是用来给过期时间留出一些空间的。  这可以是一个以秒为单位的整数,  也可以是 datetime.timedelta。
'AUTH_HEADER_TYPES': ('Bearer',), # 需要身份验证的视图将接受的授权头类型。
'AUTH_HEADER_NAME': 'HTTP_AUTHORIZATION', # 用于身份验证的授权头名称。  HTTP_xxxx 请求头 则为xxxx
'USER_ID_FIELD': 'id',  # 用户识别字段在数据中对应的字段
'USER_ID_CLAIM': 'user_id', # 用户识别字段在payload中的字段名,  默认user_id 'USER_AUTHENTICATION_RULE':
'rest_framework_simplejwt.authentication.default_user_authentication_rule', # 用户校验 规则
'AUTH_TOKEN_CLASSES': ('rest_framework_simplejwt.tokens.AccessToken',),  # 令牌生
成类
'TOKEN_TYPE_CLAIM': 'token_type', # 令牌类型字段名
'JTI_CLAIM': 'jti', # 令牌标识符字段名
'SLIDING_TOKEN_REFRESH_EXP_CLAIM': 'refresh_exp', # 用于存储滑动令牌刷新周期的过期时间 的声明名称。
'SLIDING_TOKEN_LIFETIME': timedelta(minutes=5),  # 滑动token的有效时间
'SLIDING_TOKEN_REFRESH_LIFETIME': timedelta(days=1), # 滑动刷新token的有效 }

配置说明详见官方文档

我们项目中仅配置两项:

# settings.py
from datetime import timedelta

...

SIMPLE_JWT = {
'ACCESS_TOKEN_LIFETIME': timedelta(days=1),  # 便于开发设置token过期时间1天 
'REFRESH_TOKEN_LIFETIME': timedelta(days=1), # 刷新token过期时间
}

 6. 鉴权

  • 需要鉴权的接口

除注册,登录接口外,其他接口均需要权限才能访问

  • 鉴权类型

JWT

  • 鉴权方式

从登录接口响应数据中获取token,以Authorization 为请求头的key, Bearer token作为value。

Bearer为前缀,与token值之间有一个空格,例如:

Authorization: Bearer
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ0b2tlbiI6ImFjY2VzcyIsImV4cCI6MTYxNDY4ODU2N SwianRpIjoiYmE4MjFiY2YzODJiNGEyMThiNTlhYWFkZmQ1YjFkYzYiLCJ1c2VyX2lkIjoxfQ.F2EvMh2a nxtpVRIxwhu1PHbLmryAAvglZ9c1AogDU9s

4. 登录功能前后端联调

(1)修改后端host

  • 修改api.js 中的后端主机名
  • 接下来,启动前端服务,使用前面注册好的用户名密码登录,发现报cors错误
...
axios.defaults.baseURL = 'http://127.0.0.1:8000' 
...

(2)CORS 

1. 简介

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。

它允许浏览器向跨源服务器,发出XMLHttpRequest 请求,从而克服了A JAX只能同源使用的限制。

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的A JAX通信没有差别,代码完全一样。浏览器一旦发现A JAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。 

CORS原理https://web.dev/cross-origin-resource-sharing/

(3)django-cors-headers

Django - CORS -headers是一个Django应用程序,用于处理跨源资源共享(CORS)所需的服务器头信息。

1. 安装

python -m pip install django-cors-headers

2. 添加到apps

INSTALLED_APPS = [ 
...,
"corsheaders", 	
...,
]

3. 设置中间件

Django-cors-headers 是通过中间件实现cors头设置的,所以需要设置对应的中间件

MIDDLEWARE = [
...,
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware", 
...,
]

CorsMiddleware应该放在尽可能高的位置,特别是在任何可以生成响应的中间件之前,比如django的

CommonMiddleware 。否则无法将CORS头添加到这些响应中。

4. 配置

 要使用CORS,还需要在settings.py 模块中添加如下配置:

# CORS设置
# 允许跨域的域名列表
CORS_ALLOWED_ORIGINS = [
'http://localhost:8080' 
]
# 运行所有域名跨域
CORS_ALLOW_ALL_ORIGINS = True
# 允许cookies跨域
CORS_ALLOW_CREDENTIALS = True

 更多配置详见django-cors-headers · PyPI完成上述操作后,我们的登录功能完成。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值