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完成上述操作后,我们的登录功能完成。