如何向Django的端点发送带有CSRF令牌和JSON请求体的POST请求?

移动开发 2026-07-11

我正在开发一个与运行Django的服务器通信的Android Studio应用。我正在实现手机端的登录流程,但服务器对我的POST请求总是返回 403 Forbidden,即使在header设置了 X-CSRFToken

在应用中,初始屏幕对 /phone/login 端点发起GET请求,以便从响应头中获取CSRF Token(我在Django视图上使用 @ensure_csrf_cookie 装饰器来确保它被发送)。根据响应,应用导航到登录屏幕,在那里用户可以输入信息。然后该屏幕对 /phone/login 端点发起POST请求,header中带有令牌,主体以JSON形式包含详细信息(由gson转换)。服务器收到后就会处理(尚未实现)。

我在日志中看到POST请求带有header和正确的csrf令牌(也就是我从服务器收到的那一个)。尽管如此,服务器仍然总是返回 403 Forbidden

为了确保不是header名称的问题,我在Django的设置文件中将 CSRF_HEADER_NAME 显式设为默认值。我也尝试在body发送一个空字符串,获取一个带有 get_token(request) 的csrf令牌,并把它放进 LoginRequestBody 对象中,就像HTML表单那样包含进去,但结果仍然相同。浏览器的登录页工作正常,只有来自应用的请求会遇到这个问题。我当然可以在视图上使用 @crsf_exempt,但因为它仍可从浏览器访问,这会不会有安全隐患?非常感谢任何帮助。

ViewModel.kt:

fun getLogin() {
    viewModelScope.launch {
        try {
            val response = CheckInApi.retrofitService.getLoggedIn()
            val body = response.body() ?: LoginResponse()
            if (response.code() == 200 ) {
                if (body.result == "LOGGED IN") {
                    _uiState.update { currentState ->  currentState.copy(result = AuthenticationState.SUCCESS)}
                                }
                else if (body.result == "NOT LOGGED IN") {
                    val cookies = response.headers().get("Set-Cookie")
                    val csrfCookieHeader = cookies?.substring(10,42) ?: "No cookie"
                    _uiState.update { currentState ->  currentState.copy(result = AuthenticationState.LOGIN_REQUIRED,csrfCookieHeader = csrfCookieHeader)}
                }
            }
        }
        catch (e: IOException) {
            Log.d(TAG, e.toString())
        }
    }
}

fun postLogin() {
    viewModelScope.launch {
        val loginRequestBody = LoginRequestBody(_uiState.value.username, _uiState.value.password)
        try {
            val result = CheckInApi.retrofitService.requestLogin(
                uiState.value.csrfCookieHeader,
                loginRequestBody)
        }
        catch (e: IOException) {
            Log.d(TAG, e.toString())
        }
    }
}

CheckInApiService.kt:

private val retrofit = Retrofit.Builder()
    .addConverterFactory(ScalarsConverterFactory.create())
    .addConverterFactory(GsonConverterFactory.create())
    .baseUrl(BASE_URL)
    .build()

interface CheckInApiService {

    @POST("phone/login/")
    suspend fun requestLogin(@Header("X-CSRFToken") token : String, @Body loginBody : LoginRequestBody): Response<String>

    @GET("phone/login/")
    suspend fun getLoggedIn(): Response<LoginResponse>
}

object CheckInApi {
    val retrofitService : CheckInApiService by lazy {
        retrofit.create(CheckInApiService::class.java)
    }
}

views.py:

@ensure_csrf_cookie
def login(request):

    if request.method not in ["GET", "POST"]:
        return HttpResponseNotAllowed(["GET", "POST"])

    if request.user.is_authenticated:
        return HttpResponse("LOGGED IN")

    if request.method == "GET":
        response = {
            "result" : "NOT LOGGED IN"
            }
        return JsonResponse(response)

    else:
        # Log user in
        pass

解决方案

我建议在生产API中改用一个REST API库来处理身份验证,尤其是对于移动应用,比如 django-rest-framework,因为它们简化了认证流程并且已经帮你完成了相当多繁重的工作;这将是对你当前的 csrftoken 问题一个很棒且永久的解决办法。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章