Angular 17对接Duende Identity Server遇requireHttps错误求助
Duende Identity Server + Angular 17 认证回调 requireHttps 错误排查
我负责的项目架构为:ASP.NET Core 8 Web API 服务器、Duende Identity Server 认证服务,前端使用 Angular 17。用户在 Duende Identity Server 完成认证后,本该返回用户信息,但浏览器控制台触发 requireHttps 错误。已尝试以下方案但均未解决:
- 在认证服务和 Angular 应用中切换
requireHttps的 true/false 设置 - 配置自签名 SSL 证书
- 执行
dotnet dev-certs https --trust命令 - 调整 CORS 策略
浏览器报错信息
Error during login callback processing issuer must use HTTPS (with TLS), or config value for property 'requireHttps' must be set to 'false' and allow HTTP (without TLS). auth-callback.component.ts:27:14
ngOnInit auth-callback.component.ts:27
Angular 34
4429 main.ts:5
Webpack 5
问题触发于 auth-callback.component.ts 的 this.oauthService.loadDiscoveryDocumentAndTryLogin() 方法执行阶段。
相关代码文件
Program.cs
using Duende.IdentityServer.Services; using Microsoft.EntityFrameworkCore; using System.Security.Claims; using IdentityModel; using Duende.IdentityServer.Validation; using Microsoft.AspNetCore.DataProtection; using System.IO; var builder = WebApplication.CreateBuilder(args); // Configure logging builder.Logging.ClearProviders(); builder.Logging.AddConsole(); builder.Logging.SetMinimumLevel(LogLevel.Debug); builder.Logging.AddDebug(); // Db context for data protection builder.Services.AddDbContext<KeyDbContext>(options => { options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")); }); // Configure Data Protection builder.Services.AddDataProtection() .PersistKeysToDbContext<KeyDbContext>() .SetApplicationName(builder.Configuration["DataProtection:ApplicationName"] ?? "DefaultApplicationName"); // Configure DbContext builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))); // Configure IdentityServer builder.Services.AddScoped<IUserStore, UserStore>(); builder.Services.AddScoped<IProfileService, ProfileService>(); builder.Services.AddScoped<IResourceOwnerPasswordValidator, ResourceOwnerPasswordValidator>(); builder.Services.AddIdentityServer(options => { // options.EmitStaticAudienceClaim = true; options.KeyManagement.Enabled = false; }) .AddInMemoryClients(Config.Clients) .AddInMemoryApiScopes(Config.ApiScopes) .AddInMemoryIdentityResources(Config.IdentityResources) .AddCorsPolicyService<InMemoryCorsPolicyService>() .AddDeveloperSigningCredential(persistKey: true, filename: "tempkey.rsa"); builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); builder.Services.AddCors(options => { options.AddPolicy("AllowAngularApp", policy => { policy.WithOrigins("http://localhost:4200") .AllowAnyHeader() .AllowAnyMethod(); }); }); var app = builder.Build(); if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseCors("AllowAngularApp"); app.UseIdentityServer(); app.UseAuthorization(); app.MapDefaultControllerRoute(); app.MapRazorPages(); InitializeDatabase(app); app.Run(); static void InitializeDatabase(IHost app) { using (var scope = app.Services.CreateScope()) { var context = scope.ServiceProvider.GetRequiredService<AppDbContext>(); context.Database.Migrate(); // Remove existing data context.UserClaims.RemoveRange(context.UserClaims); context.Users.RemoveRange(context.Users); context.SaveChanges(); if (!context.Users.Any()) { var users = new List<CustomUser> { new CustomUser { SubjectId = "1", Username = "nikhil", Password = "password", Claims = new List<UserClaim> { new UserClaim { Type = JwtClaimTypes.Name, Value = "Nikhil" }, new UserClaim { Type = JwtClaimTypes.Email, Value = "nikhil@example.com" } } }, new CustomUser { SubjectId = "2", Username = "bob", Password = "password", Claims = new List<UserClaim> { new UserClaim { Type = JwtClaimTypes.Name, Value = "Bob Smith" }, new UserClaim { Type = JwtClaimTypes.Email, Value = "bob@example.com" } } } }; context.Users.AddRange(users); context.SaveChanges(); } } }
Config.cs
using Duende.IdentityServer.Models; using Duende.IdentityServer; using IdentityModel; using System.Security.Claims; public static class Config { public static IEnumerable<IdentityResource> IdentityResources => new IdentityResource[] { new IdentityResources.OpenId(), new IdentityResources.Profile(), new IdentityResource("email", new[] { JwtClaimTypes.Email }) }; public static IEnumerable<ApiScope> ApiScopes => new ApiScope[] { new ApiScope("api1", "My API", new List<string> { JwtClaimTypes.Name, JwtClaimTypes.Email }) }; public static IEnumerable<Client> Clients => new Client[] { new Client { ClientId = "ro.client", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = { "openid", "profile", "api1", "email" } }, new Client { ClientId = "mvc", AllowedGrantTypes = GrantTypes.Code, RequirePkce = true, ClientSecrets = { new Secret("secret".Sha256()) }, // RedirectUris = { "http://localhost:5183/signin-oidc" }, // PostLogoutRedirectUris = { "http://localhost:5183/signout-callback-oidc" }, RedirectUris = { "https://localhost:4200", "https://localhost:4200/signin-oidc", "https://localhost:4200/silent-refresh.html", "https://localhost:4200/home", "https://localhost:4200/auth-callback", "http://localhost:4200/index.html", "http://localhost:4200/silent-refresh.html" }, //angular application addess added PostLogoutRedirectUris = { "https://localhost:4200/signout-callback-oidc" }, AllowedScopes = new List<string> { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "api1", "offline_access", "email" }, AllowOfflineAccess = true, AccessTokenLifetime = 40, AbsoluteRefreshTokenLifetime = 60, AllowedCorsOrigins = new List<string> { "https://localhost:4200" } } }; }
auth-service.ts
import { Injectable } from '@angular/core'; import { AuthConfig, OAuthService, OAuthErrorEvent } from 'angular-oauth2-oidc'; @Injectable({ providedIn: 'root' }) export class AuthService { constructor(private oauthService: OAuthService) { this.configure(); this.subscribeToOAuthEvents(); } private subscribeToOAuthEvents() { this.oauthService.events.subscribe(event => { if (event instanceof OAuthErrorEvent) { console.error('OAuthErrorEvent:', event); } else { console.log('OAuthEvent:', event); } }); } private configure() { const authConfig: AuthConfig = { issuer: 'https://localhost:7230', redirectUri: window.location.origin + '/signin-oidc', postLogoutRedirectUri: window.location.origin + '/signout-callback-oidc', clientId: 'mvc', dummyClientSecret: 'secret', // In angular-oauth2-oidc, use dummyClientSecret instead of clientSecret responseType: 'code', scope: 'openid profile email api1 offline_access', showDebugInformation: true, oidc: true, requestAccessToken: true, silentRefreshRedirectUri: window.location.origin + '/silent-refresh.html', useSilentRefresh: true, sessionChecksEnabled: true, timeoutFactor: 0.01, silentRefreshTimeout: 10, disableAtHashCheck: true, requireHttps: true, loginUrl: 'https://localhost:7230/connect/authorize', // Set the login URL logoutUrl: 'https://localhost:7230/connect/endsession', // Set the logout URL }; this.oauthService.configure(authConfig); this.oauthService.setupAutomaticSilentRefresh(); this.oauthService.showDebugInformation = true; this.oauthService.loadDiscoveryDocumentAndTryLogin().then(() => { console.log('Discovery document loaded and try login completed'); if (this.oauthService.hasValidAccessToken()) { console.log('Access token is valid'); } else { console.log('No valid access token found'); } }).catch(err => { console.error('Error loading discovery document and trying login', err); }); } login() { console.log('Initiating login flow'); this.oauthService.initCodeFlow(); } logout() { console.log('Logging out'); this.oauthService.logOut(); } get identityClaims() { return this.oauthService.getIdentityClaims(); } get accessToken() { return this.oauthService.getAccessToken(); } }
auth-callback.ts
import { Component, OnInit } from '@angular/core'; import { OAuthService } from 'angular-oauth2-oidc'; import { Router } from '@angular/router'; @Component({ selector: 'app-auth-callback', template: `<p>Loading...</p>`, }) export class AuthCallbackComponent implements OnInit { constructor(private oauthService: OAuthService, private router: Router) {} ngOnInit() { this.oauthService.loadDiscoveryDocumentAndTryLogin().then(_ => { console.log('Discovery document loaded and try login completed'); if (this.oauthService.hasValidAccessToken()) { console.log('Access token is valid'); console.log('Access Token:', this.oauthService.getAccessToken()); console.log('ID Token:', this.oauthService.getIdToken()); console.log('Identity Claims:', this.oauthService.getIdentityClaims()); this.router.navigate(['/Home']); // Redirect to Home component after successful login } else { console.error('No valid access token found'); this.router.navigate(['/']); // Redirect back to login on failure } }).catch(err => { console.error('Error during login callback processing', err); this.router.navigate(['/']); // Redirect back to login on error }); } }
内容的提问来源于stack exchange,提问作者Scrappy Coco
相关产品推荐
相关产品推荐

