Webservice 在 Android Retrofit 中不工作,但在 Postman 和 Swift / iOS 中工作,得到 401 Unauthorized

Posted

技术标签:

【中文标题】Webservice 在 Android Retrofit 中不工作,但在 Postman 和 Swift / iOS 中工作,得到 401 Unauthorized【英文标题】:Webservice not working in Android Retrofit , but works in Postman and Swift / iOS, Getting 401 Unauthorized 【发布时间】:2017-07-11 13:29:54 【问题描述】:

“技术资料”之前的简报 与 Retrofit 合作并不陌生,但遇到了这种奇怪的行为,我很难理解和修复, 我有两个 Web 服务,在 Postman 和 ios 中都可以正常工作,但只有一个在 Retrofit 中工作,而另一个不能, 在我的辩护中,我可以说我收到了(未经授权的)响应,这意味着我能够访问服务器并获得结果 在 API 开发人员的辩护中,他说它适用于 Postman 和其他设备,因此不是服务问题

如果有任何改造专家告诉我,为了得到这个错误,我可能在背后做什么改造?

技术资料 谈到服务的类型,它包含 Authorization Bearer token 作为标头,每 6 小时过期一次,并且根本不包含任何参数(所以应该很简单,对吧?)和一个简单的网址 http://hashchuna.nn-assets.com/api/locations 不幸的是,标头令牌无法与有效密钥共享,因为它会在任何人尝试之前就过期,但无论如何Authorization Bearer 3d44626a55dbb024725984e0d37868336fd7e48a

我的尝试 我正在使用 okhttp 拦截来使用 addHeader/header 方法向请求添加授权标头,url 中没有空格,因为没有参数Getting 401 unauthorized error in retrofit?Java: android: Retrofit - using Call but, Responsecode = 401,message=unauthorizedhttps://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);
    

【问题讨论】:

您确定将更新的令牌附加到您的标头吗?听起来您只是在使用旧令牌。 @C0D3LIC1OU5 是的,我知道这个问题听起来像是明显的错误,但事实并非如此,我什至在生成新令牌后就对其进行了硬编码,除非正在通过改造进行一些缓存,而我不是意识到 要仔细检查的另一件事是,您添加到标头的 KEY 与服务器期望的匹配,并且其中没有拼写错误。就像令牌值的“授权持有者”键(或您所称的任何内容) 这是干什么用的? D/OkHttp: Set-Cookie: PHPSESSID=u477o8g0q387t92hms4nhc14n1; path=/ 在 Postman 中,只接受 Authorization,当然我设置的值是 Bearer 961e171e4d27c95f29a31c91cbef2d6863c760fe,但是,即使使用 Postman 仍然得到 401。您能否提供该 Web 服务的规范或要求文档? 【参考方案1】:

解决方案(它的 COOKIE) 感谢一些提示,服务不兼容的实际原因是,假设POSTMAN和iOS客户端在发出请求时自行存储和重用COOKIE,无需任何显式处理,可以使用@987654321测试Postman中的Cookie @ ,但无法编辑,因为 chrome 不允许通过插件编辑 cookie

但是,除非指定,否则 Retrofit/OkHttp 将认为它已禁用(可能出于安全原因), Cookie 被添加到 Interseptor 内,作为标题 addHeader("Cookie","KEY-VALUE") 之一 或 使用cookieJar添加到

OkHttpClient mIntercepter = new OkHttpClient.Builder()
                .cookieJar(mCookieJar)
                .addInterceptor(new RequestResponseInterseptor(context))
                .addInterceptor(logInter)
                .build();

根据您的需要和 cookie 类型

【讨论】:

对于您的but cant be edited because chrome does not allow editing cookie by plugins,我想您可以在getpostman.com 尝试适用于 Win/MacOS 的 Postman 应用程序 是的,我应该试一试,如果我之前考虑过,本可以为我节省很多时间,.. 什么是mCookieJar?? @IgorGanapolsky 这是一个库,让我们通过创建对象和添加值来为您的请求添加 cookie 仍然无法得到响应。服务器中显示了一些会话问题。但是由于它在 Web 和 iOS 中运行,而且我没有其他选择来检查我的代码。【参考方案2】:

我认为您正在覆盖其他标头 Retrofit 正在为您添加,导致您的 API 不关心您的 Authorization 标头。下面的代码将为您现有的标题添加一个标题,而不是覆盖它们。

    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();

这些标头的格式在这里是正确的,键应该是Authorization,值应该是Bearer 1ed6b7c1839e02bbf7a1b4a8dbca84d23127c68e(在你的情况下)。

【讨论】:

我正在使用 Retrofit 2.1.0 ,RestAdapterRequestInterceptor 都已替换为 RequestOkHttpClient 但这看起来和我的拦截一样,对吗?我的RequestResponseInterseptor 扩展了Interceptor 并创建了相同的请求,你可以查看我的拦截代码 不确定,我没有看到您的 RequestResponseInterseptor 课程的完整代码。这里的关键是覆盖intercept 函数,我看你在任何地方都没有这样做。 好吧,实际上因为 der 代码太多,我只粘贴了一小部分,,,,是的,RequestResponseInterseptor 扩展了 interceptorintercept 包含与您相同的代码,没有别的 好的。顺便说一句,您使用的行不正确 - 您已注释掉正确的标题(它是上面的行)。您是否尝试过使用正确的标头键对令牌进行硬编码? .header("Authorization","Bearer "+ "1ed6b7c1839e02bbf7a1b4a8dbca84d23127c68e").【参考方案3】:

不幸的是,在我的情况下,@Ujju 的解决方案中列出的任何建议都不起作用(即“Cookie”标题和 CookieJar 均未应用)。唯一对我有帮助的是将addInterceptor 替换为addNetworkInterceptor,然后一切都开始工作了。

【讨论】:

这实际上也解决了我的问题!奇怪的是,这 only 发生在同步调用 - call.execute()(不适用于 call.enqueue())和(在我的情况下)only 用于对服务器的实际生产调用,而不是调用我的 WiFi 网络中的本地开发服务器。我找到了原因:github.com/square/okhttp/wiki/Interceptors NetworkInterceptor 也遵循重定向并被应用两次。一次用于初始请求,第二次用于 302。【参考方案4】:
401 Unauthorized http://www.***.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 之类的错误或其他错误时,我们会从服务器获取null body。但是您可以在errorBody 中从服务器获取错误消息

String response = response.errorBody().string()

【讨论】:

感谢您的回复,但我的问题不是没有收到错误消息,我得到了 errorBody 就好了,在我的情况下,消息类似于 "errors": [ "code": "bad_request", "title": "not_authorised" ] 对我来说这是文件大小问题,请参阅***.com/a/55913121/3904109【参考方案5】:

我也遇到过这个问题。 请求不仅在 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)

【讨论】:

以上是关于Webservice 在 Android Retrofit 中不工作,但在 Postman 和 Swift / iOS 中工作,得到 401 Unauthorized的主要内容,如果未能解决你的问题,请参考以下文章

android调用webservice接口都有啥方式

Android调用webService

android loginDemo +WebService用户登录验证

android怎样调用webService

Android 连接webservice(利用谷歌提供的jar包)

android调用webservice接口获取信息