如何在不到处添加特定代码的情况下处理 auth0 403 错误 (Retrofit/okhttp/RxAndroid)

How to handle auth0 403 error without adding specific code everywhere (Retrofit/okhttp/RxAndroid)

我正在使用 Auth0,它为我提供了一个 JWT(json 网络令牌)和一个刷新令牌。我在 http headers 中使用此 JWT 与我的后端进行通信。

当服务器确定 JWT 已过期时,它可能会给我一个 403。在这种情况下,我可以要求 Auth0 使用 refreshtoken 给我颁发一个新的 JWT。这意味着我调用 Auth0 后端,将 refreshtoken 传递给它,它会给我一个新的 JWT,然后我可以在我的请求中使用它。

我的问题是,如何在我的所有网络代码中高效地编写此行为?我将有几个端点可以与之交谈,它们都可能 return 403.

我想我应该首先制作一个拦截器,将 JWT 添加到所有请求中。

然后应该有检测到 403 的行为,悄悄地对 Auth0 进行网络调用,检索新的 JWT。然后应该再次尝试原始请求,新的 JWT 在其 headers.

所以我更愿意让这个 403 处理在我的其他代码不可见的地方,并且绝对不必在所有地方重写它。

任何有关如何实现此目标的指示都将不胜感激。

--

需要说明的是,我基本上是在寻找有关如何使用 RxAndroid Observables 实现这一点的指示。当某个 Observable 发现 403 时,它应该 'inject' 一个新的网络调用。

您可以在本地检查到期时间并通过检查令牌的 exp 声明相应地刷新,而不是仅在收到 403 响应后才刷新令牌。例如,this example uses the same approach in Angular。不特定于Android,但思路是一样的:

jwtInterceptorProvider.tokenGetter = function(store, jwtHelper, auth) {
  var idToken = store.get('token');
  var refreshToken = store.get('refreshToken');
  if (!idToken || !refreshToken) {
    return null;
  }
  // If token has expired, refresh it and return the new token
  if (jwtHelper.isTokenExpired(idToken)) {
    return auth.refreshIdToken(refreshToken).then(function(idToken) {
      store.set('token', idToken);
      return idToken;
    });
  // If not expired, return the token directly
  } else {
    return idToken;
  }
}

我通过为 OkHttp 写一个 Interceptor 解决了这个问题。它检查网络调用的状态代码。如果是 403,调用 Auth0 服务器并请求一个新的 id_token。然后在原始请求的新版本中使用此令牌。

为了测试,我写了一个小网络服务器来检查 TestHeader 是否 failsucceed 和 returns 一个 403 如果它 失败

public class AuthenticationInterceptor implements Interceptor {

    @Override
    public Response intercept(Chain chain) throws IOException {
        Request originalRequest = chain.request();
        Request authenticationRequest = originalRequest.newBuilder()
                .header("TestHeader", "fail")
                .build();

        Response origResponse = chain.proceed(authenticationRequest);

        // server should give us a 403, since the header contains 'fail'
        if (origResponse.code() == 403) {
            String refreshToken = "abcd"; // you got this from Auth0 when logging in

            // start a new synchronous network call to Auth0
            String newIdToken = fetchNewIdTokenFromAuth0(refreshToken);

            // make a new request with the new id token
            Request newAuthenticationRequest = originalRequest.newBuilder()
                    .header("TestHeader", "succeed")
                    .build();

            // try again
            Response newResponse = chain.proceed(newAuthenticationRequest);

            // hopefully we now have a status of 200
            return newResponse;
        } else {
            return origResponse;
        }
    }
}

然后我将这个拦截器附加到我插入 Retrofit 适配器的 OkHttpClient:

// add the interceptor to an OkHttpClient

public static OkHttpClient getAuthenticatingHttpClient() {
    if (sAuthenticatingHttpClient == null) {
        sAuthenticatingHttpClient = new OkHttpClient();
        sAuthenticatingHttpClient.interceptors().add(new AuthenticationInterceptor());
    }
    return sAuthenticatingHttpClient;
}

// use the OkHttpClient in a Retrofit adapter

mTestRestAdapter = new RestAdapter.Builder()
    .setClient(new OkClient(Network.getAuthenticatingHttpClient()))
    .setEndpoint("http://ip_of_server:port")
    .setLogLevel(RestAdapter.LogLevel.FULL)
    .build();

// call the Retrofit method on buttonclick

ViewObservable.clicks(testNetworkButton)
    .map(new Func1<OnClickEvent, Object>() {
             @Override
             public Object call(OnClickEvent onClickEvent) {
                 return mTestRestAdapter.fetchTestResponse();
             }
         }
    )