You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 21:06:03