首页  >  问答  >  正文

如何在混合 ASP.NET Core MVC(后端)和 Vuejs SPA(前端)中配置 Azure AD 身份验证?

我的应用程序是一种混合方法,使用 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粉242741921P粉242741921285 天前546

全部回复(1)我来回复

  • P粉501007768

    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。

    回复
    0
  • 取消回复