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