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();
3. 先触发一个HTTPS请求获取CSRF Cookie
因为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




