添加sso
This commit is contained in:
@@ -13,7 +13,10 @@ using Jiaowu.Api.Infrastructure.Operations;
|
||||
using Jiaowu.Api.Infrastructure.Persistence;
|
||||
using Jiaowu.Api.Infrastructure.Scheduling;
|
||||
using Jiaowu.Api.Infrastructure.Timetables;
|
||||
using Microsoft.AspNetCore.Authentication;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.Authentication.Cookies;
|
||||
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
|
||||
using Microsoft.AspNetCore.RateLimiting;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
@@ -91,6 +94,23 @@ var performanceReportingOptions = builder.Configuration
|
||||
var rabbitMqOptions = builder.Configuration
|
||||
.GetSection(RabbitMqOptions.SectionName)
|
||||
.Get<RabbitMqOptions>() ?? new RabbitMqOptions();
|
||||
var ssoOptions = builder.Configuration
|
||||
.GetSection(SsoOptions.SectionName)
|
||||
.Get<SsoOptions>() ?? new SsoOptions();
|
||||
|
||||
if (ssoOptions.Enabled &&
|
||||
(string.IsNullOrWhiteSpace(ssoOptions.ClientId) ||
|
||||
!Uri.TryCreate(ssoOptions.Authority, UriKind.Absolute, out var ssoAuthority) ||
|
||||
ssoAuthority.Scheme is not ("http" or "https") ||
|
||||
(ssoOptions.RequireHttpsMetadata && ssoAuthority.Scheme != "https") ||
|
||||
string.IsNullOrWhiteSpace(ssoOptions.UserNameClaim) ||
|
||||
(!string.IsNullOrWhiteSpace(ssoOptions.FrontendBaseUrl) &&
|
||||
(!Uri.TryCreate(ssoOptions.FrontendBaseUrl, UriKind.Absolute, out var frontendBaseUrl) ||
|
||||
frontendBaseUrl.Scheme is not ("http" or "https")))))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"启用 Sso 时必须配置有效的 Authority、ClientId、UserNameClaim 和 FrontendBaseUrl;生产元数据地址必须使用 HTTPS。");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(officialDocumentOptions.InstitutionName) ||
|
||||
string.IsNullOrWhiteSpace(officialDocumentOptions.IssuingOffice) ||
|
||||
@@ -330,6 +350,10 @@ if (cacheOptions.Enabled && !string.IsNullOrWhiteSpace(redisConnectionString))
|
||||
builder.Services.AddStackExchangeRedisCache(options =>
|
||||
options.Configuration = redisConnectionString);
|
||||
}
|
||||
else
|
||||
{
|
||||
builder.Services.AddDistributedMemoryCache();
|
||||
}
|
||||
builder.Services.AddHybridCache(options =>
|
||||
{
|
||||
options.MaximumKeyLength = 512;
|
||||
@@ -363,6 +387,8 @@ if (Encoding.UTF8.GetByteCount(jwtOptions.Key) < 32 ||
|
||||
|
||||
builder.Services.Configure<JwtOptions>(
|
||||
builder.Configuration.GetSection(JwtOptions.SectionName));
|
||||
builder.Services.Configure<SsoOptions>(
|
||||
builder.Configuration.GetSection(SsoOptions.SectionName));
|
||||
builder.Services.AddHttpContextAccessor();
|
||||
builder.Services.AddScoped<ITokenService, TokenService>();
|
||||
builder.Services.AddScoped<ICurrentUserDataScope, CurrentUserDataScope>();
|
||||
@@ -407,8 +433,12 @@ builder.Services.AddHostedService<BackgroundJobOutboxPublisher>();
|
||||
builder.Services.AddSingleton<IOfficialDocumentPdfGenerator, OfficialDocumentPdfGenerator>();
|
||||
builder.Services.AddScoped<OfficialDocumentService>();
|
||||
|
||||
builder.Services
|
||||
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
var authentication = builder.Services
|
||||
.AddAuthentication(options =>
|
||||
{
|
||||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
|
||||
})
|
||||
.AddJwtBearer(options =>
|
||||
{
|
||||
options.TokenValidationParameters = new TokenValidationParameters
|
||||
@@ -423,7 +453,45 @@ builder.Services
|
||||
Encoding.UTF8.GetBytes(jwtOptions.Key)),
|
||||
ClockSkew = TimeSpan.FromMinutes(1)
|
||||
};
|
||||
})
|
||||
.AddCookie(SsoAuthSchemes.ExternalCookie, options =>
|
||||
{
|
||||
options.Cookie.Name = "__Host-jiaowu-sso";
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
||||
options.ExpireTimeSpan = TimeSpan.FromMinutes(5);
|
||||
});
|
||||
if (ssoOptions.Enabled)
|
||||
{
|
||||
authentication.AddOpenIdConnect(SsoAuthSchemes.Keycloak, options =>
|
||||
{
|
||||
options.Authority = ssoOptions.Authority.TrimEnd('/');
|
||||
options.ClientId = ssoOptions.ClientId;
|
||||
options.ClientSecret = ssoOptions.ClientSecret;
|
||||
options.SignInScheme = SsoAuthSchemes.ExternalCookie;
|
||||
options.ResponseType = "code";
|
||||
options.UsePkce = true;
|
||||
options.SaveTokens = false;
|
||||
options.RequireHttpsMetadata = ssoOptions.RequireHttpsMetadata;
|
||||
options.CallbackPath = "/signin-keycloak";
|
||||
options.GetClaimsFromUserInfoEndpoint = true;
|
||||
options.MapInboundClaims = false;
|
||||
options.ClaimActions.MapUniqueJsonKey(
|
||||
ssoOptions.UserNameClaim,
|
||||
ssoOptions.UserNameClaim);
|
||||
options.TokenValidationParameters.NameClaimType = ssoOptions.UserNameClaim;
|
||||
options.Events.OnRemoteFailure = context =>
|
||||
{
|
||||
context.HandleResponse();
|
||||
var loginUrl = string.IsNullOrWhiteSpace(ssoOptions.FrontendBaseUrl)
|
||||
? "/login"
|
||||
: ssoOptions.FrontendBaseUrl.TrimEnd('/') + "/login";
|
||||
context.Response.Redirect(loginUrl + "?ssoError=authentication_failed");
|
||||
return Task.CompletedTask;
|
||||
};
|
||||
});
|
||||
}
|
||||
builder.Services.AddAuthorization();
|
||||
builder.Services.AddRateLimiter(options =>
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user