我的應用程式是一種混合方法,使用 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 進行重定向?還是這是不好的做法?
我可以回答你的部分問題...首先,對於我們受Azure AD 保護的API 應用程序,API 應該做的是驗證請求是否在請求標頭中包含正確的訪問令牌,如果是,給出回應,如果沒有,則給出401 或403 之類的錯誤。普通的 API 應用程式不應該有 UI 來讓使用者登入。無論如何,如果你想在 MVC 專案中公開 API,這是可以的,但是對於API 本身,它不應該有 UI。
讓我們看看下面的範例,我有一個 .net 6 Web api 項目,這是我的
program.cs
:並且需要在appsetting.json中進行設定。
這是控制器:
我有一個 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。