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

如何将HttpContext派生身份值注入多层架构业务类

问题背景

当前系统采用多层架构设计,Web API接口/Action调用BLL业务逻辑层Provider时,需要同时叠加业务规则、安全权限两类过滤逻辑:比如超级用户可以查看全部数据条目,普通用户只能查看自己所属分组下的条目。
常规实现会把用户标识作为方法参数传入,类似public IEnumerable<Item> GetItems(int? userId, string color, string location),不传userId时返回全量数据,但这种实现很容易因为开发人员漏做权限校验出现越权漏洞,因此希望在BLL层内置一层安全逻辑,基于自定义MyIdentityProvider自动生成带权限过滤的查询,初始的业务Provider代码如下:

public class BllItemDataProvider : IBllItemDataProvider 
{
    private IMyIdentityProvider _myIdentity;
    
    // 注意贴出的原始代码重复写了两次相同构造函数,实际保留一个即可
    public BllItemDataProvider(IMyIdentityProvider myIdentity)
    {
        _myIdentity = myIdentity;
    }
}

MyIdentityProvider内部需要包含userId、isSuperUser以及其他业务需要的权限标识字段。
核心需求是通过依赖注入配置,自动从当前请求的HttpContext.User.Claims中读取声明填充MyIdentityProvider实例,最终让控制器不需要手动传递任何用户参数,直接调用Provider方法即可拿到经过权限过滤的数据,目标控制器代码如下:

public class ItemController : ControllerBase
{
    private readonly IBllItemDataProvider _prov;

    public ItemController (IBllItemDataProvider prov) 
    {
        _prov = prov;
    }

    [HttpGet("[action]/{color}/{location?}")]
    public IActionResult SomeItems (string color, string location) 
    {
        var result = _prov.GetItems(color, location);
        return Ok(result);
    }
}

之前考虑过实现一个ClaimControllerBase基类控制器,在构造函数里解析声明写入线程主体,供下游BLL组件访问,但不确定这个方案是否合理。补充说明:所有权限标识都来自JWT令牌解析出的Claims,由ASP.NET Core Identity框架自动填充到HttpContext.User中,基类写线程上下文的方案虽然能跑,但不够优雅,需要更符合框架规范的实现。

实现方案

基类控制器写入线程上下文的方案不要用:一是权限逻辑耦合在控制器层,BLL层如果被后台任务、单元测试等非Web场景调用就完全失效;二是异步调用场景下线程上下文很容易出现值丢失、串请求的问题,排查问题成本极高。

按照ASP.NET Core的DI规范按以下步骤实现即可,不需要额外做基类封装:

  • 第一步:注册IHttpContextAccessor
    ASP.NET Core默认不会把HttpContext访问器注入到容器中,首先在Program.cs的服务配置段添加注册:
    builder.Services.AddHttpContextAccessor();
    
  • 第二步:实现IMyIdentityProvider,直接依赖IHttpContextAccessor读取当前请求的Claims
    这个类不需要做任何静态/线程全局存储,注册为Scoped生命周期,每个请求会独立生成实例,完全不会出现串用户的问题:
    public interface IMyIdentityProvider
    {
        int? UserId { get; }
        bool IsSuperUser { get; }
        // 其他需要的权限字段按需补充
    }
    
    public class MyIdentityProvider : IMyIdentityProvider
    {
        private readonly IHttpContextAccessor _contextAccessor;
        private ClaimsPrincipal? CurrentUser => _contextAccessor.HttpContext?.User;
    
        public MyIdentityProvider(IHttpContextAccessor contextAccessor)
        {
            _contextAccessor = contextAccessor;
        }
    
        public int? UserId
        {
            get
            {
                var uidClaim = CurrentUser?.FindFirst(ClaimTypes.NameIdentifier)?.Value;
                return int.TryParse(uidClaim, out var uid) ? uid : null;
            }
        }
    
        public bool IsSuperUser
        {
            get
            {
                var superClaim = CurrentUser?.FindFirst("isSuperUser")?.Value;
                return bool.TryParse(superClaim, out var isSuper) && isSuper;
            }
        }
    }
    
  • 第三步:按Scoped生命周期注册所有相关服务
    服务生命周期和请求生命周期对齐即可,不要注册为单例,否则会出现捕获的HttpContext是旧请求的问题:
    builder.Services.AddScoped<IMyIdentityProvider, MyIdentityProvider>();
    builder.Services.AddScoped<IBllItemDataProvider, BllItemDataProvider>();
    
  • 第四步:在BLL层的查询方法里直接叠加权限过滤逻辑,不需要控制器传任何用户参数
    示例过滤逻辑如下,权限判断完全收敛在BLL层,控制器无感知:
    public IEnumerable<Item> GetItems(string color, string location)
    {
        var query = _dbContext.Items.AsQueryable();
        // 非超管自动追加所属分组过滤,从根源避免越权
        if (!_myIdentity.IsSuperUser)
        {
            var uid = _myIdentity.UserId ?? throw new UnauthorizedAccessException("用户身份校验失败");
            var userGroupIds = _dbContext.UserGroups
                .Where(ug => ug.UserId == uid)
                .Select(ug => ug.GroupId);
            query = query.Where(item => userGroupIds.Contains(item.GroupId));
        }
        // 叠加业务参数过滤
        if (!string.IsNullOrWhiteSpace(color))
            query = query.Where(item => item.Color == color);
        if (!string.IsNullOrWhiteSpace(location))
            query = query.Where(item => item.Location == location);
        
        return query.ToList();
    }
    

这个实现的优势很明显:

  • 权限逻辑完全下沉到BLL层,不会因为控制器漏传参数、漏做校验出现越权漏洞
  • 所有组件生命周期和请求绑定,没有异步上下文错乱、串用户的风险
  • 单元测试时可以直接MockIMyIdentityProvider模拟不同权限的用户场景,不需要构造HttpContext测试环境
  • 后续如果身份来源变更(比如从JWT换成ApiKey认证、内部服务调用传递用户标识),只需要修改MyIdentityProvider的实现,上层控制器和业务逻辑完全不需要改动

内容的提问来源于stack exchange,提问作者T.S.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 14:45:32