Webservice 在 Android Retrofit 中不工作,但在 Postman 和 Swift / iOS 中工作,得到 401 Unauthorized
Webservice not working in Android Retrofit , but works in Postman and Swift / iOS, Getting 401 Unauthorized
'technical stuff'
之前的简报
对使用 Retrofit 并不陌生,但遇到了这种我很难理解和修复的奇怪行为,
我有两个 Web 服务,在 Postman 和 iOS 中都可以正常工作,但只有一个在 Retrofit 中有效,而另一个则不行,
在我的辩护中,我可以说我得到了(未经授权的)响应,这意味着我能够访问服务器并获得结果
在 API 开发人员的辩护中,他说它适用于 Postman 和其他设备,因此不是服务问题
如果有 Retrofit 专家告诉我为了得到这个错误,retrofit 可能在背后做了什么?
技术资料
谈到服务类型,它包含 Authorization Bearer token 作为 header 每 6 小时过期一次并且根本不包含任何参数(所以它应该很简单,对吧?)和一个简单的 url
http://hashchuna.nn-assets.com/api/locations
不幸的是,header 令牌无法与有效密钥共享,因为它会在任何人尝试之前过期,但无论如何它都在这里 授权承载 3d44626a55dbb024725984e0d37868336fd7e48a
我尝试了什么
我正在使用 okhttp 拦截添加授权 Header 以使用 addHeader/header 方法进行请求,url 中没有空格因为没有参数
Java: Android: Retrofit - using Call but, Response{code = 401,message=unauthorized}
https://github.com/square/retrofit/issues/1290
但他们都没有帮助
警告
现在要记住棘手的部分,令牌过期时必须给出 401 错误,这是预期的,但问题是即使是新创建的令牌我也得到 401 ,这是我的核心问题
日志
D/OkHttp: --> GET http://hashchuna.nn-assets.com/api/locations http/1.1
D/OkHttp: Authorization: Bearer 7c0d53de006b6de931f7d8747b22442354cecef9
D/OkHttp: --> END GET
D/OkHttp: <-- 401 Unauthorized http://hashchuna.nn-assets.com/api/locations (773ms)
D/OkHttp: Date: Mon, 20 Feb 2017 10:44:11 GMT
D/OkHttp: Server: Apache
D/OkHttp: X-Powered-By: PHP/7.0.15
D/OkHttp: Access-Control-Allow-Origin: *
D/OkHttp: Access-Control-Allow-Credentials: true
D/OkHttp: Access-Control-Max-Age: 1000
D/OkHttp: Access-Control-Allow-Headers: X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding
D/OkHttp: Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT
D/OkHttp: Expires: Thu, 19 Nov 1981 08:52:00 GMT
D/OkHttp: Cache-Control: no-store, no-cache, must-revalidate
D/OkHttp: Pragma: no-cache
D/OkHttp: Set-Cookie: PHPSESSID=u477o8g0q387t92hms4nhc14n1; path=/
D/OkHttp: Vary: Authorization
D/OkHttp: X-Powered-By: PleskLin
D/OkHttp: Keep-Alive: timeout=5
D/OkHttp: Connection: Keep-Alive
D/OkHttp: Transfer-Encoding: chunked
D/OkHttp: Content-Type: application/json;charset=utf-8
D/OkHttp: <-- END HTTP
代码
拦截
Request request = chain
.request()
.newBuilder()
//.header("Authorization","Bearer "+ SharedPrefsUtils.getSPinstance().getAccessToken(context))
.addHeader("Authorization","Bearer 1ed6b7c1839e02bbf7a1b4a8dbca84d23127c68e")
//.addHeader("cache-control", "no-cache")
//.cacheControl(CacheControl.FORCE_NETWORK)
.build();
改装实例
private Api getApiInstance(Context context) {
HttpLoggingInterceptor logInter = new HttpLoggingInterceptor();
logInter.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient mIntercepter = new OkHttpClient.Builder()
.addInterceptor(new RequestResponseInterseptor(context))
.addInterceptor(logInter)
.build();
Retrofit retrofitInstance = new Retrofit.Builder()
//.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(mIntercepter)
.build();
return retrofitInstance.create(Api.class);
}
我认为你压倒了其他 header 的 Retrofit
正在为你添加,导致你的 API 不关心你的 Authorization
header。下面的代码会将 header 添加到您现有的 header 而不是覆盖它们。
OkHttpClient mIntercepter = new OkHttpClient.Builder()
...
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("Authorization", "Bearer " + "1ed6b7c1839e02bbf7a1b4a8dbca84d23127c68e").build();
return chain.proceed(request);
})
...
.build();
那些 header 的格式 在这里是 正确的,键应该是 Authorization
,值应该是 Bearer 1ed6b7c1839e02bbf7a1b4a8dbca84d23127c68e
(在你的情况下) .
解决方案(它是 COOKIE)
多亏了一些提示,服务不兼容的实际原因是,据推测 POSTMAN 和 iOS 客户端在发出请求时自行存储和重用 COOKIE
而无需显式处理,Postman 中的 Cookie可以使用 Postman Intercepter 进行测试,但无法编辑,因为 chrome 不允许通过插件编辑 cookie
但是 Retrofit/OkHttp 除非指定,否则将认为它已禁用(可能出于安全原因),
Cookie 在 Interseptor
中添加为 header addHeader("Cookie","KEY-VALUE")
之一
或者
使用 cookieJar
添加到
OkHttpClient mIntercepter = new OkHttpClient.Builder()
.cookieJar(mCookieJar)
.addInterceptor(new RequestResponseInterseptor(context))
.addInterceptor(logInter)
.build();
根据您的需要和 cookie 类型
401 Unauthorized http://www.whosebug.com/api/login?email=test@test.com&password=123456
Date: Fri, 07 Apr 2017 11:23:28 GMT
Server: Apache/2.4.25 (Amazon) PHP/5.6.29
X-Powered-By: PHP/5.6.29
Cache-Control: no-cache, private
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
Content-Length: 41
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json
{"msg":"Invalid Credentials"}
我遇到了类似无法获取错误消息的问题。
当服务器抛出类似 401
的错误或我们从 server.But 得到 null body
的其他错误时,您可以从 errorBody
中的服务器得到错误消息
String response = response.errorBody().string()
就我而言,不幸的是,@Ujju 的解决方案中列出的 none 条建议有效(即 "Cookie" header 和 CookieJar
均未应用)。唯一对我有帮助的就是用 addNetworkInterceptor
替换 addInterceptor
,然后一切都开始工作了。
我也遇到了这个问题。
请求不仅在 POSTMAN 中工作正常,而且在 CURl 中也工作正常。
花了很多时间我找到了解决方案。
登录服务示例:
@FormUrlEncoded
@POST("authentication/login")
fun login(
@Field("login") login: String,
@Field("password") password: String
): Single<Void>
提供okhttp客户端:
private fun provideOkHttpClient(): OkHttpClient {
val cookieManager = CookieManager()
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL)
val client = OkHttpClient.Builder()
.cookieJar(JavaNetCookieJar(cookieManager))
.addNetworkInterceptor(provideRequestInterceptor())
.addNetworkInterceptor(provideLoggingInterceptor())
.protocols(Arrays.asList(Protocol.HTTP_1_1))
.build()
return client
}
启用 JavaNetCookieJar:
implementation "com.squareup.okhttp3:okhttp-urlconnection:$okHttpVersion"
提供授权:
private fun provideRequestInterceptor() = Interceptor {
val builder = it.request().newBuilder().url(it.request().url())
val tokenStr = BuildConfig.SONAR_TOKEN
builder.addHeader("Authorization", "Basic "+ getBase64String(tokenStr+":"))
it.proceed(builder.build())
}
private fun getBase64String(value: String): String {
return Base64.encodeToString(value.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
}
'technical stuff'
之前的简报
对使用 Retrofit 并不陌生,但遇到了这种我很难理解和修复的奇怪行为,
我有两个 Web 服务,在 Postman 和 iOS 中都可以正常工作,但只有一个在 Retrofit 中有效,而另一个则不行,
在我的辩护中,我可以说我得到了(未经授权的)响应,这意味着我能够访问服务器并获得结果
在 API 开发人员的辩护中,他说它适用于 Postman 和其他设备,因此不是服务问题
如果有 Retrofit 专家告诉我为了得到这个错误,retrofit 可能在背后做了什么?
技术资料
谈到服务类型,它包含 Authorization Bearer token 作为 header 每 6 小时过期一次并且根本不包含任何参数(所以它应该很简单,对吧?)和一个简单的 url
http://hashchuna.nn-assets.com/api/locations
不幸的是,header 令牌无法与有效密钥共享,因为它会在任何人尝试之前过期,但无论如何它都在这里 授权承载 3d44626a55dbb024725984e0d37868336fd7e48a
我尝试了什么
我正在使用 okhttp 拦截添加授权 Header 以使用 addHeader/header 方法进行请求,url 中没有空格因为没有参数
Java: Android: Retrofit - using Call but, Response{code = 401,message=unauthorized}
https://github.com/square/retrofit/issues/1290
但他们都没有帮助
警告
现在要记住棘手的部分,令牌过期时必须给出 401 错误,这是预期的,但问题是即使是新创建的令牌我也得到 401 ,这是我的核心问题
日志
D/OkHttp: --> GET http://hashchuna.nn-assets.com/api/locations http/1.1
D/OkHttp: Authorization: Bearer 7c0d53de006b6de931f7d8747b22442354cecef9
D/OkHttp: --> END GET
D/OkHttp: <-- 401 Unauthorized http://hashchuna.nn-assets.com/api/locations (773ms)
D/OkHttp: Date: Mon, 20 Feb 2017 10:44:11 GMT
D/OkHttp: Server: Apache
D/OkHttp: X-Powered-By: PHP/7.0.15
D/OkHttp: Access-Control-Allow-Origin: *
D/OkHttp: Access-Control-Allow-Credentials: true
D/OkHttp: Access-Control-Max-Age: 1000
D/OkHttp: Access-Control-Allow-Headers: X-Requested-With, Content-Type, Origin, Authorization, Accept, Client-Security-Token, Accept-Encoding
D/OkHttp: Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT
D/OkHttp: Expires: Thu, 19 Nov 1981 08:52:00 GMT
D/OkHttp: Cache-Control: no-store, no-cache, must-revalidate
D/OkHttp: Pragma: no-cache
D/OkHttp: Set-Cookie: PHPSESSID=u477o8g0q387t92hms4nhc14n1; path=/
D/OkHttp: Vary: Authorization
D/OkHttp: X-Powered-By: PleskLin
D/OkHttp: Keep-Alive: timeout=5
D/OkHttp: Connection: Keep-Alive
D/OkHttp: Transfer-Encoding: chunked
D/OkHttp: Content-Type: application/json;charset=utf-8
D/OkHttp: <-- END HTTP
代码
拦截
Request request = chain
.request()
.newBuilder()
//.header("Authorization","Bearer "+ SharedPrefsUtils.getSPinstance().getAccessToken(context))
.addHeader("Authorization","Bearer 1ed6b7c1839e02bbf7a1b4a8dbca84d23127c68e")
//.addHeader("cache-control", "no-cache")
//.cacheControl(CacheControl.FORCE_NETWORK)
.build();
改装实例
private Api getApiInstance(Context context) {
HttpLoggingInterceptor logInter = new HttpLoggingInterceptor();
logInter.setLevel(HttpLoggingInterceptor.Level.BODY);
OkHttpClient mIntercepter = new OkHttpClient.Builder()
.addInterceptor(new RequestResponseInterseptor(context))
.addInterceptor(logInter)
.build();
Retrofit retrofitInstance = new Retrofit.Builder()
//.addConverterFactory(new NullOnEmptyConverterFactory())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL)
.client(mIntercepter)
.build();
return retrofitInstance.create(Api.class);
}
我认为你压倒了其他 header 的 Retrofit
正在为你添加,导致你的 API 不关心你的 Authorization
header。下面的代码会将 header 添加到您现有的 header 而不是覆盖它们。
OkHttpClient mIntercepter = new OkHttpClient.Builder()
...
.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder().addHeader("Authorization", "Bearer " + "1ed6b7c1839e02bbf7a1b4a8dbca84d23127c68e").build();
return chain.proceed(request);
})
...
.build();
那些 header 的格式 在这里是 正确的,键应该是 Authorization
,值应该是 Bearer 1ed6b7c1839e02bbf7a1b4a8dbca84d23127c68e
(在你的情况下) .
解决方案(它是 COOKIE)
多亏了一些提示,服务不兼容的实际原因是,据推测 POSTMAN 和 iOS 客户端在发出请求时自行存储和重用 COOKIE
而无需显式处理,Postman 中的 Cookie可以使用 Postman Intercepter 进行测试,但无法编辑,因为 chrome 不允许通过插件编辑 cookie
但是 Retrofit/OkHttp 除非指定,否则将认为它已禁用(可能出于安全原因),
Cookie 在 Interseptor
中添加为 header addHeader("Cookie","KEY-VALUE")
之一
或者
使用 cookieJar
添加到
OkHttpClient mIntercepter = new OkHttpClient.Builder()
.cookieJar(mCookieJar)
.addInterceptor(new RequestResponseInterseptor(context))
.addInterceptor(logInter)
.build();
根据您的需要和 cookie 类型
401 Unauthorized http://www.whosebug.com/api/login?email=test@test.com&password=123456
Date: Fri, 07 Apr 2017 11:23:28 GMT
Server: Apache/2.4.25 (Amazon) PHP/5.6.29
X-Powered-By: PHP/5.6.29
Cache-Control: no-cache, private
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
Content-Length: 41
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: application/json
{"msg":"Invalid Credentials"}
我遇到了类似无法获取错误消息的问题。
当服务器抛出类似 401
的错误或我们从 server.But 得到 null body
的其他错误时,您可以从 errorBody
String response = response.errorBody().string()
就我而言,不幸的是,@Ujju 的解决方案中列出的 none 条建议有效(即 "Cookie" header 和 CookieJar
均未应用)。唯一对我有帮助的就是用 addNetworkInterceptor
替换 addInterceptor
,然后一切都开始工作了。
我也遇到了这个问题。 请求不仅在 POSTMAN 中工作正常,而且在 CURl 中也工作正常。 花了很多时间我找到了解决方案。
登录服务示例:
@FormUrlEncoded
@POST("authentication/login")
fun login(
@Field("login") login: String,
@Field("password") password: String
): Single<Void>
提供okhttp客户端:
private fun provideOkHttpClient(): OkHttpClient {
val cookieManager = CookieManager()
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL)
val client = OkHttpClient.Builder()
.cookieJar(JavaNetCookieJar(cookieManager))
.addNetworkInterceptor(provideRequestInterceptor())
.addNetworkInterceptor(provideLoggingInterceptor())
.protocols(Arrays.asList(Protocol.HTTP_1_1))
.build()
return client
}
启用 JavaNetCookieJar:
implementation "com.squareup.okhttp3:okhttp-urlconnection:$okHttpVersion"
提供授权:
private fun provideRequestInterceptor() = Interceptor {
val builder = it.request().newBuilder().url(it.request().url())
val tokenStr = BuildConfig.SONAR_TOKEN
builder.addHeader("Authorization", "Basic "+ getBase64String(tokenStr+":"))
it.proceed(builder.build())
}
private fun getBase64String(value: String): String {
return Base64.encodeToString(value.toByteArray(charset("UTF-8")), Base64.NO_WRAP)
}