目录:
(目录暂时不更新,跟随合集标题往下走)
源码
BlazorSSRAppOIDC
十分钟搞定单点登录
单点登录(SSO)简化了用户体验,使用户能够在访问多个应用时只需一次登录。这提高了用户满意度,减少了密码遗忘的风险,同时增强了安全性。但是,实现单点登录并不容易,需要应用程序实现和认证服务器的交互逻辑,增加了应用程序的开发工作量。例子中的安全策略中提供了 OpenID Connect (OIDC) 的能力,无需对应用做过多的修改,在十分钟内即可立刻实现单点登录。
当采用单点登录之后,用户只需要登录一次,就可以访问多个应用系统。SSO 通常由一个独立的身份管理系统来完成,该系统为每个用户分配一个全局唯一的标识,用户在登录时,只需要提供一次身份认证,就可以访问所有的应用系统。我们在使用一些网站时,经常会看到“使用微信登录”、“使用 Google 账户登录”等按钮,这些网站就是通过 SSO 来实现的。
采用单点登录有以下几个好处:
用户只需要登录一次,就可以访问多个应用系统,不需要为每个应用系统都单独登录。
应用系统不需要自己实现用户认证,只需将认证工作交给单点登录系统,可以大大减少应用系统的开发工作量。
使用 OIDC 单点登录, 可以简化客户端编写流程, 专注于功能实现而不用重复撰写登录部分功能代码, 也不用直接接触身份验证数据库, 剥离繁琐的重复劳动部分.
建立 net8 webapp ssr 工程

引用以下库
- <ItemGroup>
- <PackageReference Include="BootstrapBlazor" Version="8.*" />
- <PackageReference Include="Densen.Extensions.BootstrapBlazor" Version="8.*" />
- <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.*" />
- <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.*" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="8.*" />
- </ItemGroup>
复制代码 _Imports.razor 加入引用
- @using BootstrapBlazor.Components
- @using Microsoft.AspNetCore.Authorization
- @using Microsoft.AspNetCore.Components.Authorization
复制代码 App.razor 加入必须的UI库引用代码
完整文件- <ItemGroup>
- <PackageReference Include="BootstrapBlazor" Version="8.*" />
- <PackageReference Include="Densen.Extensions.BootstrapBlazor" Version="8.*" />
- <PackageReference Include="Microsoft.AspNetCore.Authentication.OpenIdConnect" Version="8.*" />
- <PackageReference Include="Microsoft.AspNetCore.Components.Authorization" Version="8.*" />
- <PackageReference Include="Microsoft.Extensions.Http" Version="8.*" />
- </ItemGroup>
复制代码 Routes.razor 加入授权
完整代码- <Router AppAssembly="typeof(Program).Assembly">
- <Found Context="routeData">
- <AuthorizeRouteView RouteData="routeData" DefaultLayout="typeof(Layout.MainLayout)">
- <NotAuthorized>
- <p role="alert">您无权访问该资源.</p>
- </NotAuthorized>
- <Authorizing>
- <p>正在验证您的身份...</p>
- </Authorizing>
- </AuthorizeRouteView>
- </Found>
- </Router>
复制代码 添加Oidc授权配置

新建 OidcProfile.cs 文件- using Microsoft.AspNetCore.Authentication;
- using Microsoft.AspNetCore.Authentication.OpenIdConnect;
- using Microsoft.IdentityModel.Protocols.OpenIdConnect;
- using System.Security.Claims;
- namespace OidcClientShared;
- public class OidcProfile
- {
- public static void OidcDIY(OpenIdConnectOptions options)
- {
- var authority = "https://ids2.app1.es/"; //由于时间的关系,已经部署有一个实际站点, 大家也可以参考往期文章使用本机服务器测试
- //authority = "https://localhost:5001/";
- var clientId = "Blazor5002";
- var callbackEndPoint = "http://localhost:5002";
- options.Authority = authority;
- options.ClientId = clientId;
- options.ResponseType = OpenIdConnectResponseType.Code;
- options.ResponseMode = OpenIdConnectResponseMode.Query;
- options.SignedOutRedirectUri = callbackEndPoint;
- options.CallbackPath = "/authentication/login-callback";
- options.SignedOutCallbackPath = "/authentication/logout-callback";
- options.Scope.Add("BlazorWasmIdentity.ServerAPI openid profile");
- options.GetClaimsFromUserInfoEndpoint = true;
- options.SaveTokens = true;
- options.MapInboundClaims = false;
- options.ClaimActions.MapAll();
- options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
- options.ClaimActions.MapJsonKey(ClaimValueTypes.Email, "email", ClaimValueTypes.Email);
- options.ClaimActions.MapJsonKey(ClaimTypes.Role, "role");
- options.Events = new OpenIdConnectEvents
- {
- OnAccessDenied = context =>
- {
- context.HandleResponse();
- context.Response.Redirect("/");
- return Task.CompletedTask;
- },
- OnTokenValidated = context =>
- {
- var token = context.TokenEndpointResponse?.AccessToken;
- if (!string.IsNullOrEmpty(token))
- {
- if (context.Principal?.Identity != null)
- {
- var identity = context.Principal!.Identity as ClaimsIdentity;
- identity!.AddClaim(new Claim("AccessToken", token));
- }
- }
- return Task.CompletedTask;
- }
- };
- }
- }
复制代码 Program.cs 加入授权相关
其中要加入Razor的cshtml支持, 因为登录要依靠管道跳转. 上下有两行都注释在文件内了.
完整代码- using BlazorSSRAppOIDC.Components;
- using OidcClientShared;
- var builder = WebApplication.CreateBuilder(args);
- //在具有 Blazor Web 应用程序模板的 .NET 8 中,需要将其更改为, 由于该Pages文件夹已移至该Components文件夹中,因此您需要指定新位置的根目录,或将该Pages文件夹移回项目的根级别
- builder.Services.AddRazorPages().WithRazorPagesRoot("/Components/Pages");
- // Add services to the container.
- builder.Services.AddRazorComponents()
- .AddInteractiveServerComponents();
- builder.Services.AddCascadingAuthenticationState();
- builder.Services.AddHttpClient();
- builder.Services.AddDensenExtensions();
- builder.Services.ConfigureJsonLocalizationOptions(op =>
- {
- // 忽略文化信息丢失日志
- op.IgnoreLocalizerMissing = true;
- });
- builder.Services
- .AddAuthentication(options =>
- {
- options.DefaultScheme = "Cookies";
- options.DefaultChallengeScheme = "oidc";
- })
- .AddCookie("Cookies")
- .AddOpenIdConnect("oidc", OidcProfile.OidcDIY);
- var app = builder.Build();
- // Configure the HTTP request pipeline.
- if (!app.Environment.IsDevelopment())
- {
- app.UseExceptionHandler("/Error", createScopeForErrors: true);
- // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
- app.UseHsts();
- }
- app.UseHttpsRedirection();
- app.UseStaticFiles();
- app.UseRouting();
- app.UseAuthentication();
- app.UseAuthorization();
- app.UseAntiforgery();
- //但出于某种原因,这还不够。在 Blazor Web 应用程序模板中,您明确需要调用
- app.MapRazorPages();
- app.MapRazorComponents<App>()
- .AddInteractiveServerRenderMode();
- app.Run();
复制代码 Pages 文件夹新建登录Razor页实现登录和注销跳转

展开 Login.cshtml 文件组合三角箭头, 编辑 Login.cshtml.cs

Login.cshtml.cs- using Microsoft.AspNetCore.Authentication;
- using Microsoft.AspNetCore.Mvc.RazorPages;
- namespace PersonalToolKit.Server.Components.Pages;
- public class LoginModel : PageModel
- {
- public async Task OnGet(string redirectUri)
- {
- await HttpContext.ChallengeAsync("oidc", new AuthenticationProperties { RedirectUri = redirectUri });
- }
- }
复制代码 Logout.cshtml.cs- using Microsoft.AspNetCore.Authentication;
- using Microsoft.AspNetCore.Mvc.RazorPages;
- namespace PersonalToolKit.Server.Components.Pages;
- public class LogoutModel : PageModel
- {
- public async Task OnGet(string redirectUri)
- {
- await HttpContext.SignOutAsync("Cookies");
- await HttpContext.SignOutAsync("oidc", new AuthenticationProperties { RedirectUri = redirectUri });
- }
- }
复制代码 Routes.razor 加入授权
完整代码- [/code][size=4]Home.razor[/size]
- 完整代码
- [code]@page "/"
- @using System.Security.Claims
- @inject NavigationManager Navigation
- <PageTitle>Home</PageTitle>
-
- <AuthorizeView>
- <Authorized>
- 你好, @context.User.Identity?.Name (@context.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Role)?.Value)
-
- <Button Text="注销" OnClick="BeginLogOut" />
- <br /><br /><br />
- <h5>以下是用户的声明</h5><br />
- @foreach (var claim in context.User.Claims)
- {
- <p>@claim.Type: @claim.Value</p>
- }
- </Authorized>
- <NotAuthorized>
- <Button Text="登录" OnClick="BeginLogIn" />
- <p>默认账号 test@test.com 密码 0</p>
- </NotAuthorized>
- </AuthorizeView>
- @code {
- private string LoginUrl = "login?redirectUri=";
- private void BeginLogIn()
- {
- var returnUrl = Uri.EscapeDataString(Navigation.Uri);
- Navigation.NavigateTo(LoginUrl + returnUrl, forceLoad: true);
- }
- private string LogoutUrl = "logout?redirectUri=";
- private void BeginLogOut()
- {
- var returnUrl = Uri.EscapeDataString(Navigation.Uri);
- Navigation.NavigateTo(LogoutUrl + returnUrl, forceLoad: true);
- }
- }
复制代码 运行

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |