我的应用程序是一种混合方法,使用 ASP.NET Core MVC 作为后端。我有各种控制器,我的前端使用它们从数据库中提取数据,并在 MS Graph 上进行 API 调用。我使用以下 program.cs 文件来在用户首次登录网站时启动身份验证:
//authentication pipline builder.Services.AddHttpContextAccessor(); var initialScopes = builder.Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' '); builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(options => { builder.Configuration.Bind("AzureAd", options); options.Events = new OpenIdConnectEvents { //Tap into this event to add a UserID Claim to a new HttpContext identity OnTokenValidated = context => { //This query returns the UserID from the DB by sending the email address in the claim from Azure AD string query = "select dbo.A2F_0013_ReturnUserIDForEmail(@Email) as UserID"; string connectionString = builder.Configuration.GetValue<string>("ConnectionStrings:DBContext"); string signInEmailAddress = context.Principal.FindFirst Value("preferred_username"); using (var connection = new SqlConnection(connectionString)) { var queryResult = connection.QueryFirst(query, new { Email = signInEmailAddress }); var claims = new List<Claim> { new Claim("UserID", queryResult.UserID.ToString()) }; var appIdentity = new ClaimsIdentity(claims); context.Principal.AddIdentity(appIdentity); } return Task.CompletedTask; }, }; }).EnableTokenAcquisitionToCallDownstreamApi(initialScopes) .AddMicrosoftGraph(builder.Configuration.GetSection("DownstreamApi")) .AddInMemoryTokenCaches(); //Add Transient Services builder.Services.AddTransient<IOneDrive, OneDrive>(); builder.Services.AddControllers(options => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }).AddMicrosoftIdentityUI(); builder.Services.AddRazorPages().AddRazorPagesOptions(options => { options.Conventions.AllowAnonymousToFolder("/Login"); options.Conventions.AuthorizeFolder("/"); options.Conventions.AuthorizeFolder("/files"); }).AddMicrosoftIdentityUI(); // Add the UI support to handle claims challenges builder.Services.AddServerSideBlazor() .AddMicrosoftIdentityConsentHandler(); builder.Services.AddRequiredScopeAuthorization();
在 Azure AD 门户中,我的应用程序注册为 Web 应用程序。因此,当用户最初访问该网站时,他们会被重定向到 https://login.microsoftonline.com/blahblah 以开始登录过程。这是由 Azure AD 身份平台自动执行的。然后,一旦登录发生,它们就会被重定向到加载 VueJS spa 的本地主机 (localhost:43862)。我的 spa 使用各种 axios 请求到控制器,它们提取数据,vue 路由器加载组件。然而,我的问题是用户需要重新登录,因为 cookie 已过期或者他们在另一个选项卡中注销。过期会话发出的下一个 axios 请求不会将用户重定向到 Azure 登录屏幕,而是会导致 CORS 错误。因此,我需要让我的 axios 请求强制页面重定向到 Azure AD 登录屏幕(这可能是最糟糕的想法,因为 CORS 策略会导致错误),或者让它返回到 localhost/login 的重定向,这是我自己的自定义登录屏幕使用 Azure AD 登录按钮,不应影响 CORS。那么如何拦截此 Azure AD 重定向到 Azure AD 登录并替换为我自己的?
我还尝试返回 401 错误代码,以便我可以在 axios 请求中检查该错误代码,但无济于事,它什么也不做。如果我在那里放置一个断点,它确实会命中此代码,但不会更改响应的状态代码,我仍然得到 302。我的代码是尝试添加到事件中:
OnRedirectToIdentityProvider = context => { context.Response.StatusCode = 401; return Task.CompletedTask; }
我的其他想法是也许我应该设置 CORS 策略以允许从 login.microsoft.com 进行重定向?或者这是不好的做法?
P粉5010077682024-01-09 09:14:02
我可以回答你的部分问题...首先,对于我们受 Azure AD 保护的 API 应用程序,API 应该做的是验证请求是否在请求标头中包含正确的访问令牌,如果是,给出响应,如果没有,则给出 401 或 403 之类的错误。普通的 API 应用程序不应该有 UI 来让用户登录。无论如何,如果你想在 MVC 项目中公开 API,这是可以的,但是对于API 本身,它不应该有 UI。
让我们看看下面的示例,我有一个 .net 6 Web api 项目,这是我的 program.cs
:
using Microsoft.Identity.Web; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddMicrosoftIdentityWebApiAuthentication(builder.Configuration); builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
并且需要在appsetting.json中进行配置。
"AzureAd": { "Instance": "https://login.microsoftonline.com/", "ClientId": "azure_ad_client_id", "ClientSecret": "client_secret", "Domain": "tenant_id", "TenantId": "tenant_id", //"Audience": "api://azure_ad_client_id_which exposed_api" // here I used the same azure ad app to expose API, so I can comment this property },
这是控制器:
[ApiController] [Route("[controller]")] [Authorize] public class WeatherForecastController : ControllerBase { [RequiredScope("Tiny.Read")] [HttpGet] public string Get() { return "world"; } }
我有一个 Azure AD 应用程序,并且公开了如下 API:
我还为同一个 Azure AD 应用添加了此 API。
那我们来做个测试吧。当我直接调用这个API时,我会得到401错误:
如果我在请求中使用了过期的令牌,我也会收到 401 错误:
但是如果我使用了正确的令牌(转到 https://jwt.io 来解码令牌,我们应该看到它包含正确的范围,对我来说它的 "scp": "Tiny.Read",
),我会得到响应:
至此,API部分已经完成。让我们看看客户端 SPA。对于 SPA,您应该集成 MSAL
,以便您可以让您的用户通过 Azure AD 登录,并生成用于调用 MS graph API 或您自己的 API 的访问令牌。生成访问令牌的代码应该相同,但您应该为不同的API设置不同的范围
。在我的场景中,我的 API 需要一个范围 Tiny.Read
,那么我应该在我的客户端应用程序中设置。
这是在react中生成访问令牌的屏幕截图。您需要在代码中设置范围。
现在您已经有了生成访问令牌的方法,您已经知道了 API url。然后你可以发送请求调用api,使用AJAX,使用fetch,或者其他什么,发送http请求就可以了。并且在调用api部分,还需要处理响应。如果响应代码是 401,那么您需要执行一些逻辑,可能会重定向到登录页面。你说你在这里遇到了麻烦,你遇到了 CORS 问题。这部分我无法回答。我认为这取决于您如何重定向到 Azure AD 登录页面。恐怕你可以看看 此示例,了解如何登录用户和调用图形 api。