【发布时间】:2017-05-07 00:08:09
【问题描述】:
我创建了一个使用 Azure AD 进行身份验证的 Web 应用程序。 访问令牌在一小时后过期(默认情况下)。 由于我的应用程序主要使用 Telerik Controls,因此它不会对服务器进行任何整页往返。 为了刷新令牌,我实现了 JavaScript 倒计时。 我尝试了不同的方法
- 在令牌过期之前插入隐藏的 IFrame 并使用我的应用内的页面刷新 IFrame 的内容
- 插入隐藏的 IFrame 并使用 Microsoft 登录链接刷新 IFrame 的内容,如 Microsoft 文章中所述:https://docs.microsoft.com/en-us/azure/active-directory/active-directory-v2-protocols-implicit
- 使用 Microsoft 登录链接打开一个临时窗口
- 我还在这里查看了 ADAL JavaScript 库:https://github.com/AzureAD/azure-activedirectory-library-for-js/tree/dev 但这意味着我必须重建整个应用程序
所有方法都不起作用。更糟糕的是:如果我在浏览器中按 F5 进行整页刷新,大约在令牌过期前 5 分钟,页面将不再加载,并且浏览器中的“加载”圈会无休止地旋转。
有没有人有一个可行的方法来处理 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 中用于在令牌过期之前刷新令牌的代码。我监控 Token Expiration 客户端,在 Token 过期前调用这个方法:
[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;
}
}
【问题讨论】:
标签: azure authentication model-view-controller access-token adal