非Django托管的React前端如何获取Django auth视图的CSRF Token?
问题描述
我使用Django 3.1.1及Django auth contrib模块管理用户,未使用Django模板,而是创建Django API应用以响应React前端的请求。我的settings.py配置如下:
MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'directory.middleware.extend_token_response.ExtendTokenResponse' ] #CORS_ORIGIN_ALLOW_ALL = True ALLOWED_HOSTS = ['127.0.0.1', 'localhost', 'dev.mywebsite.com', 'prod.mywebsite.com', 'map.mywebsite.com'] CORS_ORIGIN_WHITELIST = [ 'http://localhost:3000', 'http://localhost:3001', 'http://127.0.0.1:3000', 'http://127.0.0.1:3001' ] CORS_EXPOSE_HEADERS = [ 'Refresh-Token', 'Content-Type', 'Authorization', 'X-CSRFToken' ] CORS_ALLOW_CREDENTIALS = True CSRF_TRUSTED_ORIGINS = ['localhost', '127.0.0.1']
views.py中的密码重置视图配置:
class ResetPasswordView(SuccessMessageMixin, PasswordResetView): email_template_name = 'users/password_reset_email.html' subject_template_name = 'users/password_reset_subject' success_message = "We've emailed you instructions for setting your password, " \ "if an account exists with the email you entered. You should receive them shortly." \ " If you don't receive an email, " \ "please make sure you've entered the address you registered with, and check your spam folder." success_url = reverse_lazy('users-home') def post(self, request, *args, **kwargs): print("request data %s" % request.data) email = request.data.get('email') try: if User.objects.get(email=email).active: print("email: %s " % email) return super(ResetPasswordView, self).post(request, *args, **kwargs) except: # this for if the email is not in the db of the system return super(ResetPasswordView, self).post(request, *args, **kwargs)
前端React通过fetch提交密码重置请求:
const handleFormSubmit = (e) => { e.preventDefault() const csrftoken = getCsrfToken(); fetch(REACT_APP_PROXY + '/reset_password', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken }, body: JSON.stringify({username}) }).then(resp => resp.json()) .then(jsob => { if(!jsob.ok){ console.log(jsob) setErrors(jsob) } if(jsob.token){ sessionStorage.setItem('token', jsob.token) setRedirect(true) } }) }
问题:由于Django未托管React应用(由Apache托管),首次提交表单时没有CSRF Token,请问如何从Django获取CSRF Token用于提交密码重置表单?
解决方案
方法1:创建专门的CSRF Token接口
在Django中新增视图,直接返回CSRF Token:
# views.py from django.middleware.csrf import get_token from django.http import JsonResponse def get_csrf_token(request): csrf_token = get_token(request) return JsonResponse({'csrfToken': csrf_token})
在urls.py配置对应路由:
# urls.py from django.urls import path from .views import get_csrf_token urlpatterns = [ # 其他已有路由... path('get-csrf-token/', get_csrf_token, name='get_csrf_token'), ]
前端修改表单提交逻辑,先调用接口获取Token:
// 封装获取CSRF Token的异步函数 async function fetchCsrfToken() { const response = await fetch(REACT_APP_PROXY + '/get-csrf-token/', { credentials: 'include' // 必须携带,确保会话Cookie被传递 }); const data = await response.json(); return data.csrfToken; } // 更新表单提交函数 const handleFormSubmit = async (e) => { e.preventDefault() const csrftoken = await fetchCsrfToken(); fetch(REACT_APP_PROXY + '/reset_password', { method: 'POST', headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'X-CSRFToken': csrftoken }, credentials: 'include', // 维持会话状态 body: JSON.stringify({username}) }).then(resp => resp.json()) .then(jsob => { if(!jsob.ok){ console.log(jsob) setErrors(jsob) } if(jsob.token){ sessionStorage.setItem('token', jsob.token) setRedirect(true) } }) }
方法2:直接从Django的CSRF Cookie读取
Django默认会在首次访问时设置csrftoken Cookie,无需额外接口,直接从Cookie中提取:
// 重写getCsrfToken函数 function getCsrfToken() { let cookieValue = null; if (document.cookie && document.cookie !== '') { const cookies = document.cookie.split(';'); for (let i = 0; i < cookies.length; i++) { const cookie = cookies[i].trim(); // 匹配csrftoken开头的Cookie if (cookie.substring(0, 10) === 'csrftoken=') { cookieValue = decodeURIComponent(cookie.substring(10)); break; } } } return cookieValue; }
注意:首次加载页面时需要触发Django设置Cookie,可在React组件的useEffect中发送一个简单的GET请求:
import { useEffect } from 'react'; // 页面初始化时执行 useEffect(() => { fetch(REACT_APP_PROXY + '/', { credentials: 'include' }); }, []);
关键配置校验
确保settings.py中以下配置正确:
CORS_ALLOW_CREDENTIALS = True(已配置)CSRF_TRUSTED_ORIGINS需包含所有前端域名,生产环境补充:
CSRF_TRUSTED_ORIGINS = ['localhost', '127.0.0.1', 'dev.mywebsite.com', 'prod.mywebsite.com']
CORS_EXPOSE_HEADERS包含X-CSRFToken(已配置)
内容的提问来源于stack exchange,提问作者Dave
相关产品推荐
相关产品推荐

