使用Duende Server保护API时鉴权重定向异常及认证识别问题求助
问题:API认证后重定向异常且无法识别合法授权
成功认证用户后调用API的/identity端点时,被重定向到不存在的localhost:6001/Account/Login,预期未认证时应重定向到localhost:5001/Account/Login,同时API无法识别用户已认证及合法授权。
相关代码片段
1. Identity控制器
[Route("identity")] [Authorize] public class IdentityController : ControllerBase { [HttpGet] public IActionResult Get() { return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); } }
2. API认证授权配置
var builder = WebApplication.CreateBuilder(args); var connectionString = builder.Configuration.GetConnectionString("database_conn"); // Identity builder.Services.AddDbContext<MyDBContext>(opt => { opt.UseNpgsql(connectionString); }); builder.Services.AddIdentity<User, IdentityRole>() .AddEntityFrameworkStores<MyDBContext>() .AddDefaultTokenProviders(); // Add services to the container. builder.Services.AddControllers(); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddAuthentication() .AddJwtBearer(options => { options.Authority = "https://localhost:5001"; options.TokenValidationParameters.ValidateAudience = false; options.IncludeErrorDetails = true; }); builder.Services.AddAuthorization(options => options.AddPolicy("ApiScope", policy => { policy.RequireAuthenticatedUser(); policy.RequireClaim("scope", "api1"); }) ); builder.Services.AddCors(options => { // this defines a CORS policy called "default" options.AddPolicy("default", policy => { policy.AllowAnyHeader() .AllowAnyMethod() .AllowAnyOrigin(); }); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseSwagger(); app.UseSwaggerUI(); } app.UseHttpsRedirection(); app.UseCors("default"); app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.MapControllers(); app.Run();
3. Duende IdentityServer客户端配置
new Client { ClientId = "js", ClientName = "JavaScript Client", AllowedGrantTypes = GrantTypes.Code, RequireClientSecret = false, RedirectUris = { "https://localhost:5003/callback.html" }, PostLogoutRedirectUris = { "https://localhost:5003/index.html" }, AllowOfflineAccess = true, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, IdentityServerConstants.StandardScopes.OfflineAccess, "api1" }, RefreshTokenUsage = TokenUsage.ReUse, RefreshTokenExpiration = TokenExpiration.Sliding }
4. 返回的JWT令牌内容
{ "alg": "RS256", "kid": "DA4EE3153F56DF0877C3DDE4766DAB7B", "typ": "at+jwt" }, { "iss": "https://localhost:5001", "nbf": 1705681216, "iat": 1705681216, "exp": 1705684816, "aud": "https://localhost:5001/resources", "scope": [ "openid", "profile", "api1" ], "amr": [ "pwd" ], "client_id": "js", "sub": "some-user-id", "auth_time": 1705681215, "idp": "local", "sid": "04830986C58DCDD600C25B1CB0A93093", "jti": "43EF5683A3ACA0041852B326F6620A9A" }
5. 前端App.js代码
/// <reference path="oidc-client.js" /> function log() { document.getElementById("results").innerText = ""; Array.prototype.forEach.call(arguments, function (msg) { if (msg instanceof Error) { msg = "Error: " + msg.message; } else if (typeof msg !== "string") { msg = JSON.stringify(msg, null, 2); } document.getElementById("results").innerText += msg + "\r\n"; }); } document.getElementById("login").addEventListener("click", login, false); document.getElementById("api").addEventListener("click", api, false); document.getElementById("logout").addEventListener("click", logout, false); var config = { authority: "https://localhost:5001", client_id: "js", redirect_uri: "https://localhost:5003/callback.html", response_type: "code", scope: "openid profile api1", post_logout_redirect_uri: "https://localhost:5003/index.html", }; var mgr = new Oidc.UserManager(config); mgr.events.addUserSignedOut(function () { log("User signed out of IdentityServer"); }); mgr.getUser().then(function (user) { if (user) { log("User logged in", user.profile); } else { log("User not logged in"); } }); function login() { mgr.signinRedirect(); } function api() { mgr.getUser().then(function (user) { console.log("user:", user) fetch('https://localhost:6001/identity', { method: 'GET', headers: { 'Authorization': "Bearer " + user.access_token, }, }) .then(response => console.log(response)) .catch(err => console.error(err)); }); } function logout() { mgr.signoutRedirect(); }
问题分析与解决方案
1. 重定向到localhost:6001/Account/Login的原因
API中添加的AddIdentity服务会默认启用Cookie认证方案,并设置默认登录路径为/Account/Login。当JWT认证失败时,ASP.NET Core会回退到Cookie认证逻辑,从而重定向到API自身地址(localhost:6001)下的该路径。
解决方法:
- 若API仅需JWT认证,明确设置默认认证方案为JWTBearer:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = "https://localhost:5001"; options.TokenValidationParameters.ValidateAudience = false; options.IncludeErrorDetails = true; });
- 或配置JWT认证失败时直接返回401,禁用重定向:
.AddJwtBearer(options => { options.Authority = "https://localhost:5001"; options.TokenValidationParameters.ValidateAudience = false; options.IncludeErrorDetails = true; options.Events = new JwtBearerEvents { OnChallenge = context => { context.HandleResponse(); context.Response.StatusCode = StatusCodes.Status401Unauthorized; return Task.CompletedTask; } }; });
2. API无法识别用户认证与授权的原因
控制器的[Authorize]特性未指定使用ApiScope策略或JWT认证方案,导致认证逻辑未匹配到正确的规则。
解决方法:
修改控制器的Authorize特性,指定对应的策略或认证方案:
[Route("identity")] [Authorize(Policy = "ApiScope")] // 或 [Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)] public class IdentityController : ControllerBase { // ... }
3. 额外检查点
- 确认前端调用API时,
Authorization请求头正确携带Bearer令牌,可通过API日志查看请求头传递情况。 - 确保API中间件顺序正确:
UseAuthentication必须在UseAuthorization之前,当前代码顺序符合要求。
内容的提问来源于stack exchange,提问作者Vil
相关产品推荐
相关产品推荐

