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

基于OpenIddict的角色授权问题:资源服务器端点403排查

问题描述

我用OpenIddict搭建了独立授权服务器、资源服务器和SPA前端架构,基础授权功能正常:未登录调用端点返回401,登录后调用无角色限制的端点能正常获取数据,但添加了[Authorize(Roles = "ADMIN")]注解的资源服务器端点,在登录且持有包含ADMIN角色的access_token时仍返回403。以下是资源服务器的相关代码:

控制器代码

using Microsoft.AspNetCore.Mvc;
using OpenIddict.Validation.AspNetCore;
using System.Security.Claims;

namespace TestProtectedApi.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class WeatherForecastController : ControllerBase
    {
        private static readonly string[] Summaries = new[]
        {
            "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
        };

        private readonly ILogger<WeatherForecastController> _logger;

        public WeatherForecastController(ILogger<WeatherForecastController> logger)
        {
            _logger = logger;
        }

        [Authorize(Roles = "ADMIN")]
        [HttpGet("GetWeatherForecast")]
        public IEnumerable<WeatherForecast> Get()
        {
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }

        [Authorize]
        [HttpGet("GetWeatherForecastNoRole")]
        public IEnumerable<WeatherForecast> GetNoRole()
        {
            return Enumerable.Range(1, 5).Select(index => new WeatherForecast
            {
                Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
                TemperatureC = Random.Shared.Next(-20, 55),
                Summary = Summaries[Random.Shared.Next(Summaries.Length)]
            })
            .ToArray();
        }
    }
}

资源服务器Program.cs配置

using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using OpenIddict.Validation.AspNetCore;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddCors(options =>
{
    options.AddPolicy("AllowAllOrigins",
        builder =>
        {
            builder
                .AllowCredentials()
                .WithOrigins(
                    "http://localhost:4200", "http://localhost:4204", "https://localhost:7150")
                .SetIsOriginAllowedToAllowWildcardSubdomains()
                .AllowAnyHeader()
                .AllowAnyMethod();
        });
});

builder.Services.AddOpenIddict()
    .AddValidation(options =>
    {
        options.SetIssuer("https://localhost:7150/");

        options.UseIntrospection()
            .SetClientId("api_server")
            .SetClientSecret("test");

        options.UseSystemNetHttp();
        options.UseAspNetCore();
    });

builder.Services.AddCors();
builder.Services.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme);
builder.Services.AddAuthorization();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
}

app.UseHttpsRedirection();

app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.UseCors("AllowAllOrigins");

app.Run();
解决方案

针对403问题,按以下步骤排查和修复:

  • 修正中间件顺序
    CORS中间件必须放在认证、授权和路由之前,否则无法正确处理跨域请求头。调整Program.cs中的中间件顺序:

    app.UseHttpsRedirection();
    
    // 移到认证授权之前
    app.UseCors("AllowAllOrigins");
    
    app.UseAuthentication();
    app.UseAuthorization();
    
    app.MapControllers();
    
  • 映射角色声明类型
    ASP.NET Core默认使用http://schemas.microsoft.com/ws/2008/06/identity/claims/role作为角色声明类型,而OpenIddict通常返回role(小写)声明。在OpenIddict验证配置中添加声明映射:

    builder.Services.AddOpenIddict()
        .AddValidation(options =>
        {
            options.SetIssuer("https://localhost:7150/");
            options.AddAudiences("api_server"); // 确保匹配token的audience
    
            options.UseIntrospection()
                .SetClientId("api_server")
                .SetClientSecret("test");
    
            options.UseSystemNetHttp();
            options.UseAspNetCore();
    
            // 映射OpenIddict的role声明到ASP.NET Core的角色类型
            options.AddClaim(OpenIddictConstants.Claims.Role, System.Security.Claims.ClaimTypes.Role);
        });
    
  • 验证Introspection端点返回的角色
    检查授权服务器的Introspection端点响应,确保返回的claims字段包含role: "ADMIN"。可以用接口调试工具调用https://localhost:7150/connect/introspect,携带token、client_id和client_secret参数,查看返回结果。

  • 自定义授权策略(可选)
    如果不想修改声明映射,可以直接在授权配置中指定使用role声明:

    builder.Services.AddAuthorization(options =>
    {
        options.AddPolicy("AdminOnly", policy =>
        {
            policy.RequireAuthenticatedUser();
            policy.RequireClaim("role", "ADMIN");
        });
    });
    

    然后控制器中替换为:

    [Authorize(Policy = "AdminOnly")]
    [HttpGet("GetWeatherForecast")]
    
  • 确认token受众匹配
    确保access_token的aud字段包含api_server,否则资源服务器在验证时会过滤掉不相关的声明。

内容的提问来源于stack exchange,提问作者kevin van gils

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 20:20:00