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

基于IdentityServer4的多组织方案API层权限控制问题咨询

实现方案

核心逻辑是将用户当前选中的组织ID注入到访问令牌中,在API层通过统一校验逻辑确保用户仅能操作归属当前组织的实体,具体实现步骤如下:


1. IdentityServer4 端注入组织声明

用户在门户切换组织后,需触发访问令牌刷新,在IdS4的IProfileService实现中,将当前选中的组织ID作为自定义声明写入令牌:

public class CustomProfileService : IProfileService
{
    private readonly UserManager<IdentityUser> _userManager;
    private readonly IUserSessionStore _userSessionStore;

    public CustomProfileService(UserManager<IdentityUser> userManager, IUserSessionStore userSessionStore)
    {
        _userManager = userManager;
        _userSessionStore = userSessionStore;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        var userId = context.Subject.GetSubjectId();
        // 从会话存储中取用户当前选中的组织ID(切换组织时更新该存储值)
        var currentOrgId = await _userSessionStore.GetCurrentOrganizationIdAsync(userId);
        // 将组织ID加入令牌声明
        context.IssuedClaims.Add(new Claim("organization_id", currentOrgId.ToString()));
        // 保留原有Identity声明逻辑
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        var user = await _userManager.FindByIdAsync(context.Subject.GetSubjectId());
        context.IsActive = user != null;
    }
}

记得在IdS4配置中注册该自定义ProfileService。


2. API 端解析当前用户信息

先在API项目中配置JWT认证,同时封装当前用户上下文服务方便全局调用:

配置JWT认证

// Program.cs
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.Authority = "你的IdentityServer4服务地址";
        options.Audience = "你的API资源名称";
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            NameClaimType = ClaimTypes.NameIdentifier
        };
    });
builder.Services.AddHttpContextAccessor();

封装当前用户上下文

// 定义接口
public interface ICurrentUser
{
    Guid UserId { get; }
    Guid OrganizationId { get; }
}

// 实现
public class CurrentUser : ICurrentUser
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    public CurrentUser(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public Guid UserId => Guid.Parse(_httpContextAccessor.HttpContext.User.FindFirstValue(ClaimTypes.NameIdentifier));
    public Guid OrganizationId => Guid.Parse(_httpContextAccessor.HttpContext.User.FindFirstValue("organization_id"));
}

// 注册服务
builder.Services.AddScoped<ICurrentUser, CurrentUser>();

3. 组织权限校验实现

根据业务复杂度二选一即可:

方案1:EF Core全局查询过滤器(推荐,低侵入性)

所有带组织归属的实体统一实现IHasOrganization接口,在DbContext中配置全局过滤规则,自动过滤非当前组织的数据:

// 统一接口
public interface IHasOrganization
{
    Guid OrganizationId { get; set; }
}

// DbContext配置
public class AppDbContext : DbContext
{
    private readonly ICurrentUser _currentUser;
    public AppDbContext(DbContextOptions<AppDbContext> options, ICurrentUser currentUser) : base(options)
    {
        _currentUser = currentUser;
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // 批量给所有实现IHasOrganization的实体加全局过滤器
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            if (typeof(IHasOrganization).IsAssignableFrom(entityType.ClrType))
            {
                var parameter = Expression.Parameter(entityType.ClrType, "e");
                var property = Expression.Property(parameter, nameof(IHasOrganization.OrganizationId));
                var orgId = Expression.Constant(_currentUser.OrganizationId);
                var equal = Expression.Equal(property, orgId);
                var lambda = Expression.Lambda(equal, parameter);
                modelBuilder.Entity(entityType.ClrType).HasQueryFilter(lambda);
            }
        }
    }
}

该方案无需修改业务代码,用户完全无法查询/修改其他组织的实体,漏校验风险极低。

方案2:自定义授权策略(适合细粒度控制场景)

如果存在跨组织操作的特殊权限需求,可通过自定义授权策略实现校验:

// 定义授权要求
public class OrganizationAccessRequirement : IAuthorizationRequirement { }

// 实现授权处理逻辑
public class OrganizationAccessHandler : AuthorizationHandler<OrganizationAccessRequirement>
{
    private readonly ICurrentUser _currentUser;
    private readonly AppDbContext _dbContext;

    public OrganizationAccessHandler(ICurrentUser currentUser, AppDbContext dbContext)
    {
        _currentUser = currentUser;
        _dbContext = dbContext;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OrganizationAccessRequirement requirement)
    {
        if (context.Resource is HttpContext httpContext)
        {
            // 从路由/请求体获取待操作的实体ID
            var entityId = Guid.Parse(httpContext.GetRouteValue("id").ToString());
            var entity = await _dbContext.Set<YourEntity>().FindAsync(entityId);
            // 实体不存在/属于当前组织则校验通过
            if (entity == null || (entity as IHasOrganization).OrganizationId == _currentUser.OrganizationId)
            {
                context.Succeed(requirement);
                return;
            }
        }
        context.Fail();
    }
}

注册策略并在需要校验的接口上加标记即可:

// Program.cs注册策略
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("OrgAccess", policy => policy.Requirements.Add(new OrganizationAccessRequirement()));
});
builder.Services.AddScoped<IAuthorizationHandler, OrganizationAccessHandler>();

// Controller/Action上加标记
[Authorize(Policy = "OrgAccess")]
public async Task<IActionResult> UpdateEntity(Guid id, [FromBody] EntityUpdateDto dto)
{
    // 业务逻辑
}

注意事项

  • 所有写操作的入参中禁止接收前端传入的OrganizationId,统一用ICurrentUser中的组织ID赋值,避免参数篡改
  • 超级管理员角色可在过滤器/授权逻辑中增加判断,跳过组织校验
  • 用户切换组织后必须重新拉取访问令牌,确保令牌中的组织ID为最新值

内容的提问来源于stack exchange,提问作者Branislav B.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 21:54:02