MVC应用程序中的Azure AD ADAL - 令牌过期

时间:2016-12-21 14:19:52

标签: azure authentication model-view-controller access-token adal

我使用Azure AD创建了一个Web应用程序进行身份验证。 访问令牌在一小时后到期(默认情况下)。 由于我的应用程序主要使用Telerik Controls,因此它不会对服务器进行任何整页往返。 为了刷新令牌,我实现了JavaScript倒计时。 我尝试了不同的方法

  1. 插入隐藏的IFrame,并在令牌过期前使用我的应用内的页面刷新IFrame的内容
  2. 插入隐藏的IFrame并使用Microsoft登录链接刷新IFrame的内容,如本文中所述:https://docs.microsoft.com/en-us/azure/active-directory/active-directory-v2-protocols-implicit
  3. 使用Microsoft登录链接打开一个临时窗口
  4. 我还在这里查看了ADAL JavaScript库:https://github.com/AzureAD/azure-activedirectory-library-for-js/tree/dev 但这意味着我必须重建整个应用程序
  5. 所有方法都不起作用。更糟糕的是:如果我通过在浏览器中点击F5进行整页刷新,在令牌到期前大约5分钟,页面就不再加载了,并且#34; loading"浏览器中的圆圈无休止地旋转着。

    有没有人有办法如何处理MVC中的ADAL访问令牌?

    这是配置我的身份验证的代码

     public void ConfigureAuth(IAppBuilder app)
            {
                app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);
    
                app.UseCookieAuthentication(new CookieAuthenticationOptions() { CookieSecure = CookieSecureOption.Always });
    
                app.UseOpenIdConnectAuthentication(
                    new OpenIdConnectAuthenticationOptions
                    {
                        ClientId = clientId,
                        Authority = "https://login.microsoftonline.com/common/",
    
                        TokenValidationParameters = new System.IdentityModel.Tokens.TokenValidationParameters
                        {
                            // instead of using the default validation (validating against a single issuer value, as we do in line of business apps (single tenant apps)), 
                            // we turn off validation
                            //
                            // NOTE:
                            // * In a multitenant scenario you can never validate against a fixed issuer string, as every tenant will send a different one.
                            // * If you don’t care about validating tenants, as is the case for apps giving access to 1st party resources, you just turn off validation.
                            // * If you do care about validating tenants, think of the case in which your app sells access to premium content and you want to limit access only to the tenant that paid a fee, 
                            //       you still need to turn off the default validation but you do need to add logic that compares the incoming issuer to a list of tenants that paid you, 
                            //       and block access if that’s not the case.
                            // * Refer to the following sample for a custom validation logic: https://github.com/AzureADSamples/WebApp-WebAPI-MultiTenant-OpenIdConnect-DotNet
    
                            ValidateIssuer = false
                        },
    
                        Notifications = new OpenIdConnectAuthenticationNotifications()
                        {
                            // If there is a code in the OpenID Connect response, redeem it for an access token and refresh token, and store those away. 
                            AuthorizationCodeReceived = (context) =>
                            {
                                var code = context.Code;
    
                                ClientCredential credential = new ClientCredential(clientId, appKey);
                                string tenantID = context.AuthenticationTicket.Identity.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
                                string signInUserId = context.AuthenticationTicket.Identity.FindFirst(ClaimTypes.NameIdentifier).Value;
    
                                AuthenticationContext authContext = new AuthenticationContext(string.Format("{0}/{1}", "https://login.microsoftonline.com", tenantID), new ADALTokenCache(signInUserId));
    
                                // Get the access token for AAD Graph. Doing this will also initialize the token cache associated with the authentication context
                                // In theory, you could acquire token for any service your application has access to here so that you can initialize the token cache
                                AuthenticationResult result = authContext.AcquireTokenByAuthorizationCode(code, new Uri(HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Path)), credential, "https://graph.windows.net");
    
                                return Task.FromResult(0);
                            },
    
                            RedirectToIdentityProvider = (context) =>
                            {
                                // This ensures that the address used for sign in and sign out is picked up dynamically from the request
                                // this allows you to deploy your app (to Azure Web Sites, for example)without having to change settings
                                // Remember that the base URL of the address used here must be provisioned in Azure AD beforehand.
                                string appBaseUrl = context.Request.Scheme + "://" + context.Request.Host + context.Request.PathBase;
                                context.ProtocolMessage.RedirectUri = appBaseUrl + "/";
                                context.ProtocolMessage.PostLogoutRedirectUri = appBaseUrl;
    
                                return Task.FromResult(0);
                            },
    
                            AuthenticationFailed = (context) =>
                            {
                                // Suppress the exception if you don't want to see the error
                                context.HandleResponse();
                                return Task.FromResult(0);
                            }
                        }
    
                    });
            }
    

    这是我的BaseController中的代码,用于在令牌过期之前刷新它。我监控令牌过期客户端并在令牌过期之前调用此方法:

    [HttpGet]
    public ActionResult RefreshAuthenticationToken()
    {
        var refreshToken = DirectoryUserRepository.GetTokenForApplication().Result.RefreshToken;
        var newToken = DirectoryUserRepository.RefreshToken(refreshToken);
        return Json(new { TokenExpirationTimestampUTC = newToken.ExpiresOn.GetUnixTimestamp() }, JsonRequestBehavior.AllowGet);
    }
    

    以下是其他遗漏方法

    public async Task<AuthenticationToken> GetTokenForApplication()
            {
                string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
                string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
                string userObjectID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier").Value;
    
                // get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
                ClientCredential clientcred = new ClientCredential(clientId, appKey);
                // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
                AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID, new ADALTokenCache(signedInUserID));
                try
                {
    
                    var authenticationResult = await authenticationContext
                        .AcquireTokenSilentAsync(
                      graphResourceID,
                      new ClientCredential(clientId, appKey),
                      new UserIdentifier(userObjectID, UserIdentifierType.UniqueId));
                    return new AuthenticationToken(authenticationResult.AccessToken) { ExpiresOn = authenticationResult.ExpiresOn, RefreshToken = authenticationResult.RefreshToken };
    
                    //return authenticationResult.AccessToken;
    
                }
                catch (AggregateException e)
                {
                    foreach (Exception inner in e.InnerExceptions)
                    {
                        if (!(inner is AdalException)) continue;
                        if (((AdalException)inner).ErrorCode == AdalError.FailedToAcquireTokenSilently)
                        {
                            authenticationContext.TokenCache.Clear();
                        }
                    }
                    throw e.InnerException;
                }
                catch (AdalException exception)
                {
                    if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
                    {
                        authenticationContext.TokenCache.Clear();
                        throw;
                    }
                    return null;
                }
            }
    
    
    
      public AuthenticationToken RefreshToken(string refreshToken)
            {
                string signedInUserID = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value;
                string tenantID = ClaimsPrincipal.Current.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid").Value;
    
                // get a token for the Graph without triggering any user interaction (from the cache, via multi-resource refresh token, etc)
                ClientCredential clientcred = new ClientCredential(clientId, appKey);
                // initialize AuthenticationContext with the token cache of the currently signed in user, as kept in the app's database
                AuthenticationContext authenticationContext = new AuthenticationContext(aadInstance + tenantID, new ADALTokenCache(signedInUserID));
                try
                {
    
                    var authenticationResult = authenticationContext
                        .AcquireTokenByRefreshToken(
                        refreshToken,
                        clientcred,
                        graphResourceID);
                    return new AuthenticationToken(authenticationResult.AccessToken) { ExpiresOn = authenticationResult.ExpiresOn, RefreshToken = authenticationResult.RefreshToken };
                }
                catch (AggregateException e)
                {
                    foreach (Exception inner in e.InnerExceptions)
                    {
                        if (!(inner is AdalException)) continue;
                        if (((AdalException)inner).ErrorCode == AdalError.FailedToAcquireTokenSilently)
                        {
                            authenticationContext.TokenCache.Clear();
                        }
                    }
                    throw e.InnerException;
                }
                catch (AdalException exception)
                {
                    if (exception.ErrorCode == AdalError.FailedToAcquireTokenSilently)
                    {
                        authenticationContext.TokenCache.Clear();
                        throw;
                    }
                    return null;
                }
            }
    

1 个答案:

答案 0 :(得分:1)

我们可以从Web应用程序使用两种访问令牌来调用Web API。

首先,使用委托用户身份和OAuth 2.0授权代码授予流程。第二个是将应用程序标识与OAuth 2.0客户端凭据授权流程一起使用。

当我们使用委托的用户令牌时,我们可以通过Web应用程序服务器端刷新令牌,然后令牌过期。

如果您使用应用程序标识获取令牌,我们可以使用应用程序的凭据再次获取访问令牌。

  

由于我的应用程序主要使用Telerik Controls,因此它不会向服务器进行任何整页往返。

您是说使用AJAX接收数据的控件?如果我理解正确,我们可以在MVC应用程序中开发代理来获取数据。在代理服务中,我们可以在令牌过期时续订令牌。

以下是关于Web应用程序调用Web API的流程图:

enter image description here

更新(通过HTTP请求刷新访问令牌)

 public static void RefreshToken(string refreshToken)
 {
        HttpClient client = new HttpClient();
        string clientId = "{clientId}";
        string secret = "{secret}";
        string resource = "https://graph.windows.net";
        StringBuilder sb = new StringBuilder();
        sb.Append($"client_id={clientId}");
        sb.Append($"&grant_type=refresh_token");
        sb.Append($"&client_secret={secret}");
        sb.Append($"&resource={resource}");
        sb.Append($"&refresh_token={refreshToken}");

        HttpContent bodyContent = new StringContent(sb.ToString(), Encoding.UTF8, "application/x-www-form-urlencoded");
        var tokenResponse = client.PostAsync("https://login.microsoftonline.com/common/oauth2/token", bodyContent).Result;
        var stringResponse = tokenResponse.Content.ReadAsStringAsync().Result;
        JObject jObject = JObject.Parse(stringResponse);
        Console.WriteLine(jObject["access_token"].Value<string>());
 }