Android端Retrofit发送POST注册请求返回403状态码问题排查
安卓Retrofit POST注册请求返回403错误排查修复
问题现象
安卓端通过Retrofit提交表单实现用户账号创建,相同请求参数在Postman中可正常调用,安卓端发起请求固定返回403状态码。
排查步骤与修复方案
修复重复Content-Type头问题
你的Api接口定义存在冲突:@FormUrlEncoded注解会自动为请求添加Content-Type: application/x-www-form-urlencoded头,不需要手动通过@Headers重复声明。重复添加头会导致部分服务端框架解析请求异常,直接返回403。
修复后Api.kt代码:interface Api { @POST("users/signup") @FormUrlEncoded fun createUser( @Field("first_name") fName: String, @Field("last_name") lName: String, @Field("email") email: String, @Field("username") username: String, @Field("password") password: String, ) : Call<UserResponse> }补全通用请求头,绕过服务端WAF拦截
Postman发送请求时会默认携带通用浏览器请求头,而OkHttp默认的User-Agent标识为okhttp/版本号,绝大多数服务端的WAF防护规则会直接拦截这类标识为爬虫/非正常客户端的请求。
另外你当前代码把OkHttp、Retrofit初始化逻辑写在按钮点击事件里,每次点击都会新建实例,既浪费资源也可能导致连接状态异常,需要把初始化逻辑移到点击事件外部,同时添加请求头拦截器:// 初始化逻辑移到点击事件外,不要重复创建 val logging = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } val httpClient = OkHttpClient.Builder() .addInterceptor(logging) .addInterceptor { chain -> val original = chain.request() val request = original.newBuilder() // 替换为常规移动端UA,也可自定义为你的APP标识 .header("User-Agent", "Mozilla/5.0 (Linux; Android 13; Mobile) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Mobile Safari/537.36") .header("Accept", "*/*") .header("Connection", "keep-alive") // 如果服务端开启CSRF校验,需在此处补充从前置页面获取的CSRF token // .header("X-CSRFToken", csrfToken) .build() chain.proceed(request) } .build() val apiInstance: Api by lazy { Retrofit.Builder() // *注意:BASE_URL必须以`/`结尾,否则Retrofit会出现路径拼接错误* .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .client(httpClient) .build() .create(Api::class.java) }补充错误日志打印,定位具体拦截原因
你当前的响应回调只打印了状态码,没有读取服务端返回的错误信息,补充错误体打印逻辑可以直接看到403的具体触发原因:override fun onResponse( call: Call<UserResponse>, response: Response<UserResponse> ) { if (response.isSuccessful) { Toast.makeText(applicationContext, "注册成功", Toast.LENGTH_LONG).show() } else { // 读取服务端返回的具体错误内容 val errorContent = response.errorBody()?.string() Toast.makeText(applicationContext, "请求失败:$errorContent", Toast.LENGTH_LONG).show() } }修复已知逻辑bug
你当前的输入校验逻辑有误,用户名为空时错误提示写成了密码必填,需要修正:if (username.isEmpty()) { binding.etUsername.error = "用户名不能为空" binding.etUsername.requestFocus() return@setOnClickListener }其他排查点
- 确认
BASE_URL配置正确,必须以/结尾,避免路径拼接错误 - 如果请求使用HTTP明文协议,安卓9及以上版本默认禁止明文请求,需在AndroidManifest中配置网络安全规则放开限制
- 对比HttpLoggingInterceptor打印的全量请求日志和Postman的请求日志,逐行核对请求路径、参数、头信息是否完全一致
- 确认
内容的提问来源于stack exchange,提问作者SaeedMoeen
相关产品推荐
相关产品推荐

