2017-02-18 25 views
1

我正在用azure移動服務創建一個android應用程序。我有一個始終運行的服務(使用startForeground())並監視一些用戶活動。該服務有時需要查詢的Azure數據庫調用的API,存儲在Azure雲,是這樣的:通過使用LoginActivity在沒有活動的服務中刷新Azure用戶令牌android

mClient.invokeApi("APIname", null, "GET", parameters); 
//mClient is the MobileServiceClient instance 

在開始的時候用戶登錄,一切工作正常。經過一段時間後(通常1小時)客戶端的令牌過期,我收到的例外是這樣的:

IDX10223: Lifetime validation failed. The token is expired. 

一些搜索後,我找到了解決令牌這裏刷新: https://github.com/Microsoft/azure-docs/blob/master/includes/mobile-android-authenticate-app-refresh-token.md

如果activiy是活動代碼工作並在過期時成功刷新令牌。但是,如果活動被破壞,它就不起作用。所以我決定ApplicationContext的傳遞給客戶,這種方式:

mClient.setContext(activity.getApplicationContext()); 

,但現在我得到一個ClassCastException,因爲客戶端嘗試投上下文活動。這裏有一些有趣的例外情況:

 java.lang.ClassCastException: android.app.Application cannot be cast to android.app.Activity 
        at com.microsoft.windowsazure.mobileservices.authentication.LoginManager.showLoginUI(LoginManager.java:349) 
        at com.microsoft.windowsazure.mobileservices.authentication.LoginManager.authenticate(LoginManager.java:161) 
        at com.microsoft.windowsazure.mobileservices.MobileServiceClient.login(MobileServiceClient.java:371) 
        at com.microsoft.windowsazure.mobileservices.MobileServiceClient.login(MobileServiceClient.java:356) 
        at com.microsoft.windowsazure.mobileservices.MobileServiceClient.login(MobileServiceClient.java:309) 

那麼我怎樣才能在沒有活動的情況下從服務刷新令牌?還是有另一種方法來保持客戶端始終通過身份驗證?

編輯

我嘗試在這裏粘貼一些代碼,希望更清楚我使用的認證令牌的方式。我有一個用於管理身份驗證的LoginManager。這裏從該類一些有意義的代碼:

public boolean loadUserTokenCache(Context context) 
{ 
    init(context); //update context 

    SharedPreferences prefs = context.getSharedPreferences(SHARED_PREF_FILE, Context.MODE_PRIVATE); 
    String userId = prefs.getString(USERID_PREF, null); 
    if (userId == null) 
     return false; 
    String token = prefs.getString(LOGIN_TOKEN_PREF, null); 
    if (token == null) 
     return false; 

    MobileServiceUser user = new MobileServiceUser(userId); 
    user.setAuthenticationToken(token); 
    mClient.setCurrentUser(user); 

    return true; 
} 

過濾器是:

private class RefreshTokenCacheFilter implements ServiceFilter { 

    AtomicBoolean mAtomicAuthenticatingFlag = new AtomicBoolean(); 

    //--------------------http://stackoverflow.com/questions/7860384/android-how-to-runonuithread-in-other-class 
    private final Handler handler; 
    public RefreshTokenCacheFilter(Context context){ 
     handler = new Handler(context.getMainLooper()); 
    } 
    private void runOnUiThread(Runnable r) { 
     handler.post(r); 
    } 
    //-------------------- 

    @Override 
    public ListenableFuture<ServiceFilterResponse> handleRequest(
      final ServiceFilterRequest request, 
      final NextServiceFilterCallback nextServiceFilterCallback 
    ) 
    { 
     // In this example, if authentication is already in progress we block the request 
     // until authentication is complete to avoid unnecessary authentications as 
     // a result of HTTP status code 401. 
     // If authentication was detected, add the token to the request. 
     waitAndUpdateRequestToken(request); 
     Log.d(Constants.TAG, logClassIdentifier+"REFRESH_TOKEN_CACHE_FILTER is Sending the request down the filter chain for 401 responses"); 
     Log.d(Constants.TAG, logClassIdentifier+mClient.getContext().toString()); 
     // Send the request down the filter chain 
     // retrying up to 5 times on 401 response codes. 
     ListenableFuture<ServiceFilterResponse> future = null; 
     ServiceFilterResponse response = null; 
     int responseCode = 401; 
     for (int i = 0; (i < 5) && (responseCode == 401); i++) 
     { 
      future = nextServiceFilterCallback.onNext(request); 
      try { 
       response = future.get(); 
       responseCode = response.getStatus().code; 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } catch (ExecutionException e) { 
       if (e.getCause().getClass() == MobileServiceException.class) 
       { 
        MobileServiceException mEx = (MobileServiceException) e.getCause(); 
        responseCode = mEx.getResponse().getStatus().code; 
        if (responseCode == 401) 
        { 
         // Two simultaneous requests from independent threads could get HTTP status 401. 
         // Protecting against that right here so multiple authentication requests are 
         // not setup to run on the UI thread. 
         // We only want to authenticate once. Requests should just wait and retry 
         // with the new token. 
         if (mAtomicAuthenticatingFlag.compareAndSet(false, true)) 
         { 
          // Authenticate on UI thread 

          runOnUiThread(new Runnable() { 
           @Override 
           public void run() { 
            // Force a token refresh during authentication. 
            SharedPreferences pref = context.getSharedPreferences(Constants.SHARED_PREF_FILE, Context.MODE_PRIVATE); 
            MobileServiceAuthenticationProvider provider = Utilities.getProviderFromName(pref.getString(Constants.LAST_PROVIDER_PREF, null)); 
            authenticate(context, provider, true); 
           } 
          }); 
         } 

         // Wait for authentication to complete then update the token in the request. 
         waitAndUpdateRequestToken(request); 
         mAtomicAuthenticatingFlag.set(false); 
        } 
       } 
      } 
     } 
     return future; 
    } 
} 

的身份驗證方法(我修改了對話和主要活動的正確顯示一些小事,但它的工作方式應該是相同的,從微軟原碼):

/** 
    * Returns true if mClient is not null; 
    * A standard sign-in requires the client to contact both the identity 
    * provider and the back-end Azure service every time the app starts. 
    * This method is inefficient, and you can have usage-related issues if 
    * many customers try to start your app simultaneously. A better approach is 
    * to cache the authorization token returned by the Azure service, and try 
    * to use this first before using a provider-based sign-in. 
    * This authenticate method uses a token cache. 
    * 
    * Authenticates with the desired login provider. Also caches the token. 
    * 
    * If a local token cache is detected, the token cache is used instead of an actual 
    * login unless bRefresh is set to true forcing a refresh. 
    * 
    * @param bRefreshCache 
    *   Indicates whether to force a token refresh. 
    */ 
    public boolean authenticate(final Context context, MobileServiceAuthenticationProvider provider, final boolean bRefreshCache) { 
     if (mClient== null) 
      return false; 
     final ProgressDialog pd = null;//Utilities.createAndShowProgressDialog(context, "Logging in", "Log in"); 

     bAuthenticating = true; 

     // First try to load a token cache if one exists. 
     if (!bRefreshCache && loadUserTokenCache(context)) { 
      Log.d(Constants.TAG, logClassIdentifier+"User cached token loaded successfully"); 

      // Other threads may be blocked waiting to be notified when 
      // authentication is complete. 
      synchronized(mAuthenticationLock) 
      { 
       bAuthenticating = false; 
       mAuthenticationLock.notifyAll(); 
      } 

      QueryManager.getUser(context, mClient, mClient.getCurrentUser().getUserId(), pd); 
      return true; 
     }else{ 
      Log.d(Constants.TAG, logClassIdentifier+"No cached token found or bRefreshCache"); 
     } 

     // If we failed to load a token cache, login and create a token cache 
     init(context);//update context for client 

     ListenableFuture<MobileServiceUser> mLogin = mClient.login(provider); 

     Futures.addCallback(mLogin, new FutureCallback<MobileServiceUser>() { 
      @Override 
      public void onFailure(Throwable exc) { 
       String msg = exc.getMessage(); 
       if (msg.equals("User Canceled")) 
        return; 

       if (pd!= null && pd.isShowing()) 
        pd.dismiss(); 
       createAndShowDialog(context, msg, "Error"); 

       synchronized(mAuthenticationLock) 
       { 
        bAuthenticating = false; 
        mAuthenticationLock.notifyAll(); 
       } 

      } 
      @Override 
      public void onSuccess(MobileServiceUser user) { 
       cacheUserToken(context, mClient.getCurrentUser()); 
       if(!bRefreshCache)//otherwise main activity is launched even from other activity (like shop activity) 
        QueryManager.getUser(context, mClient, mClient.getCurrentUser().getUserId(), pd);//loads user's info and shows MainActivity 
       else if (pd!= null && pd.isShowing()) 
        pd.dismiss(); 
       synchronized(mAuthenticationLock) 
       { 
        bAuthenticating = false; 
        mAuthenticationLock.notifyAll(); 
       } 
       ClientUtility.UserId = mClient.getCurrentUser().getUserId(); 
      } 
     }); 

     return true; 
    } 
+0

我找不到刷新過期令牌的完整示例。是什麼讓它更復雜是,mClient綁定到活動上下文,當令牌過期時,它可能會被銷燬。微軟沒有提供處理這種情況的方法。 –

回答

0

誤差java.lang.ClassCastException: android.app.Application cannot be cast to android.app.Activity通過該方法MobileServiceClient.setContext引起需要的上下文Ô如activity.this,但activity.getApplicationContext()的上下文是整個android應用程序的上下文。這是不正確的用法。

您的需求的官方解決方案顯示在Cache authentication tokens on the client部分,請參考它來嘗試解決您的問題。

+0

謝謝彼得回答我的問題。我已經在我的應用程序中使用身份驗證令牌緩存。但據我瞭解,即使一個緩存的令牌可以過期。事實上,當loadUserToken失敗時,將會請求一個新的令牌,所以,不幸的是,這並沒有解決我的問題。我添加了更多希望使問題更清楚的代碼。 – CatLog02

+0

@Peter Pan我們第一次登錄時,會創建一個mClient對象,但它被綁定到一個活動上下文。在身份驗證令牌過期時,此活動可能會被破壞。那麼你如何刷新令牌?而且,一般來說,當構造函數活動死亡時,如何從不同的活動中調用mCLient的方法?這是一種非常常見的情況。 –

1

我認爲API應該有一個刷新標記而不顯示活動的方法(據我所知,只需要插入憑證;但憑證刷新不需要憑證)。我正在考慮的另一個解決方案是切換到另一個雲服務提供商,放棄在Microsoft Azure上:(