You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在Retrofit中向请求头传入JWT调用后端受保护端点遇阻

Retrofit调用受保护接口的认证与解析问题

问题场景

后端有个简单的受保护接口,Postman测试完全正常:发送GET请求时设置Authorization头为bearer + JWT字符串,能拿到200 OK响应及对应内容,后端无问题。但用Retrofit复现流程时,通过OkHttpClient.Builder插入JWT到请求头,遇到两类问题:

  1. 直接传入JWT字符串:
Request.Builder newRequest = request.newBuilder().header("Authorization", bearerToken);

接口返回401未授权状态码。

  1. 按Postman格式添加Bearer 前缀后,请求直接进入onFailure方法,报错:
use jsonreader.setlenient(true) to accept malformed json at line 1 column 1

给addConverterFactory配置带setLenient()的GsonBuilder后,仍触发onFailure,报错变为:

JSON document was not fully consumed.

相关代码

CoffeeController 核心代码

public CoffeeController() {

    okhttpBuilder = new OkHttpClient.Builder()
        .addInterceptor(new Interceptor() {
         @NonNull
         @Override
         public okhttp3.Response intercept(@NonNull Chain chain) throws IOException {
             Request request = chain.request();

             bearerToken = "Bearer " +LoginController.getToken();
             bearerToken = LoginController.getToken();

             Request.Builder newRequest = request.newBuilder().header("Authorization", bearerToken);

             return chain.proceed(newRequest.build());
         }
     });

    gson = new GsonBuilder()
            .setLenient()
            .create();

    retrofit = new Retrofit.Builder()
            .baseUrl("http://10.0.2.2:8080/")
            .client(okhttpBuilder.build())
            .addConverterFactory(GsonConverterFactory.create(gson))
            .build();
}

public static void CoffeeRead(Context context, TextView ResponseView) {
    try {
        CoffeeRepo repo = retrofit.create(CoffeeRepo.class);
        Call<String> call = repo.Read();
        call.enqueue(new Callback<String>() {
            @Override
            public void onResponse(Call<String> call, Response<String> response) {
                message = "Read Coffee: \nToken: " +bearerToken +"\nResponse: " + response.code();
                ResponseView.setText(message);
                Toast.makeText(context, message, Toast.LENGTH_LONG).show();
            }

            @Override
            public void onFailure(Call<String> call, Throwable t) {
                message = "Failed to read coffee: \n" + t.getMessage();
                ResponseView.setText(message);
                Toast.makeText(context, message, Toast.LENGTH_LONG).show();
            }
        });
    } catch (Exception e) {
        message = "Caught Exception: \n" + e.getMessage();
        ResponseView.setText(message);
        Toast.makeText(context, message, Toast.LENGTH_LONG).show();
    }
}

CoffeeRepo 接口定义

public interface CoffeeRepo {
    @Headers({
            "Cache-Control: max-age=3600",
            "User-Agent: Android"
    })
    @GET("coffee")
    Call<String> Read();
}

解决思路

1. 修复Authorization头的核心问题

看Interceptor里的代码,你先给bearerToken赋值了"Bearer " + LoginController.getToken(),但紧接着又把它重新赋值为LoginController.getToken(),等于完全没加Bearer前缀,这就是直接传JWT返回401的原因。删掉多余的赋值行即可:

bearerToken = "Bearer " + LoginController.getToken();
// 删掉这行无效代码:bearerToken = LoginController.getToken();

确保最终请求头的格式和Postman一致:Bearer {你的JWT字符串}。

2. 解决JSON解析报错问题

你用Call<String>接收响应,但GsonConverterFactory会默认把响应内容当成JSON格式解析。如果后端返回的是纯文本(而非带引号的JSON字符串),Gson就会抛出解析错误。有两种解决方式:

  • 方式一:改用ResponseBody接收,手动读取内容(推荐,避免解析冲突)
    修改CoffeeRepo接口:
    @GET("coffee")
    Call<ResponseBody> Read();
    
    然后在onResponse中处理响应:
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        if (response.isSuccessful()) {
            try {
                String content = response.body().string();
                message = "Read Coffee: \nToken: " + bearerToken + "\nResponse Code: " + response.code() + "\nContent: " + content;
            } catch (IOException e) {
                message = "Failed to read response content";
                e.printStackTrace();
            }
        } else {
            message = "Read Coffee: \nResponse Code: " + response.code();
        }
        ResponseView.setText(message);
        Toast.makeText(context, message, Toast.LENGTH_LONG).show();
    }
    
  • 方式二:如果后端确实返回的是JSON格式字符串(比如"coffee list data"),可以保持Call<String>,但要确保Gson的解析逻辑匹配返回格式。

3. 额外排查点

  • 确认LoginController.getToken()拿到的JWT和Postman中使用的完全一致,没有过期或格式错误。
  • 检查是否有其他Interceptor覆盖了Authorization头。
  • 可以添加OkHttp的日志拦截器(HttpLoggingInterceptor),打印完整的请求头和响应内容,直观排查问题。

内容的提问来源于stack exchange,提问作者Wabi-Sabi

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 05:40:41