目录:
BlazorOIDC/Server
新建wasm工程 BlazorOIDC
BlazorOIDC.Server项目
编辑 Models/WebAppIdentityUser.cs 文件
using Microsoft.AspNetCore.Identity;
using System.ComponentModel.DataAnnotations;
namespace BlazorOIDC.Server.Models;
public class ApplicationUser : IdentityUser
{
///
/// Full name
///
[Display(Name = "全名")]
[PersonalData]
public string? Name { get; set; }
///
/// Birth Date
///
[Display(Name = "生日")]
[PersonalData]
public DateTime? DOB { get; set; }
[Display(Name = "识别码")]
public string? UUID { get; set; }
[Display(Name = "外联")]
public string? provider { get; set; }
[Display(Name = "税号")]
[PersonalData]
public string? TaxNumber { get; set; }
[Display(Name = "街道地址")]
[PersonalData]
public string? Street { get; set; }
[Display(Name = "邮编")]
[PersonalData]
public string? Zip { get; set; }
[Display(Name = "县")]
[PersonalData]
public string? County { get; set; }
[Display(Name = "城市")]
[PersonalData]
public string? City { get; set; }
[Display(Name = "省份")]
[PersonalData]
public string? Province { get; set; }
[Display(Name = "国家")]
[PersonalData]
public string? Country { get; set; }
[Display(Name = "类型")]
[PersonalData]
public string? UserRole { get; set; }
}
BlazorOIDC.Server项目
新建 Data/ApplicationUserClaimsPrincipalFactory.cs 文件
using BlazorOIDC.Server.Models;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Security.Claims;
namespace Densen.Models.ids;
public class ApplicationUserClaimsPrincipalFactory : UserClaimsPrincipalFactory
{
public ApplicationUserClaimsPrincipalFactory(UserManager userManager, RoleManager role,IOptions optionsAccessor) : base(userManager, role, optionsAccessor)
{
}
protected override async Task GenerateClaimsAsync(ApplicationUser user)
{
ClaimsIdentity claims = await base.GenerateClaimsAsync(user);
var roles = await UserManager.GetRolesAsync(user);
foreach (var role in roles)
{
claims.AddClaim(new Claim("roleVIP", role));
}
return claims;
}
}
BlazorOIDC.Server项目
引用 Microsoft.EntityFrameworkCore.Sqlite
包, 示例使用sqlite数据库演示
引用第三方登录包
Microsoft.AspNetCore.Authentication.Facebook
Microsoft.AspNetCore.Authentication.Google
Microsoft.AspNetCore.Authentication.MicrosoftAccount
Microsoft.AspNetCore.Authentication.Twitter
AspNet.Security.OAuth.GitHub
编辑配置文件 appsettings.json
, 添加连接字符串和第三方登录ClientId/ClientSecret等配置
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-BlazorOIDC.Server-e292861d-0c29-45ea-84b1-b4558d5aa35d;Trusted_Connection=True;MultipleActiveResultSets=true",
"IdsSQliteConnection": "Data Source=../ids_api.db;"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"IdentityServer": {
"Clients": {
"BlazorOIDC.Client": {
"Profile": "IdentityServerSPA"
}
}
},
"AllowedHosts": "*",
"Authentication": {
"Google": {
"Instance": "https://accounts.google.com/o/oauth2/v2/auth",
"ClientId": "ClientId",
"ClientSecret": "ClientSecret",
"CallbackPath": "/signin-google"
},
"Facebook": {
"AppId": "AppId",
"AppSecret": "AppSecret"
},
"Microsoft": {
"ClientId": "ClientId",
"ClientSecret": "ClientSecret"
},
"Twitter": {
"ConsumerAPIKey": "ConsumerAPIKey",
"ConsumerSecret": "ConsumerSecret"
},
"Github": {
"ClientID": "ClientID",
"ClientSecret": "ClientSecret"
},
"WeChat": {
"AppId": "AppId",
"AppSecret": "AppSecret"
},
"QQ": {
"AppId": "AppId",
"AppKey": "AppKey"
}
}
}
BlazorOIDC.Server项目
编辑 Program.cs 文件
using BlazorOIDC.Server.Data;
using BlazorOIDC.Server.Models;
using Densen.Identity.Areas.Identity;
using Densen.Models.ids;
using Duende.IdentityServer;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Components.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
//var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
//builder.Services.AddDbContext(options =>
// options.UseSqlServer(connectionString));
//EF Sqlite 配置
builder.Services.AddDbContext(o => o.UseSqlite(builder.Configuration.GetConnectionString("IdsSQliteConnection")));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
//附加自定义用户声明到用户主体
builder.Services.AddScoped();
builder.Services.AddDefaultIdentity(o =>
{ // Password settings.
o.Password.RequireDigit = false;
o.Password.RequireLowercase = false;
o.Password.RequireNonAlphanumeric = false;
o.Password.RequireUppercase = false;
o.Password.RequiredLength = 1;
o.Password.RequiredUniqueChars = 1;
})
.AddRoles()
.AddEntityFrameworkStores()
.AddClaimsPrincipalFactory();
builder.Services.AddIdentityServer(options =>
{
options.LicenseKey = builder.Configuration["IdentityServerLicenseKey"];
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddApiAuthorization(options =>
{
options.IdentityResources["openid"].UserClaims.Add("roleVIP");
// Client localhost
var url2 = "localhost";
var spaClient2 = ClientBuilder
.SPA("BlazorWasmIdentity.Localhost")
.WithRedirectUri($"https://{url2}:5001/authentication/login-callback")
.WithLogoutRedirectUri($"https://{url2}:5001/authentication/logout-callback")
.WithScopes("openid Profile")
.Build();
spaClient2.AllowOfflineAccess = true;
spaClient2.AllowedCorsOrigins = new[]
{
$"https://{url2}:5001"
};
options.Clients.Add(spaClient2);
//2024-1-23 更新测试端点配置项
var spaClientBlazor5002 = ClientBuilder
.SPA("Blazor5002")
.WithScopes("api")
.Build();
spaClientBlazor5002.AllowedCorsOrigins = new[]
{
$"http://0.0.0.0",
$"http://0.0.0.0:5001",
$"http://0.0.0.0:5002",
$"http://localhost",
$"http://localhost:5001",
$"http://localhost:5002",
$"https://localhost",
$"https://localhost:5001",
$"https://localhost:5002"
};
foreach (var item in spaClientBlazor5002.AllowedCorsOrigins)
{
spaClientBlazor5002.RedirectUris.Add($"{item}/authentication/login-callback");
spaClientBlazor5002.PostLogoutRedirectUris.Add($"{item}/authentication/logout-callback");
}
spaClientBlazor5002.AllowOfflineAccess = true;
options.Clients.Add(spaClientBlazor5002);
});
builder.Services.AddAuthentication();
var autbuilder = new AuthenticationBuilder(builder.Services);
autbuilder.AddGoogle(o =>
{
o.ClientId = builder.Configuration["Authentication:Google:ClientId"] ?? "";
o.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"] ?? "";
o.ClaimActions.MapJsonKey("urn:google:profile", "link");
o.ClaimActions.MapJsonKey("urn:google:image", "picture");
});
//autbuilder.AddFacebook(o =>
//{
// o.AppId = builder.Configuration["Authentication:Facebook:AppId"] ?? "";
// o.AppSecret = builder.Configuration["Authentication:Facebook:AppSecret"] ?? "";
//});
//autbuilder.AddTwitter(o =>
//{
// o.ConsumerKey = builder.Configuration["Authentication:Twitter:ConsumerAPIKey"] ?? "";
// o.ConsumerSecret = builder.Configuration["Authentication:Twitter:ConsumerSecret"] ?? "";
// o.RetrieveUserDetails = true;
//});
autbuilder.AddGitHub(o =>
{
o.ClientId = builder.Configuration["Authentication:Github:ClientID"] ?? "";
o.ClientSecret = builder.Configuration["Authentication:Github:ClientSecret"] ?? "";
});
//autbuilder.AddMicrosoftAccount(o =>
//{
// o.ClientId = builder.Configuration["Authentication:Microsoft:ClientId"] ?? "";
// o.ClientSecret = builder.Configuration["Authentication:Microsoft:ClientSecret"] ?? "";
//});
//if (WeChat) autbuilder.AddWeChat(o =>
//{
// o.AppId = Configuration["Authentication:WeChat:AppId"];
// o.AppSecret = Configuration["Authentication:WeChat:AppSecret"];
// o.UseCachedStateDataFormat = true;
//})
//autbuilder.AddQQ(o =>
//{
// o.AppId = builder.Configuration["Authentication:QQ:AppId"] ?? "";
// o.AppKey = builder.Configuration["Authentication:QQ:AppKey"] ?? "";
//});
autbuilder.AddOpenIdConnect("oidc", "Demo IdentityServer", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.SignOutScheme = IdentityServerConstants.SignoutScheme;
options.SaveTokens = true;
options.Authority = "https://demo.duendesoftware.com";
options.ClientId = "interactive.confidential";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.TokenValidationParameters = new TokenValidationParameters
{
NameClaimType = "name",
RoleClaimType = "role"
};
});
builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
builder.Services.AddScoped>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// 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.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseCors(o => o.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseIdentityServer();
app.UseAuthorization();
app.MapBlazorHub();
app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");
app.Run();
因为篇幅的关系,具体数据库改为sqlite生成脚本步骤参考以前文章或者直接拉源码测试