当ajax调用失败时,在后端续订访问令牌

时间:2017-04-05 21:48:30

标签: c# angularjs ajax asp.net-mvc azure-active-directory

我们有一个ASP.NET MVC 5 Web应用程序,我们使用AngularJS从MVC控制器(而不是ApiControllers)获取数据。其身份验证使用cookie身份验证链接到Azure AD,默认过期时间为1小时。

该应用程序是SPA。一旦用户登录,他们就不会导航到其他页面,而只使用ajax($ http)调用。

到目前为止,我们在Startup.Configuration()中扩展了RedirectToIdentityProvider方法,以识别ajax调用,并在令牌过期时将错误403返回给客户端。这样,我们避免重定向到权限页面并获得CORS错误。

此外,我们在同一类的TokenCache中实现了持久性令牌缓存助手Microsoft.IdentityModel.Clients.ActiveDirectory(名称空间AuthorizationCodeReceived)。

app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
app.UseCookieAuthentication(new CookieAuthenticationOptions());

app.UseOpenIdConnectAuthentication(
    new OpenIdConnectAuthenticationOptions
    {
        ClientId = ConfigurationHelper.ClientId,
        Authority = ConfigurationHelper.AzureAdAuthorizationUri,

        TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
        {
            ValidateIssuer = true
        },

        Notifications = new OpenIdConnectAuthenticationNotifications()
        {
            AuthorizationCodeReceived = (context) =>
            {
                var code = context.Code;

                ClientCredential credential = new ClientCredential(ConfigurationHelper.ClientId, ConfigurationHelper.AppKey);
                String UserObjectId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;

                AuthenticationContext authContext = new AuthenticationContext(ConfigurationHelper.AzureAdAuthorizationUri, new InMemoryTokenCache(UserObjectId));

                AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, ConfigurationHelper.AzureAdGraphResourceUri);

                return Task.FromResult(0);
            },


            RedirectToIdentityProvider = (context) =>
            {
                if (IsAjaxRequest(context.Request))
                {
                    context.Response.StatusCode = 401; // for web API only!
                    context.Response.Headers.Remove("Set-Cookie");
                    context.State = NotificationResultState.HandledResponse;
                }
                else
                {
                    string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;

                    context.ProtocolMessage.RedirectUri = appBaseUrl + "/" + context.Request.QueryString;
                    context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
                }

                return Task.FromResult(0);
            },

            AuthenticationFailed = (context) =>
            {
                // Suppress the exception
                context.HandleResponse();

                return Task.FromResult(0);
            }
        }
    });
}
  • InMemoryTokenCacheTokenCache

  • 的封套
  • IsAjaxRequest是一个识别ajax调用的函数。所有其余的都是ASP.NET MVC 5模板的标准。

我们的问题是,当用户访问令牌过期时,我们希望刷新它并继续运行,而无需将用户重定向到登录屏幕或将403返回到客户端。我们应该在哪里以及如何做到这一点

1 个答案:

答案 0 :(得分:1)

解决此问题的一种方法是在令牌过期前刷新一会儿。 就我而言,我的应用程序由Node.js服务器提供的许多单页组成。 登录后,我将token.expires_in值存储在cookie中,可在服务器端访问。

当用户导航或按F5刷新页面时,服务器使用tokenExpiresIn初始化客户端上下文。 如果令牌在100分钟后过期,则会在90分钟后自动刷新。

示例代码

angular.module('app').run(function() {

    var tokenExpiresIn = context['tokenExpiresIn'];
    if (tokenExpiresIn) {
      refreshToken(tokenExpiresIn);
    }

    // Automatically refresh token after a delay
    function refreshToken(delay) {
      $log.debug('Token will be refreshed in ' + delay + ' ms');
      $timeout(function () {
        AuthenticationService.refreshToken().then(
        function (token) {
          // Token refresh successful
          // Broadcast event so that anyone can react if necessary
          $rootScope.$broadcast(AuthenticationService.Events.REFRESH_TOKEN, token);
          // Refresh token again after this one expires
          refreshToken(token.expires_in * 1000 * (90/100);
        }, function (error) {
          // Token is invalid, force logout
          AuthenticationService.logout();
        });
      }, delay);
    }

});

使用身份验证拦截器的另一种方法

angular
    .module('app')
    .factory('authenticationHTTP401Interceptor', authenticationHTTP401Interceptor)

    // Intercept 401 Unauthorized http response from Backend
    authenticationHTTP401Interceptor.$inject = ['$q'];
    function moAuthenticationHTTP401Interceptor($q) {
        return {
            responseError: function(rejection) {
              if (rejection.status === 401 
                && rejection.config.url
                && rejection.config.url.indexOf(context.BACKEND_BASE_URL') === 0
                && rejection.headers("WWW-Authenticate")
                && rejection.headers("WWW-Authenticate").indexOf('error="invalid_token"') !== -1
                && rejection.headers("WWW-Authenticate").indexOf('error_description="The access token expired"') !== -1
              )
              // Or using a RegExp
              // if (rejection.status === 401 
              //    && /invalid_token.*The access token  expired/.test(rejection.headers("WWW-Authenticate"))
              // )  
              {
                // Refresh token here
                // Display an overlay while doing it if necessary
              }
              return $q.reject(rejection);
            }
        };
    }

来源:RFC 6750