gpt4 book ai didi

c# - 如何使用 Active Directory 存储在 AcquireTokenAsync 中收到的 token

转载 作者:太空狗 更新时间:2023-10-29 17:44:22 31 4
gpt4 key购买 nike

问题陈述

我正在使用 .NET Core,并且正在尝试使 Web 应用程序与 Web API 通信。两者都需要使用 [Authorize] 进行身份验证属性在他们所有的类上。为了能够在它们之间进行服务器到服务器通信,我需要检索验证 token 。感谢 a Microsoft tutorial 我能够做到这一点.

问题

在本教程中,他们使用对 AcquireTokenByAuthorizationCodeAsync 的调用。为了把token保存在缓存中,这样在其他地方,代码就可以做一个AcquireTokenSilentAsync ,这不需要去管理局验证用户。

This method does not lookup token cache, but stores the result in it, so it can be looked up using other methods such as AcquireTokenSilentAsync



当用户已经登录时,问题就会出现。方法存储在 OpenIdConnectEvents.OnAuthorizationCodeReceived永远不会被调用,因为没有收到授权。只有在有新登录时才会调用该方法。

还有一个事件叫做: CookieAuthenticationEvents.OnValidatePrincipal当用户仅通过 cookie 进行验证时。这有效,我可以获得 token ,但我必须使用 AcquireTokenAsync ,因为我当时没有授权码。根据文档,它

Acquires security token from the authority.



这使得调用 AcquireTokenSilentAsync失败,因为 token 尚未缓存。我宁愿不总是使用 AcquireTokenAsync ,因为那总是去管理局。



我怎么知道 AcquireTokenAsync得到的token被缓存以便我可以使用 AcquireTokenSilentAsync其他地方?

相关代码

这一切都来自主 Web 应用程序项目中的 Startup.cs 文件。

这是事件处理的完成方式:
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
Events = new CookieAuthenticationEvents()
{
OnValidatePrincipal = OnValidatePrincipal,
}
});

app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
ClientId = ClientId,
Authority = Authority,
PostLogoutRedirectUri = Configuration["AzureAd:PostLogoutRedirectUri"],
ResponseType = OpenIdConnectResponseType.CodeIdToken,
CallbackPath = Configuration["Authentication:AzureAd:CallbackPath"],
GetClaimsFromUserInfoEndpoint = false,

Events = new OpenIdConnectEvents()
{
OnRemoteFailure = OnAuthenticationFailed,
OnAuthorizationCodeReceived = OnAuthorizationCodeReceived,
}
});

这些是背后的事件:
private async Task OnValidatePrincipal(CookieValidatePrincipalContext context)
{
string userObjectId = (context.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
ClientCredential clientCred = new ClientCredential(ClientId, ClientSecret);
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectId, context.HttpContext.Session));
AuthenticationResult authResult = await authContext.AcquireTokenAsync(ClientResourceId, clientCred);

// How to store token in authResult?
}

private async Task OnAuthorizationCodeReceived(AuthorizationCodeReceivedContext context)
{
// Acquire a Token for the Graph API and cache it using ADAL. In the TodoListController, we'll use the cache to acquire a token to the Todo List API
string userObjectId = (context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier"))?.Value;
ClientCredential clientCred = new ClientCredential(ClientId, ClientSecret);
AuthenticationContext authContext = new AuthenticationContext(Authority, new NaiveSessionCache(userObjectId, context.HttpContext.Session));
AuthenticationResult authResult = await authContext.AcquireTokenByAuthorizationCodeAsync(
context.ProtocolMessage.Code, new Uri(context.Properties.Items[OpenIdConnectDefaults.RedirectUriForCodePropertiesKey]), clientCred, GraphResourceId);

// Notify the OIDC middleware that we already took care of code redemption.
context.HandleCodeRedemption();
}

// Handle sign-in errors differently than generic errors.
private Task OnAuthenticationFailed(FailureContext context)
{
context.HandleResponse();
context.Response.Redirect("/Home/Error?message=" + context.Failure.Message);
return Task.FromResult(0);
}

任何其他代码都可以在链接的教程中找到,或者询问,我会将其添加到问题中。

最佳答案

(注意:我已经在这个确切的问题上苦苦挣扎了好几天。我遵循了与问题中链接的相同的 Microsoft 教程,并跟踪了各种问题,例如野鹅追逐;事实证明该示例包含一大堆看似使用最新版本的 Microsoft.AspNetCore.Authentication.OpenIdConnect 包时不必要的步骤。)。

当我读到这个页面时,我终于有了一个突破性的时刻:
http://docs.identityserver.io/en/release/quickstarts/5_hybrid_and_api_access.html

该解决方案主要涉及让 OpenID Connect auth 将各种 token ( access_tokenrefresh_token )放入 cookie。

首先,我使用的是 融合应用创建于 https://apps.dev.microsoft.com和 Azure AD 端点的 v2.0。该应用程序具有应用程序 key (密码/公钥)并使用 Allow Implicit Flow对于 Web 平台。

(出于某种原因,端点的 v2.0 似乎不适用于仅限 Azure AD 的应用程序。我不确定为什么,也不确定它是否真的很重要。)

相关线路来自启动.配置 方法:

    // Configure the OWIN pipeline to use cookie auth.
app.UseCookieAuthentication(new CookieAuthenticationOptions());

// Configure the OWIN pipeline to use OpenID Connect auth.
var openIdConnectOptions = new OpenIdConnectOptions
{
ClientId = "{Your-ClientId}",
ClientSecret = "{Your-ClientSecret}",
Authority = "http://login.microsoftonline.com/{Your-TenantId}/v2.0",
ResponseType = OpenIdConnectResponseType.CodeIdToken,
TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
},
GetClaimsFromUserInfoEndpoint = true,
SaveTokens = true,
};

openIdConnectOptions.Scope.Add("offline_access");

app.UseOpenIdConnectAuthentication(openIdConnectOptions);

就是这样!否 OpenIdConnectOptions.Event回调。不接电话 AcquireTokenAsyncAcquireTokenSilentAsync .否 TokenCache .这些东西似乎都不是必需的。

魔法似乎是 OpenIdConnectOptions.SaveTokens = true 的一部分

这是我使用访问 token 代表使用 Office365 帐户的用户发送电子邮件的示例。

我有一个 WebAPI Controller 操作,它使用 HttpContext.Authentication.GetTokenAsync("access_token") 获取访问 token :
    [HttpGet]
public async Task<IActionResult> Get()
{
var graphClient = new GraphServiceClient(new DelegateAuthenticationProvider(async requestMessage =>
{
var accessToken = await HttpContext.Authentication.GetTokenAsync("access_token");
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", accessToken);
}));

var message = new Message
{
Subject = "Hello",
Body = new ItemBody
{
Content = "World",
ContentType = BodyType.Text,
},
ToRecipients = new[]
{
new Recipient
{
EmailAddress = new EmailAddress
{
Address = "email@address.com",
Name = "Somebody",
}
}
},
};

var request = graphClient.Me.SendMail(message, true);
await request.Request().PostAsync();

return Ok();
}

旁注#1

在某些时候,您可能还需要获取 refresh_token同样,如果 access_token 过期:
HttpContext.Authentication.GetTokenAsync("refresh_token")

旁注#2

我的 OpenIdConnectOptions实际上还包括一些我在此省略的内容,例如:
    openIdConnectOptions.Scope.Add("email");
openIdConnectOptions.Scope.Add("Mail.Send");

我已经用这些来处理 Microsoft.Graph API 代表当前登录的用户发送电子邮件。

(Microsoft Graph 的那些委派权限也是在应用程序上设置的)。

更新 - 如何“静默”刷新 Azure AD 访问 token

到目前为止,这个答案解释了如何使用缓存的访问 token ,而不是在 token 过期时(通常在 1 小时后)做什么。

选项似乎是:
  • 强制用户重新登录。 (不沉默)
  • 使用 refresh_token 向 Azure AD 服务发布请求获取新的 access_token (沉默的)。

  • 如何使用端点 v2.0 刷新访问 token

    经过更多的挖掘,我在这个 SO Question 中找到了部分答案:

    How to handle expired access token in asp.net core using refresh token with OpenId Connect

    Microsoft OpenIdConnect 库似乎不会为您刷新访问 token 。不幸的是,上述问题的答案缺少关于 的关键细节。怎么样刷新 token ;大概是因为它取决于 OpenIdConnect 不关心的 Azure AD 的具体细节。

    上述问题的已接受答案建议直接向 Azure AD token REST API 发送请求,而不是使用 Azure AD 库之一。

    这是相关文档(注意:这涵盖了 v1.0 和 v2.0 的混合)
  • https://developer.microsoft.com/en-us/graph/docs/concepts/rest
  • https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code#refreshing-the-access-tokens

  • 这是基于 API 文档的代理:
    public class AzureAdRefreshTokenProxy
    {
    private const string HostUrl = "https://login.microsoftonline.com/";
    private const string TokenUrl = $"{Your-Tenant-Id}/oauth2/v2.0/token";
    private const string ContentType = "application/x-www-form-urlencoded";

    // "HttpClient is intended to be instantiated once and re-used throughout the life of an application."
    // - MSDN Docs:
    // https://msdn.microsoft.com/en-us/library/system.net.http.httpclient(v=vs.110).aspx
    private static readonly HttpClient Http = new HttpClient {BaseAddress = new Uri(HostUrl)};

    public async Task<AzureAdTokenResponse> RefreshAccessTokenAsync(string refreshToken)
    {
    var body = $"client_id={Your-Client-Id}" +
    $"&refresh_token={refreshToken}" +
    "&grant_type=refresh_token" +
    $"&client_secret={Your-Client-Secret}";
    var content = new StringContent(body, Encoding.UTF8, ContentType);

    using (var response = await Http.PostAsync(TokenUrl, content))
    {
    var responseContent = await response.Content.ReadAsStringAsync();
    return response.IsSuccessStatusCode
    ? JsonConvert.DeserializeObject<AzureAdTokenResponse>(responseContent)
    : throw new AzureAdTokenApiException(
    JsonConvert.DeserializeObject<AzureAdErrorResponse>(responseContent));
    }
    }
    }
    AzureAdTokenResponseAzureAdErrorResponse JsonConvert 使用的类:
    [JsonObject(MemberSerialization = MemberSerialization.OptIn)]
    public class AzureAdTokenResponse
    {
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "token_type", Required = Required.Default)]
    public string TokenType { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "expires_in", Required = Required.Default)]
    public int ExpiresIn { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "expires_on", Required = Required.Default)]
    public string ExpiresOn { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "resource", Required = Required.Default)]
    public string Resource { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "access_token", Required = Required.Default)]
    public string AccessToken { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "refresh_token", Required = Required.Default)]
    public string RefreshToken { get; set; }
    }

    [JsonObject(MemberSerialization = MemberSerialization.OptIn)]
    public class AzureAdErrorResponse
    {
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "error", Required = Required.Default)]
    public string Error { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "error_description", Required = Required.Default)]
    public string ErrorDescription { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "error_codes", Required = Required.Default)]
    public int[] ErrorCodes { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "timestamp", Required = Required.Default)]
    public string Timestamp { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "trace_id", Required = Required.Default)]
    public string TraceId { get; set; }
    [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "correlation_id", Required = Required.Default)]
    public string CorrelationId { get; set; }
    }

    public class AzureAdTokenApiException : Exception
    {
    public AzureAdErrorResponse Error { get; }

    public AzureAdTokenApiException(AzureAdErrorResponse error) :
    base($"{error.Error} {error.ErrorDescription}")
    {
    Error = error;
    }
    }

    最后,我对 的修改Startup.cs 刷新 access_token(基于我上面链接的答案)
            // Configure the OWIN pipeline to use cookie auth.
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
    Events = new CookieAuthenticationEvents
    {
    OnValidatePrincipal = OnValidatePrincipal
    },
    });
    OnValidatePrincipal处理程序在 Startup.cs (同样,来自上面链接的答案):
        private async Task OnValidatePrincipal(CookieValidatePrincipalContext context)
    {
    if (context.Properties.Items.ContainsKey(".Token.expires_at"))
    {
    if (!DateTime.TryParse(context.Properties.Items[".Token.expires_at"], out var expiresAt))
    {
    expiresAt = DateTime.Now;
    }

    if (expiresAt < DateTime.Now.AddMinutes(-5))
    {
    var refreshToken = context.Properties.Items[".Token.refresh_token"];
    var refreshTokenService = new AzureAdRefreshTokenService();
    var response = await refreshTokenService.RefreshAccessTokenAsync(refreshToken);

    context.Properties.Items[".Token.access_token"] = response.AccessToken;
    context.Properties.Items[".Token.refresh_token"] = response.RefreshToken;
    context.Properties.Items[".Token.expires_at"] = DateTime.Now.AddSeconds(response.ExpiresIn).ToString(CultureInfo.InvariantCulture);
    context.ShouldRenew = true;
    }
    }
    }

    最后,使用 Azure AD API v2.0 的 OpenIdConnect 解决方案。

    有趣的是,v2.0 似乎并没有要求 resource包含在 API 请求中;文档表明这是必要的,但 API 本身只是回复 resource不支持。这可能是一件好事 - 大概这意味着访问 token 适用于所有资源(它当然适用于 Microsoft Graph API)

    关于c# - 如何使用 Active Directory 存储在 AcquireTokenAsync 中收到的 token ,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41519132/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com