如何用Firebase与Django Rest验证ID Token及对接认证?
Firebase Auth 对接 Django REST 后端完整流程解析&问题解决
我来一步步帮你理清整个流程,解决你遇到的核心疑问:
一、先搞懂整体认证链路
整个流程的核心是用Firebase的ID Token作为前后端的认证凭证,完整链路是:
- 前端调用Firebase原生登录接口完成身份验证,拿到用户对象
- 前端从用户对象中获取Firebase签发的ID Token
- 前端带着这个Token请求Django后端接口(作为身份凭证)
- 后端用Firebase Admin SDK验证Token合法性
- 验证通过后,后端可关联Django本地用户(可选),返回业务数据或授权凭证
二、前端代码的两处修正
你的前端代码有两个关键问题,修正后就能解决部分报错:
async login() { this.error = null; try { const response = await firebase.auth().signInWithEmailAndPassword(this.email, this.password); const tokenResult = await response.user.getIdTokenResult(); // 1. 获取Django默认设置的CSRF Token(从cookie中读取) const csrfToken = document.cookie.split('; ').find(row => row.startsWith('csrftoken='))?.split('=')[1]; fetch("/api/verify-firebase-token/", { method: "POST", headers: { "Content-Type": "application/json", // 2. 修正Authorization头格式:DRF默认识别Bearer Token规范 "Authorization": `Bearer ${tokenResult.token}`, "X-CSRFToken": csrfToken // 带上CSRF Token解决403错误 }, // 这里不需要再传密码了!Firebase已经验证过身份,后端只需要Token body: JSON.stringify({ uid: tokenResult.uid }) }).then((response) => response.json().then((data) => { console.log(data); // 验证通过后,后续所有API请求都可以带上这个Authorization头 })); } catch (error) { this.error = error; } },
三、后端:verify_id_token该放哪里?
verify_id_token需要封装在自定义认证类里,用来自动验证前端传来的Token。这个类是Django REST Framework的认证扩展,步骤如下:
1. 安装并初始化Firebase Admin SDK
先安装依赖:
pip install firebase-admin
然后在项目中初始化Firebase(比如新建myapp/firebase.py):
import firebase_admin from firebase_admin import credentials, auth # 替换为你的Firebase服务账号密钥路径 cred = credentials.Certificate("path/to/serviceAccountKey.json") firebase_admin.initialize_app(cred)
2. 编写自定义认证类
新建myapp/authentication.py:
from rest_framework.authentication import BaseAuthentication from rest_framework.exceptions import AuthenticationFailed from firebase_admin import auth from .firebase import * class FirebaseAuthentication(BaseAuthentication): def authenticate(self, request): # 从请求头中提取Token auth_header = request.headers.get('Authorization') if not auth_header or not auth_header.startswith('Bearer '): return None # 无Token则交给其他认证类处理 id_token = auth_header.split('Bearer ')[1] try: # 验证Token合法性 decoded_token = auth.verify_id_token(id_token) uid = decoded_token['uid'] # 可选:关联Django本地用户(比如根据uid查找或创建User) # from django.contrib.auth.models import User # user, created = User.objects.get_or_create(username=uid) # 返回(user, None),DRF会自动把user绑定到request.user return (uid, None) except auth.InvalidIdTokenError: raise AuthenticationFailed('无效的Firebase Token') except Exception as e: raise AuthenticationFailed(f'Token验证失败:{str(e)}') # 新增这个方法,让DRF识别为无Session认证,避免CSRF验证 def authenticate_header(self, request): return 'Bearer'
四、如何使用FirebaseAuthentication类?
你不需要把它直接作为URL视图,而是把它作为认证规则应用到API视图上:
方式1:单个视图应用
在需要保护的API视图中指定认证类:
# myapp/views.py from rest_framework.views import APIView from rest_framework.response import Response from .authentication import FirebaseAuthentication from rest_framework.permissions import IsAuthenticated class VerifyFirebaseTokenView(APIView): authentication_classes = [FirebaseAuthentication] permission_classes = [IsAuthenticated] # 要求必须认证才能访问 def post(self, request): # request.user就是认证类返回的uid(或绑定的Django User) return Response({ 'success': True, 'uid': request.user, 'message': 'Firebase Token验证成功' })
然后在urls.py中配置这个视图:
from django.urls import path from myapp.views import VerifyFirebaseTokenView urlpatterns = [ path('api/verify-firebase-token/', VerifyFirebaseTokenView.as_view(), name='verify-firebase-token'), # 其他业务API也可以用同样的authentication_classes配置 ]
方式2:全局应用(所有API都用Firebase认证)
在settings.py中配置REST Framework默认认证规则:
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': [ 'myapp.authentication.FirebaseAuthentication', ], 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.IsAuthenticated', ] }
五、关键疑问解答
- 是否需要创建自定义Django视图?:是的,你需要创建类似
VerifyFirebaseTokenView的视图来处理Token验证,或者直接用这个认证类保护你的业务API。 - 何时调用FirebaseAuthentication类?:当前端请求受保护的API时,DRF会自动调用
authentication_classes中的认证类,自动验证请求头里的Token。 - CSRF验证失败怎么解决?:要么前端带上
X-CSRFToken头,要么在认证类中添加authenticate_header方法(如上面代码所示),让DRF识别为无Session认证,跳过CSRF检查。
内容的提问来源于stack exchange,提问作者erikvm




