我的應(yīng)用程式是一種混合方法,使用 ASP.NET Core MVC 作為後端。我有各種控制器,我的前端使用它們從資料庫(kù)中提取數(shù)據(jù),並在 MS Graph 上進(jìn)行 API 呼叫。我使用以下 program.cs 檔案來(lái)在使用者首次登入網(wǎng)站時(shí)啟動(dòng)身份驗(yàn)證:
//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 入口網(wǎng)站中,我的應(yīng)用程式註冊(cè)為 Web 應(yīng)用程式。因此,當(dāng)使用者最初造訪網(wǎng)站時(shí),他們會(huì)被重新導(dǎo)向至 https://login.microsoftonline.com/blahblah 以開(kāi)始登入程序。這是由 Azure AD 身分識(shí)別平臺(tái)自動(dòng)執(zhí)行的。然後,一旦登入發(fā)生,它們就會(huì)被重定向到載入 VueJS spa 的本機(jī) (localhost:43862)。我的 spa 使用各種 axios 請(qǐng)求到控制器,它們提取數(shù)據(jù),vue 路由器加載組件。然而,我的問(wèn)題是用戶(hù)需要重新登錄,因?yàn)?cookie 已過(guò)期或他們?cè)诹硪粋€(gè)選項(xiàng)卡中登出。過(guò)期會(huì)話發(fā)出的下一個(gè) axios 請(qǐng)求不會(huì)將使用者重新導(dǎo)向到 Azure 登入畫(huà)面,而是導(dǎo)致 CORS 錯(cuò)誤。因此,我需要讓我的axios 請(qǐng)求強(qiáng)制頁(yè)面重定向到Azure AD 登入畫(huà)面(這可能是最糟糕的想法,因?yàn)镃ORS 策略會(huì)導(dǎo)致錯(cuò)誤),或者讓它返回到localhost/login 的重定向,這是我自己的自訂登入畫(huà)面使用Azure AD 登入按鈕,不應(yīng)影響CORS。那麼如何攔截此 Azure AD 重定向到 Azure AD 登入並替換為我自己的?
我還嘗試返回 401 錯(cuò)誤代碼,以便我可以在 axios 請(qǐng)求中檢查該錯(cuò)誤代碼,但無(wú)濟(jì)於事,它什麼都不做。如果我在那裡放置一個(gè)斷點(diǎn),它確實(shí)會(huì)命中此代碼,但不會(huì)更改響應(yīng)的狀態(tài)代碼,我仍然得到 302。我的程式碼是嘗試新增到事件中:
OnRedirectToIdentityProvider = context => { context.Response.StatusCode = 401; return Task.CompletedTask; }
我的其他想法是也許我應(yīng)該設(shè)定 CORS 策略以允許從 login.microsoft.com 進(jìn)行重定向?還是這是不好的做法?
我可以回答你的部分問(wèn)題...首先,對(duì)於我們受Azure AD 保護(hù)的API 應(yīng)用程序,API 應(yīng)該做的是驗(yàn)證請(qǐng)求是否在請(qǐng)求標(biāo)頭中包含正確的訪問(wèn)令牌,如果是,給出回應(yīng),如果沒(méi)有,則給出401 或403 之類(lèi)的錯(cuò)誤。普通的 API 應(yīng)用程式不應(yīng)該有 UI 來(lái)讓使用者登入。無(wú)論如何,如果你想在 MVC 專(zhuān)案中公開(kāi) API,這是可以的,但是對(duì)於API 本身,它不應(yīng)該有 UI。
讓我們看看下面的範(fàn)例,我有一個(gè) .net 6 Web api 項(xiàng)目,這是我的 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中進(jìn)行設(shè)定。
"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"; } }
我有一個(gè) Azure AD 應(yīng)用程序,並且公開(kāi)瞭如下 API:
我還為同一個(gè) Azure AD 應(yīng)用程式新增了此 API。
那我們來(lái)做個(gè)測(cè)試吧。當(dāng)我直接呼叫這個(gè)API時(shí),我會(huì)得到401錯(cuò)誤:
如果我在請(qǐng)求中使用了過(guò)期的令牌,我也會(huì)收到 401 錯(cuò)誤:
但是如果我使用了正確的令牌(轉(zhuǎn)到https://jwt.io 來(lái)解碼令牌,我們應(yīng)該看到它包含正確的範(fàn)圍,對(duì)我來(lái)說(shuō)它的"scp": "Tiny.Read",
),我會(huì)得到回應(yīng):
至此,API部分已經(jīng)完成。讓我們看看客戶(hù)端 SPA。對(duì)於 SPA,您應(yīng)該整合 MSAL
,以便您可以讓您的使用者透過(guò) Azure AD 登錄,並產(chǎn)生用於呼叫 MS graph API 或您自己的 API 的存取權(quán)杖。產(chǎn)生存取令牌的程式碼應(yīng)該相同,但您應(yīng)該為不同的API設(shè)定不同的範(fàn)圍
。在我的場(chǎng)景中,我的 API 需要一個(gè)範(fàn)圍 Tiny.Read
,那麼我應(yīng)該在我的客戶(hù)端應(yīng)用程式中設(shè)定。
這是在react中產(chǎn)生存取令牌的螢?zāi)唤貓D。您需要在程式碼中設(shè)定範(fàn)圍。
現(xiàn)在您已經(jīng)有了產(chǎn)生存取權(quán)杖的方法,您已經(jīng)知道了 API url。然後你可以發(fā)送請(qǐng)求呼叫api,使用AJAX,使用fetch,或其他什麼,發(fā)送http請(qǐng)求就可以了。並且在呼叫api部分,還需要處理回應(yīng)。如果回應(yīng)代碼是 401,那麼您需要執(zhí)行一些邏輯,可能會(huì)重新導(dǎo)向到登入頁(yè)面。你說(shuō)你在這裡遇到了麻煩,你遇到了 CORS 問(wèn)題。這部分我無(wú)法回答。我認(rèn)為這取決於您如何重定向到 Azure AD 登入頁(yè)面??峙履憧梢钥纯?此範(fàn)例,了解如何登入使用者和呼叫圖形 api。