2017-02-18 5 views
1

Я создаю приложение для Android с лазурным мобильным сервисом. У меня есть служба, которая работает всегда (с startForeground()) и отслеживает некоторые действия пользователя. Службе иногда требуется запрашивать лазурную базу данных вызывающей API-интерфейсы, которые хранятся в лазурном облаке, таким образом:Обновите токен пользователя Azure в службе без активности в android

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

В начале логинов пользователей с помощью LoginActivity и все работает отлично. Через некоторое время (обычно 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) 

Так как я могу обновить токен от службы без активности? Или существует другой способ, чтобы клиент всегда аутентифицировался?

EDIT

Я стараюсь, чтобы вставить сюда код, в надежде сделать более ясным, как я использую маркер аутентификации. У меня есть 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; 
    } 
} 

Метод Аутентифицировать (я изменил некоторые мелочи для правильного показа диалога и основной деятельности, но так, как это работает должно быть таким же, как исходный код от Microsoft):

/** 
    * 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 привязан к который, к моменту истечения срока действия токена, вероятно, будет уничтожен. Microsoft не предоставляет метод для обработки этого случая. –

ответ

0

java.lang.ClassCastException: android.app.Application cannot be cast to android.app.Activity ошибка была вызвана методом MobileServiceClient.setContext нужен контекст O f Activity как activity.this, но контекст от activity.getApplicationContext() - это контекст для всего приложения для Android. Это неправильное использование.

Официальное решение для ваших нужд показано в разделе Cache authentication tokens on the client, пожалуйста, обратитесь к нему, чтобы решить проблему.

+0

Спасибо Петру за ответ на мой вопрос.Я уже использую кеширование кеш аутентификации в своем приложении. Но, насколько я понимаю, даже кешированный токен может истечь. Фактически, когда loadUserToken терпит неудачу, будет запрошен новый токен, поэтому, к сожалению, это не решило мою проблему. Я добавил больше кода, надеясь сделать проблему более ясной. – CatLog02

+0

@Peter Pan При первом входе в систему создается объект mClient, но он связан с контекстом активности. К моменту истечения токена аутентификации эта активность, возможно, будет уничтожена. Как вы можете обновить токен? И вообще, как вы можете вызвать методы mCLient из разных видов деятельности, когда активность конструктора мертва? Это очень распространенный сценарий. –

1

Я думаю, что API должен иметь метод обновления токенов без отображения активности (что, насколько я понимаю, необходимо только для вставки учетных данных, но учетные данные не нужны для обновления токенов). Еще одно решение, о котором я думаю, заключается в том, чтобы переключиться на другого поставщика облачных сервисов, отказавшись от Microsoft Azure :(

Смежные вопросы