You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android中Retrofit 2/OkHTTP3下如何通过HTTPS发送安全Cookie?

这个场景我帮不少Android开发者处理过——当Django开启CSRF_COOKIE_SECURE=True后,只有HTTPS请求才能拿到CSRF Cookie,而默认OkHttp不会自动管理Cookie,也不会主动添加CSRF Token到请求头,导致验证失败。下面是一套安全且可行的解决方案:

1. 先搞定Cookie的持久化管理

默认OkHttp不会保存Cookie,所以第一步要配置一个CookieJar来持久化HTTPS返回的Cookie,推荐用PersistentCookieJar库,它能把Cookie存在SharedPreferences里,重启App也不会丢失。

首先在你的Gradle依赖里加上:

implementation 'com.github.franmontiel:PersistentCookieJar:v1.0.1'

然后初始化CookieJar并配置到OkHttpClient:

// 初始化持久化CookieJar,传入上下文
CookieJar cookieJar = new PersistentCookieJar(
    new SetCookieCache(),
    new SharedPrefsCookiePersistor(getApplicationContext())
);

// 构建OkHttpClient的基础配置
OkHttpClient.Builder okHttpBuilder = new OkHttpClient.Builder()
    .cookieJar(cookieJar);

2. 添加自动注入CSRF Token的拦截器

Django要求POST/PUT/DELETE等请求必须在请求头携带X-CSRFToken,值就是之前拿到的csrftoken Cookie。我们可以写一个OkHttp拦截器,自动帮我们完成这个步骤,不用每个请求手动加:

Interceptor csrfInterceptor = new Interceptor() {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();
        String csrfToken = null;

        // 从CookieJar里找到csrftoken
        List<Cookie> allCookies = ((PersistentCookieJar) cookieJar).getCookieCache().getAll();
        for (Cookie cookie : allCookies) {
            if ("csrftoken".equals(cookie.name())) {
                csrfToken = cookie.value();
                break;
            }
        }

        // 如果找到Token,就添加到请求头
        Request modifiedRequest = originalRequest;
        if (csrfToken != null) {
            modifiedRequest = originalRequest.newBuilder()
                .addHeader("X-CSRFToken", csrfToken)
                .build();
        }

        return chain.proceed(modifiedRequest);
    }
};

把这个拦截器添加到OkHttpBuilder里:

okHttpBuilder.addInterceptor(csrfInterceptor);

// 最终构建OkHttpClient
OkHttpClient okHttpClient = okHttpBuilder.build();

因为CSRF_COOKIE_SECURE=True,只有当你发送HTTPS请求时,Django才会返回csrftoken Cookie。所以在发送实际业务请求前,先调用一个简单的HEAD/GET请求(比如访问后端首页或者专门的CSRF端点),让服务器把Cookie发过来。

先定义Retrofit接口:

public interface ApiService {
    // 用HEAD请求获取Cookie,不需要返回内容,更轻量
    @HEAD("/")
    Call<Void> fetchCsrfCookie();

    // 你的实际业务请求示例
    @POST("/api/your-business-endpoint/")
    Call<YourResponseModel> submitData(@Body YourRequestModel request);
}

然后在代码里先调用fetchCsrfCookie(),拿到Cookie后再发送业务请求:

// 初始化Retrofit,注意baseUrl必须是HTTPS!
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://your-django-backend-domain.com/")
    .client(okHttpClient)
    .addConverterFactory(GsonConverterFactory.create())
    .build();

ApiService apiService = retrofit.create(ApiService.class);

// 第一步:获取CSRF Cookie
apiService.fetchCsrfCookie().enqueue(new Callback<Void>() {
    @Override
    public void onResponse(Call<Void> call, Response<Void> response) {
        if (response.isSuccessful()) {
            // Cookie已经被PersistentCookieJar自动保存,现在可以发业务请求了
            sendBusinessRequest();
        } else {
            // 处理请求失败的情况,比如网络问题
        }
    }

    @Override
    public void onFailure(Call<Void> call, Throwable t) {
        // 处理网络错误
    }
});

// 第二步:发送实际业务请求
private void sendBusinessRequest() {
    YourRequestModel requestData = new YourRequestModel(...);
    apiService.submitData(requestData).enqueue(new Callback<YourResponseModel>() {
        @Override
        public void onResponse(Call<YourResponseModel> call, Response<YourResponseModel> response) {
            // 处理业务响应
        }

        @Override
        public void onFailure(Call<YourResponseModel> call, Throwable t) {
            // 处理业务请求失败
        }
    });
}

几个关键注意点

  • 强制使用HTTPS:绝对不能用HTTP请求,否则Django根本不会返回csrftoken Cookie,这是CSRF_COOKIE_SECURE=True的核心要求。
  • 匹配Django的Cookie名称:如果后端修改了CSRF_COOKIE_NAME配置,拦截器里的"csrftoken"要改成对应的名称。
  • HTTPONLY Cookie不影响:如果后端设置了CSRF_COOKIE_HTTPONLY=True,不用慌——这个限制只针对浏览器的JavaScript,原生App的OkHttp可以正常读取这个Cookie。
  • Cookie有效期:Django的csrftoken默认有效期是1年,所以不用每次启动App都重新获取,除非Cookie被清除或者过期。

内容的提问来源于stack exchange,提问作者waynejohn1110

火山引擎 最新活动