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

ASP.NET Core中Okta授权过滤器传参及执行顺序问题排查

问题描述

我已在ASP.NET Core应用中实现Okta认证,可满足SPA与API的认证需求,但API需要区分不同用户类型,用户类型信息通过JWT的claim传递。目前已实现无参数的通用授权过滤器且运行正常,但希望给过滤器传递参数(类似[OktaAuthorize(Type = new string[] { "Type1", "Type2" })]),只要用户满足其中一种类型即可访问。

更新代码后,过滤器会在控制器执行后运行,无法起到访问限制作用,求解决方法。

原无参数过滤器代码:

public class OktaAuthorizeFilter : IAuthorizationFilter
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly Okta.IJwtValidator _validationService;

    public OktaAuthorizeFilter(IHttpContextAccessor httpContextAccessor, IJwtValidator validationService)
    {
        _httpContextAccessor = httpContextAccessor;
        _validationService = validationService;
    }

    public async void OnAuthorization(AuthorizationFilterContext context)
    {
        var authToken = _httpContextAccessor.HttpContext!.Request.Headers["Authorization"].ToString();

        if (String.IsNullOrEmpty(authToken))
        {
            context.Result = new UnauthorizedObjectResult(string.Empty);
            return;
        }

        var validatedToken = await _validationService.ValidateToken(authToken.Split(" ")[1]);

        if (validatedToken == null)
        {
            context.Result = new UnauthorizedObjectResult(string.Empty);
            return;
        }
    }
}

public class OktaAuthorizeAttribute : TypeFilterAttribute
{
    public OktaAuthorizeAttribute() : base(typeof(OktaAuthorizeFilter))
    {
    }
}

更新后的带参数代码(存在执行时机问题):

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using ARMS_API.Okta;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.Tokens;

namespace ARMS_API.Filters
{
    public class OktaAuthorizationFilter : IAuthorizationFilter
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly string[]? _permissions;
        private readonly Okta.IJwtValidator? _validationService;
        public OktaAuthorizationFilter(IHttpContextAccessor httpContextAccessor, Okta.IJwtValidator validationService, string[] Permissions)
        {
            _httpContextAccessor = httpContextAccessor;
            _validationService = validationService;
            _permissions = Permissions;
        }
        public async void OnAuthorization(AuthorizationFilterContext context)
        {
            var authToken = context.HttpContext!.Request.Headers["Authorization"].ToString();

            //ensure that acccess token has data
            if (String.IsNullOrEmpty(authToken) || _validationService == null)
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            }
        
            var validatedToken = await _validationService.ValidateToken(authToken.Split(" ")[1]);

            if (validatedToken == null)
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            } 

            var RoleClaim = validatedToken.Claims.Where(claim => claim.Type.ToString() == "role").FirstOrDefault();
            
            if(RoleClaim == null)
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            }
            
            var roles = RoleClaim.Value.Split(',');

            if(roles.Intersect(_permissions!).ToArray().IsNullOrEmpty())
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            }
        }
    }

    public class OktaAuthorizationAttribute : TypeFilterAttribute
    {
        public OktaAuthorizationAttribute(params string[] Permissions) : base(typeof(OktaAuthorizationFilter))
        {
            Arguments = new object[] { Permissions };
        }
    }
}

当前属性使用方式:

[OktaAuthorization("usertype1", "usertype2")]
解决方案

问题根源

  1. 异步方法误用:IAuthorizationFilter的OnAuthorization是同步方法,使用async void会导致ASP.NET Core无法正确等待异步逻辑完成,控制器会提前执行,造成过滤器“后置运行”的错觉。
  2. 参数传递不全:过滤器构造函数需要三个参数,但TypeFilterAttribute的Arguments只传递了自定义权限,依赖注入的服务未被正确处理(实际DI会自动注入,无需手动传,但原代码的参数顺序易引发解析错误)。

修改后完整代码

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using ARMS_API.Okta;
using System.Security.Claims;
using System.IdentityModel.Tokens.Jwt;

namespace ARMS_API.Filters
{
    // 改用支持异步的IAsyncAuthorizationFilter接口
    public class OktaAuthorizationFilter : IAsyncAuthorizationFilter
    {
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly Okta.IJwtValidator _validationService;
        private readonly string[] _permissions;

        public OktaAuthorizationFilter(IHttpContextAccessor httpContextAccessor, Okta.IJwtValidator validationService, string[] permissions)
        {
            _httpContextAccessor = httpContextAccessor;
            _validationService = validationService;
            _permissions = permissions;
        }

        // 使用异步方法确保逻辑执行完成后再进入控制器
        public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
        {
            var authToken = context.HttpContext.Request.Headers["Authorization"].ToString();

            if (string.IsNullOrEmpty(authToken))
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            }

            // 验证Bearer令牌格式
            var tokenParts = authToken.Split(" ");
            if (tokenParts.Length != 2 || tokenParts[0] != "Bearer")
            {
                context.Result = new UnauthorizedObjectResult("无效的令牌格式");
                return;
            }

            var validatedToken = await _validationService.ValidateToken(tokenParts[1]);

            if (validatedToken == null)
            {
                context.Result = new UnauthorizedObjectResult(string.Empty);
                return;
            }

            // 获取用户角色Claim
            var roleClaim = validatedToken.Claims.FirstOrDefault(claim => claim.Type == "role");
            
            if (roleClaim == null)
            {
                context.Result = new ForbidResult("缺少角色权限");
                return;
            }
            
            var userRoles = roleClaim.Value.Split(',', StringSplitOptions.RemoveEmptyEntries);

            // 检查用户是否拥有至少一个允许的权限
            var hasPermission = userRoles.Intersect(_permissions).Any();
            if (!hasPermission)
            {
                context.Result = new ForbidResult("权限不足");
                return;
            }
        }
    }

    public class OktaAuthorizationAttribute : TypeFilterAttribute
    {
        public OktaAuthorizationAttribute(params string[] permissions) : base(typeof(OktaAuthorizationFilter))
        {
            // 仅传递自定义权限参数,DI服务会自动注入
            Arguments = new object[] { permissions };
        }
    }
}

使用方式不变

[HttpGet(Name = "GetUserAccount")]
[OktaAuthorization("usertype1", "usertype2")]
[EnableRateLimiting("api")]
public IActionResult Get()
{
    UserAccount TestAccount = new UserAccount() { InternalUserNumber = 3, EmailAddress = "test@mail.com" };
    var json = _userAccountService.Read(TestAccount);
    return Ok(json);
}

关键修改说明

  • 切换到IAsyncAuthorizationFilter:通过OnAuthorizationAsync异步方法,确保所有认证授权逻辑执行完成后,才会进入控制器处理请求。
  • 优化令牌验证逻辑:增加Bearer格式检查,避免非法令牌解析错误。
  • 语义化返回结果:权限不足时返回ForbidResult(HTTP 403),与未认证的UnauthorizedObjectResult(HTTP 401)做区分,符合HTTP规范。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 08:14:52