【问题标题】:Refresh Azure user token in a Service without an activity in android在没有活动的情况下刷新服务中的 Azure 用户令牌 android
【发布时间】:2017-02-18 12:32:25
【问题描述】:

我正在使用 azure 移动服务创建一个 android 应用程序。我有一个始终运行的服务(使用 startForeground())并且正在监视一些用户活动。该服务有时需要查询 Azure 数据库调用 API,存储在 Azure 云中,这种方式:

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

如果活动处于活动状态,则代码工作并成功刷新令牌(如果过期)。但是,如果活动被破坏,它就不起作用。所以我决定将 ApplicationContext 传递给客户端,这样:

mClient.setContext(activity.getApplicationContext());

但现在我收到 ClassCastException,因为客户端尝试将上下文强制转换为 Activity。以下是异常的有趣行:

     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 绑定到一个活动上下文,当令牌过期时,该上下文可能已经被销毁。微软没有提供处理这种情况的方法。

标签: android azure azure-mobile-services


【解决方案1】:

我认为 API 应该有一种在不显示活动的情况下刷新令牌的方法(据我所知,只需要插入凭据;但刷新令牌不需要凭据)。我正在考虑的另一个解决方案是切换到不同的云服务提供商,放弃 Microsoft Azure:(

【讨论】:

    【解决方案2】:

    错误java.lang.ClassCastException: android.app.Application cannot be cast to android.app.Activity 是由方法MobileServiceClient.setContext 引起的,需要Activity 的上下文,如activity.this,但来自activity.getApplicationContext() 的上下文是整个android 应用程序的上下文。这是错误的用法。

    您需要的官方解决方案在Cache authentication tokens on the client部分显示,请参考它来尝试解决您的问题。

    【讨论】:

    • 感谢彼得回答我的问题。我已经在我的应用程序上使用了身份验证令牌缓存。但据我了解,即使是缓存的令牌也会过期。事实上,当 loadUserToken 失败时,会请求一个新的令牌,所以很遗憾,这并没有解决我的问题。我添加了更多代码,希望能更清楚地说明问题。
    • @Peter Pan 我们第一次登录时,创建了一个 mClient 对象,但它绑定到一个活动上下文。到身份验证令牌过期时,此活动可能已被破坏。那你怎么能刷新令牌呢?而且,一般来说,当构造函数活动停止时,如何从不同的活动中调用 mCLient 的方法?这是一种非常常见的情况。
    猜你喜欢
    • 2020-02-27
    • 1970-01-01
    • 1970-01-01
    • 2018-10-14
    • 2016-06-27
    • 2013-09-05
    • 2015-03-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多