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

C# 当ajax调用失败时在后端续订访问令牌,c#,angularjs,ajax,asp.net-mvc,azure-active-directory,C#,Angularjs,Ajax,Asp.net Mvc,Azure Active Directory,我们有一个ASP.NETMVC5Web应用程序,我们使用AngularJS从MVC控制器(而不是APIController)获取数据。其身份验证使用cookie身份验证链接到Azure AD,默认过期时间为1小时后 该应用程序是一个水疗中心。用户登录后,不会导航到其他页面,只使用ajax($http)调用 到目前为止,我们在Startup.Configuration()中扩展了RedirectToIdentityProvider方法,以识别ajax调用,并在令牌过期时向客户端返回错误403。这样

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

该应用程序是一个水疗中心。用户登录后,不会导航到其他页面,只使用ajax($http)调用

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

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

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);
            }
        }
    });
}
  • InMemoryTokenCache
    是我们对
    TokenCache

  • IsAjaxRequest
    是一个识别ajax调用的函数。其余的都是ASP.NETMVC5模板中的标准配置


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

解决此问题的一种方法是在令牌过期之前刷新它。 在我的例子中,我的应用程序由Node.js服务器提供的许多单页组成。 登录后,我将
token.expires\u存储在服务器端可访问的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);
            }
        };
    }

来源:

这是一个选择,但理论上说我不应该这样做。刷新令牌仅用于获取访问令牌,并且仅当用户在稍后到期后请求某些内容时才使用。我添加了另一个解决方案,希望能有所帮助;)