簡單的Django auth + React 跨域cookie登入
把原來的程式碼,用React後端分離來重構,踩了挺多坑的,特別是這個登入功能,坑太多了。
登入介面
首先是後端的登入介面,這個簡單,用的是django自帶的auth庫。遷移資料庫的時候,會自動生成一張使用者表,可以通過admin後臺來管理使用者,這是django的基礎,就不多說了。
def login_react(request): from django.contrib import auth data = json.loads(request.body) username = data.get('username', '') password = data.get('password', '') user_object = auth.authenticate(username=username, password=password) if user_object: # 登入成功為止 auth.login(request, user_object) request.session['user'] = username return JsonResponse({'code': 0, 'msg': '登入成功'}) return JsonResponse({'code': -1, 'msg': '使用者名稱或密碼錯誤'})
跨域解決
跨域一般後端解決,解決起來也簡單,兩種方式。
一、自定義中介軟體
第一種是在每個請求的響應頭加上跨域欄位,可以手寫一箇中間件,類似這種:
from django.utils.deprecation import MiddlewareMixin class HttpResponseCustomHeader(MiddlewareMixin): # 全域性修改響應header def process_response(self, request, response): response['Access-Control-Allow-Origin'] = '*' if request.method == 'OPTIONS': response["Access-Control-Allow-Headers"] = "Content-Type" response["Access-Control-Allow-Methods"] = "PUT,PATCH,DELETE" return response
二、django-cors-headers第三方庫
第二種可以用一個第三方庫:
pip install django-cors-headers
後面配置一下settings.py檔案,網上很多教程,一搜一大把。
axios登入請求
大概就是這樣:
$axios.post( 'http://127.0.0.1/login/', { username: xxxx, password: xxxx, }, ).then((res) => { if (res.data.code === 0) { // 跳轉到首頁 } else { // 登入失敗的處理 } })
坑一、set-cookie黃色感嘆號
第一個坑出現了,用Django自帶的auth登入成功,返回的response header會自動寫入一個set-cookie欄位,瀏覽器接到響應之後,會將set-cookie中的值寫入瀏覽器,這是cookie登入的原理。
抓包看一下,登入成功之後,響應頭裡明明有set-cookie欄位,但是cookie卻沒有寫入,提示SameSite=Lax時,set-cookie行為被禁止了。
然後百度,中間踩了許多坑:
1、後端返回時,將SameSite欄位設定為None,這個做法不成功。直接配置corsheaders中介軟體,將SESSION_COOKIE_SAMESITE = None CSRF_COOKIE_SAMESITE = None ,不生效,如果設定成'None',後端會報錯,顯示:SameSite欄位只能是Lax或者Strict;
2、手寫中介軟體,篡改響應頭的SameSite欄位,這個改成功了,返回的set-cookie欄位裡面確實沒有SameSite欄位了,但是還是標黃色感嘆號,沒寫入成功;
3、瀏覽器輸入chrome://flags/,將SameSite by default cookies 和 Cookies without SameSite must be secure設定為disable,實測失敗;
正確的解決方式,有兩種:
1、axios請求時,把127.0.0.1,替換成localhost;
2、使用corsheaders中介軟體,在settings.py中加入這兩個欄位:SESSION_COOKIE_SECURE = True SESSION_COOKIE_SAMESITE = 'None',同時登入介面使用https請求。
坑二、set-cookie仍無法寫入
我在本地用的是第一種方式,直接將127.0.0.1,替換成localhost,但是這樣還是不行,因為axios請求預設是不寫入、不攜帶cookie的,需要在請求中加入一個欄位withCredentials: true,因為之後所有的請求都需要攜帶cookie,所以這裡要封裝一下axios:
import axios from 'axios'
const service = axios.create({
baseURL: 'http://localhost:8000',
timeout: 5000,
headers: {
'Content-Type': 'application/json; charset=UTF-8;',
},
withCredentials: true
})
export default service
在登入元件中呼叫:
import $axios from './request/index'
$axios.post(
'/login/',
{
username: values.username,
password: values.password,
}
).then((res) => {
if (res.data.code === 0) {
// 跳轉到首頁
} else {
// 登入失敗的處理
}
})
大功告成,可以看到set-cookie後面的黃色感嘆號消失,sessionId也成功寫入:
其他攜帶cookie的請求
這個就比較簡單了,後端在接到請求之後,如果校驗cookie不正確,就給重定向到一個介面,這個介面返回一個登入不成功的資訊,然後axios接受到這個資訊,就給重定向到登入頁。
一、後端處理
新開一個介面:no_login
def no_login(request):
return JsonResponse({'code': -1, 'msg': '未登入'})
在需要登入的介面上加一個裝飾器:@login_required(login_url='/no_login'),如:
@login_required(login_url='/no_login')
def login_out(request):
from django.contrib import auth
auth.logout(request)
return JsonResponse({'code': 0, 'msg': '退出登入成功'})
這樣,如果前端請求login_out介面時,django會自動校驗cookie,如果校驗失敗,就重定向到no_login介面,由no_login介面返回錯誤資訊給前端
另:上面示例的是退出登入介面,在執行完auth.logout(request)後的響應,django會自動構造響應頭,將set-cookie欄位中的sessionId設為空,這樣瀏覽器接到請求後,就會將sessionId從網站的cookie中移除了。
二、前端處理
前端不可能每次發請求都判斷一下後端的msg是不是“未登入”,所以還得去封裝axios:
import axios from 'axios'
const service = axios.create({
baseURL: 'http://localhost:8000',
timeout: 5000,
headers: {
'Content-Type': 'application/json; charset=UTF-8;',
},
withCredentials: true
})
// 新增對響應的處理
service.interceptors.response.use((response) => {
if (response.data.msg === '未登入') {
window.location.href = '/login/'
}else {
return response
}
})
export default service
這樣就ok了。
個人踩坑過來的,不知道是否是最佳實踐,酌情使用哈,有錯誤之處歡迎大家批評指正。