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

ASP.NET Core中基于Redis与gRPC的自定义授权属性实现咨询

解决方案:使用授权处理程序实现自定义Token验证逻辑

选择建议:优先用授权处理程序(IAuthorizationHandler)

ASP.NET Core的授权处理程序是框架原生的授权扩展方式,能完美集成到内置的授权流程中,支持ClaimsPrincipal的构建、授权上下文的传递,后续如果需要结合角色/策略授权会更灵活。而Authorization Filter更偏向请求管道的前置拦截,处理授权逻辑时无法很好地复用框架的授权上下文,扩展性较差,因此不推荐。

具体实现步骤

1. 定义自定义授权需求与属性

先创建关联的授权需求和标记属性,用于在API上标记需要验证Token的接口:

// 自定义授权需求,仅作为标记
public class ValidateTokenRequirement : IAuthorizationRequirement
{
}

// 自定义授权属性,关联上述需求
public class ValidateTokenAttribute : AuthorizeAttribute
{
    public ValidateTokenAttribute()
    {
        Policy = nameof(ValidateTokenRequirement);
    }
}

2. 实现授权处理程序

注入gRPC客户端、Redis缓存和HttpContext访问器,完成Token验证、缓存逻辑:

public class ValidateTokenHandler : AuthorizationHandler<ValidateTokenRequirement>
{
    private readonly AuthorizationGrpcServices.AuthorizationGrpcServicesClient _grpcAuthClient;
    private readonly IDistributedCache _redisCache;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public ValidateTokenHandler(AuthorizationGrpcServices.AuthorizationGrpcServicesClient grpcClient,
                                IDistributedCache cache,
                                IHttpContextAccessor httpContextAccessor)
    {
        _grpcAuthClient = grpcClient;
        _redisCache = cache;
        _httpContextAccessor = httpContextAccessor;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, ValidateTokenRequirement requirement)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        if (httpContext == null)
        {
            context.Fail();
            return;
        }

        // 从请求头提取Token
        var authHeader = httpContext.Request.Headers.Authorization.FirstOrDefault();
        var token = authHeader?.Split(" ").Last();
        if (string.IsNullOrEmpty(token))
        {
            context.Fail();
            return;
        }

        // 先查Redis缓存,避免重复调用gRPC
        var cacheKey = $"Auth:Token:{token}";
        var cachedRoles = await _redisCache.GetStringAsync(cacheKey);
        if (!string.IsNullOrEmpty(cachedRoles))
        {
            // 构建用户身份信息,添加角色声明
            var roleClaims = cachedRoles.Split(",", StringSplitOptions.RemoveEmptyEntries)
                                        .Select(r => new Claim(ClaimTypes.Role, r.Trim()));
            var identity = new ClaimsIdentity(roleClaims, "CustomTokenAuth");
            context.User.AddIdentity(identity);
            context.Succeed(requirement);
            return;
        }

        // 调用gRPC验证Token
        var grpcResponse = await _grpcAuthClient.ValidateTokenAsync(new ValidateTokenRequest { Token = token });
        if (!grpcResponse.Access)
        {
            context.Fail();
            return;
        }

        // 将验证结果存入Redis,过期时间建议与Token有效期一致
        await _redisCache.SetStringAsync(cacheKey, grpcResponse.Roles, new DistributedCacheEntryOptions
        {
            AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1) // 按需调整
        });

        // 构建用户身份信息,完成授权
        var validRoleClaims = grpcResponse.Roles.Split(",", StringSplitOptions.RemoveEmptyEntries)
                                               .Select(r => new Claim(ClaimTypes.Role, r.Trim()));
        var validIdentity = new ClaimsIdentity(validRoleClaims, "CustomTokenAuth");
        context.User.AddIdentity(validIdentity);

        context.Succeed(requirement);
    }
}

3. 注册服务到DI容器

在API的Program.cs中注册gRPC客户端、Redis缓存和授权相关服务:

// 注册gRPC客户端
builder.Services.AddGrpcClient<AuthorizationGrpcServices.AuthorizationGrpcServicesClient>(options =>
{
    options.Address = new Uri("https://your-identity-server-grpc-url"); // 替换为你的Identity Server gRPC地址
});

// 注册Redis缓存
builder.Services.AddStackExchangeRedisCache(options =>
{
    options.Configuration = "your-redis-connection-string"; // 替换为你的Redis连接字符串
});

// 配置授权策略
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy(nameof(ValidateTokenRequirement), policy =>
        policy.Requirements.Add(new ValidateTokenRequirement()));
});

// 注册授权处理程序和HttpContext访问器
builder.Services.AddScoped<IAuthorizationHandler, ValidateTokenHandler>();
builder.Services.AddHttpContextAccessor();

4. 在API接口上使用自定义授权属性

直接在需要验证Token的控制器或Action上标记[ValidateToken]:

[ApiController]
[Route("api/[controller]")]
public class DemoController : ControllerBase
{
    [HttpGet("protected")]
    [ValidateToken]
    public IActionResult GetProtectedData()
    {
        var userRoles = User.Claims.Where(c => c.Type == ClaimTypes.Role).Select(c => c.Value);
        return Ok($"当前用户角色:{string.Join(", ", userRoles)}");
    }
}

注意事项

  • Redis缓存的过期时间建议与JWT Token的有效期保持一致,避免缓存的角色信息过期后Token仍有效
  • 需添加异常处理逻辑(如gRPC调用失败、Redis连接异常),可根据业务需求实现重试或降级策略
  • 如果Token是自包含JWT,可先本地验证签名,再调用gRPC验证Token是否被吊销,进一步减少gRPC调用次数

内容的提问来源于stack exchange,提问作者Regestea

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 09:56:19