Retrofit请求登录遇HTTP 401未授权错误排查求助
问题概述
- Postman发送POST请求(Body为JSON格式用户名密码)可正常登录本地Spring Web服务器
- Android环境使用Retrofit复刻相同请求时,返回HTTP 401 Unauthorized错误,且
response.body()为null,触发空指针异常 - 已尝试修改接口端点、BaseURL、GsonBuilder配置,问题未解决
核心排查方向
1. 请求头差异
Postman会自动添加部分默认请求头(如Content-Type: application/json),Retrofit若未明确设置,可能导致Spring端无法正确解析请求体。
2. JSON序列化/反序列化不匹配
Android实体类的字段名、类型与Spring接口接收的参数不匹配,比如大小写不一致、缺少@SerializedName映射,导致请求体解析失败,触发认证失败。
3. 本地服务器访问地址错误
Android模拟器访问本地PC服务器时,不能直接用localhost,需替换为10.0.2.2;真机需使用电脑的局域网IP地址,否则无法连接服务器。
4. Spring端CSRF校验拦截
若Spring开启了CSRF保护,Postman会自动处理Cookie中的CSRF token,但Retrofit默认不会携带,导致请求被拦截返回401。
5. 空指针未做防护
未先判断请求是否成功、response.body()是否为null就直接操作,触发空指针异常。
解决方案
1. 确保请求头正确设置
通过OkHttpClient拦截器全局添加Content-Type,或在接口方法上用@Headers注解指定:
// 全局拦截器方式 OkHttpClient okHttpClient = new OkHttpClient.Builder() .addInterceptor(chain -> { Request request = chain.request().newBuilder() .header("Content-Type", "application/json") .build(); return chain.proceed(request); }) .build(); // 接口方法注解方式 @POST("login") @Headers("Content-Type: application/json") Call<LoginResponse> login(@Body LoginRequest loginRequest);
2. 统一JSON字段映射
确保Android实体类与Spring接收参数完全匹配,字段名不一致时用@SerializedName指定:
public class LoginRequest { @SerializedName("username") // 与Spring接口参数名一致 private String userName; @SerializedName("password") private String passWord; // getter、setter }
3. 修正本地服务器地址
- 模拟器:将BaseURL改为
http://10.0.2.2:端口号/ - 真机:将BaseURL改为
http://电脑局域网IP:端口号/(如http://192.168.1.100:8080/)
4. 处理CSRF校验
若Spring开启CSRF,需先获取CSRF token并加入请求头:
- 先发送GET请求获取Cookie中的
XSRF-TOKEN - 在登录请求中添加
X-XSRF-TOKEN头,值为获取到的token - 确保OkHttpClient启用Cookie持久化:
CookieJar cookieJar = new CookieJar() { private final HashMap<String, List<Cookie>> cookieStore = new HashMap<>(); @Override public void saveFromResponse(HttpUrl url, List<Cookie> cookies) { cookieStore.put(url.host(), cookies); } @Override public List<Cookie> loadForRequest(HttpUrl url) { List<Cookie> cookies = cookieStore.get(url.host()); return cookies != null ? cookies : new ArrayList<>(); } }; OkHttpClient okHttpClient = new OkHttpClient.Builder() .cookieJar(cookieJar) .addInterceptor(chain -> { Request request = chain.request(); String xsrfToken = null; List<Cookie> cookies = cookieJar.loadForRequest(request.url()); for (Cookie cookie : cookies) { if ("XSRF-TOKEN".equals(cookie.name())) { xsrfToken = cookie.value(); break; } } if (xsrfToken != null) { request = request.newBuilder() .header("X-XSRF-TOKEN", xsrfToken) .build(); } return chain.proceed(request); }) .build();
5. 空指针防护
处理响应时先判断请求是否成功,再检查response.body()是否为null:
call.enqueue(new Callback<LoginResponse>() { @Override public void onResponse(Call<LoginResponse> call, Response<LoginResponse> response) { if (response.isSuccessful()) { LoginResponse body = response.body(); if (body != null) { // 处理登录成功逻辑 } else { Log.e("Login", "响应体为空"); } } else { // 打印错误响应内容,排查401原因 try { String errorBody = response.errorBody().string(); Log.e("LoginError", "错误码:" + response.code() + ",错误信息:" + errorBody); } catch (IOException e) { e.printStackTrace(); } } } @Override public void onFailure(Call<LoginResponse> call, Throwable t) { Log.e("LoginFailure", t.getMessage()); } });
内容的提问来源于stack exchange,提问作者Wabi-Sabi
相关产品推荐
相关产品推荐

