.NET 6.0集成Azure AD SSO:如何无需存储用户直接依赖微软认证?
问题
我正在使用.NET 6.0,尝试通过微软登录服务实现SSO,不想自行管理用户(注册与存储)。我已在Azure门户创建应用,将重定向URI设置为https://localhost:7013/signin-oidc,注销URL设置为https://localhost:7013/signout-oidc。
appsettings.json中的配置:
"AzureAd": { "Instance": "https://login.microsoftonline.com", "TenantId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "ClientId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "CallbackPath": "/signin-oidc", "SignedOutCallbackPath": "/signout-oidc" }
Program.cs中的代码:
builder.Services.AddAuthentication() .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")); builder.Services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options => options.SignInScheme = IdentityConstants.ExternalScheme); builder.Services.AddMvc(options => { var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); options.Filters.Add(new AuthorizeFilter(policy)); }).AddMicrosoftIdentityUI();
随后我添加了脚手架生成的Identity项(Login、Logout和ExternalLogin),启动应用后点击OpenIdConnect按钮能正常跳转至微软登录页,但登录后返回需邮箱注册的页面,同时在ExternalLoginSignInAsync处触发InvalidOperationException,提示Cannot create a DbSet for 'IdentityUserLogin<string>' because this type is not included in the model for the context.
请问能否移除所有需注册存储用户的部分,完全依赖微软/Azure的认证?
解决方案
完全可以实现纯依赖微软认证、不存储本地用户的SSO,问题根源在于你引入了ASP.NET Core Identity组件,它强制要求本地数据库存储用户关联信息。按以下步骤修改:
1. 移除所有Identity相关内容
- 删除脚手架生成的
Areas/Identity文件夹 - 卸载项目中所有
Microsoft.AspNetCore.Identity.*系列的NuGet包 - 移除appsettings.json中与Identity相关的配置(比如用于用户存储的ConnectionStrings)
2. 修改Program.cs配置
替换原有的认证相关代码,直接用微软身份认证结合Cookie存储身份,无需Identity:
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.Identity.Web; var builder = WebApplication.CreateBuilder(args); // 添加微软身份认证,指定Cookie为登录凭证存储方案 builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme) .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd")); // 可选:自定义Cookie过期策略 builder.Services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.ExpireTimeSpan = TimeSpan.FromHours(8); options.SlidingExpiration = true; }); // 全局授权策略:所有请求需登录后访问 builder.Services.AddControllersWithViews(options => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }); 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.Run();
3. 自定义登录/注销逻辑(替代IdentityUI)
创建AccountController处理登录和注销请求:
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; public class AccountController : Controller { // 触发微软登录流程 public IActionResult SignIn() { var redirectUrl = Url.Action("Index", "Home"); return Challenge(new AuthenticationProperties { RedirectUri = redirectUrl }, OpenIdConnectDefaults.AuthenticationScheme); } // 注销:同时清除本地Cookie和微软会话 public async Task<IActionResult> SignOut() { var redirectUrl = Url.Action("Index", "Home"); await HttpContext.SignOutAsync(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties { RedirectUri = redirectUrl }); await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return Redirect(redirectUrl); } }
在视图(比如_Layout.cshtml)中添加登录/注销按钮:
@if (User.Identity.IsAuthenticated) { <form asp-controller="Account" asp-action="SignOut" method="post" class="d-inline"> <button type="submit" class="nav-link btn btn-link">注销 (@User.Identity.Name)</button> </form> } else { <a asp-controller="Account" asp-action="SignIn" class="nav-link">登录</a> }
关键说明
- 原代码中
options.SignInScheme = IdentityConstants.ExternalScheme是错误的,这个Scheme属于ASP.NET Core Identity,会强制关联本地用户;改用默认的Cookie认证方案后,直接用微软返回的身份信息生成本地Cookie,无需存储用户数据。 - 移除
AddMicrosoftIdentityUI(),这个组件是Identity的内置UI,会引导用户注册本地账户,不符合纯SSO的需求。
内容的提问来源于stack exchange,提问作者yyy-t

