我有 ASP.NET Core Web 应用程序,需要将 Azure AD 访问令牌发送到 PHP 脚本。但我无法使用 GetTokenAsync 从 http 上下文获取令牌 - 它总是返回 null。
在我的 Startup.cs 中,我按照 Microsoft Learn 的记录配置 openid connect:
public void ConfigureServices(IServiceCollection services)
{
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddControllersWithViews();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(options =>
{
IConfigurationSection sectionOptions = AppConfiguration?.GetSection("OpenIdConnectOptions");
if(sectionOptions?.GetChildren()?.Any() == true)
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = sectionOptions["Authority"];
options.ClientId = sectionOptions["ClientId"];
options.ClientSecret = sectionOptions["ClientSecret"];
options.CallbackPath = new PathString(sectionOptions["CallbackPath"]);
options.SignedOutCallbackPath = new PathString(sectionOptions["SignedOutCallbackPath"]);
options.RemoteSignOutPath = new PathString(sectionOptions["RemoteSignOutPath"]);
options.ResponseType = OpenIdConnectResponseType.Code;
options.ResponseMode = OpenIdConnectResponseMode.Query;
options.GetClaimsFromUserInfoEndpoint = true;
List<string> scopes =
(from curSubsectionScope in sectionOptions.GetSection("Scope")?.GetChildren()
where !string.IsNullOrWhiteSpace(curSubsectionScope.Value)
select curSubsectionScope.Value.Trim().ToLower())?.ToList();
options.Scope?.Clear();
scopes?.ForEach(options.Scope.Add);
options.MapInboundClaims = false;
options.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
options.TokenValidationParameters.RoleClaimType = ClaimTypes.Role;
}
}
}
appsettings.json:
"OpenIdConnectOptions": {
"Authority": "https://login.microsoftonline.com/{my-tenant-id}/v2.0/",
"ClientId": "{my-client-id}",
"ClientSecret": "{my-client-secret}",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath": "/signout-callback-oidc",
"RemoteSignOutPath": "/signout-oidc",
"RedirectUri": "http://localhost:80/azure",
"SaveTokens": true,
"Scope": [ "openid", "offline_access" ]
},
我的 Azure AD 应用程序设置:
az ad app show --id $clientId --query "web.redirectUris"
[
"https://localhost/signin-oidc",
"https://localhost/signout-callback-oidc",
"https://localhost/signout-oidc",
"http://localhost/azure"
]
az ad app permission list --id $clientId
[
{
"resourceAccess": [
{
"id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d", # User.Read
"type": "Scope"
},
{
"id": "1bfefb4e-e0b5-418b-a88f-73c46d2cc8e9", # Application.ReadWrite.All
"type": "Role"
},
{
"id": "37f7f235-527c-4136-accd-4a02d197296e", # openid
"type": "Scope"
},
{
"id": "7427e0e9-2fba-42fe-b0c0-848c9e6a8182", # offline_access
"type": "Scope"
}
],
"resourceAppId": "00000003-0000-0000-c000-000000000000" # https://graph.microsoft.com
}
]
但是当我尝试从 http 上下文获取访问令牌时,适当的方法总是返回 null:
string access_token = await httpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
string id_token = await httpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken);
在调试时,我进入 GetTokenAsync 并发现该进程被 CookieAuthenticationHandler.HandleAuthenticateOnceAsync() 函数中止,该函数返回 AuthenticateResult.Failure。
我的设置有什么问题?
我将以下代码行添加到 ASP.net core 6 Web 应用程序中的 Program.cs 中,并获取了访问令牌和 ID 令牌。
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(options =>
{
IConfigurationSection sectionOptions = builder.Configuration.GetSection("OpenIdConnectOptions");
if (sectionOptions.GetChildren().Any())
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = sectionOptions["Authority"];
options.ClientId = sectionOptions["ClientId"];
options.ClientSecret = sectionOptions["ClientSecret"];
options.CallbackPath = sectionOptions["CallbackPath"];
options.SignedOutCallbackPath = sectionOptions["SignedOutCallbackPath"];
options.RemoteSignOutPath = sectionOptions["RemoteSignOutPath"];
options.ResponseType = OpenIdConnectResponseType.Code;
options.ResponseMode = OpenIdConnectResponseMode.Query;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
var scopes = sectionOptions.GetSection("Scope").GetChildren()
.Select(scope => scope.Value.Trim().ToLower())
.ToList();
options.Scope.Clear();
scopes.ForEach(scope => options.Scope.Add(scope));
options.MapInboundClaims = false;
options.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
options.TokenValidationParameters.RoleClaimType = ClaimTypes.Role;
}
});
下面是完整的 Program.cs 代码。
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Identity.Web.UI;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect(options =>
{
IConfigurationSection sectionOptions = builder.Configuration.GetSection("OpenIdConnectOptions");
if (sectionOptions.GetChildren().Any())
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = sectionOptions["Authority"];
options.ClientId = sectionOptions["ClientId"];
options.ClientSecret = sectionOptions["ClientSecret"];
options.CallbackPath = sectionOptions["CallbackPath"];
options.SignedOutCallbackPath = sectionOptions["SignedOutCallbackPath"];
options.RemoteSignOutPath = sectionOptions["RemoteSignOutPath"];
options.ResponseType = OpenIdConnectResponseType.Code;
options.ResponseMode = OpenIdConnectResponseMode.Query;
options.GetClaimsFromUserInfoEndpoint = true;
options.SaveTokens = true;
var scopes = sectionOptions.GetSection("Scope").GetChildren()
.Select(scope => scope.Value.Trim().ToLower())
.ToList();
options.Scope.Clear();
scopes.ForEach(scope => options.Scope.Add(scope));
options.MapInboundClaims = false;
options.TokenValidationParameters.NameClaimType = JwtRegisteredClaimNames.Name;
options.TokenValidationParameters.RoleClaimType = ClaimTypes.Role;
}
});
builder.Services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
builder.Services.AddRazorPages()
.AddMicrosoftIdentityUI();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
app.MapRazorPages();
app.Run();
HomeController.cs:
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace WebApplication3
{
public class HomeController : Controller
{
public IActionResult Index()
{
return View();
}
[Authorize]
public async Task<IActionResult> Secure()
{
var authenticateResult = await HttpContext.AuthenticateAsync();
if (authenticateResult.Succeeded)
{
string? accessToken = await HttpContext.GetTokenAsync("access_token");
string? idToken = await HttpContext.GetTokenAsync("id_token");
ViewData["AccessToken"] = accessToken;
ViewData["IdToken"] = idToken;
}
else
{
ViewData["Error"] = "Authentication failed.";
}
return View();
}
}
}
appsettings.json:
"OpenIdConnectOptions": {
"Authority": "https://login.microsoftonline.com/<tenant_ID>/v2.0/",
"Instance": "https://login.microsoftonline.com/",
"Domain": "xxxxxxxxx.onmicrosoft.com",
"TenantId": "<tenant_ID>",
"ClientSecret": "<client_ID>",
"ClientId": "<client_secret>",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath": "/signout-callback-oidc",
"RemoteSignOutPath": "/signout-oidc",
"RedirectUri": "http://localhost:7116/signin-oidc",
"SaveTokens": true,
"Scope": [ "openid", "profile", "offline_access" ]
}
.csproj:
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>aspnet-WebApplication3-a98bec0e-1eab-4fe3-bf2a-0f269133384b</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.31" NoWarn="NU1605" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="6.0.31" NoWarn="NU1605" />
<PackageReference Include="Microsoft.Identity.Web" Version="2.16.0" />
<PackageReference Include="Microsoft.Identity.Web.UI" Version="2.16.0" />
</ItemGroup>
</Project>
我在 Azure AD 应用程序中授予了 openid、profile 和 offline_access 的权限,如下所示,
浏览器输出:
成功登录并点击转到安全页面后,我得到了以下页面,
我获得了访问令牌和ID令牌,如下所示。