在ASP.NET Core中如何创建自定义的AuthorizeAttribute?

649
我试图在ASP.NET Core中创建自定义的授权属性。 在之前的版本中,可以重写bool AuthorizeCore(HttpContextBase httpContext)。 但在AuthorizeAttribute中不再存在此功能。
当前制定自定义AuthorizeAttribute的方法是什么?
我想要实现的目标是:从Header Authorization中获取会话ID,然后根据该ID确定特定操作是否有效。

1
我不确定如何做,但MVC是开源的。你可以拉取GitHub仓库并寻找IAuthorizationFilter的实现。如果我今天有时间,我会帮你找到并发布一个真正的答案,但不能保证。GitHub仓库链接:https://github.com/aspnet/Mvc - bopapa_1979
1
好的,时间有点紧,但请在MVC Repo中寻找AuthorizationPolicy的用法,该Repo使用AuthorizeAttribute,在aspnet/Security Repo中可以找到,链接为:https://github.com/aspnet/Security。或者,在MVC Repo中查找您关心的安全内容所在的命名空间,即Microsoft.AspNet.Authorization。很抱歉我无法提供更多帮助,祝你好运! - bopapa_1979
18个回答

716

ASP.Net Core团队推荐的方法是使用全面记录在此处的新策略设计。 新方法背后的基本思想是使用新的[Authorize]属性来指定“策略”(例如,[Authorize( Policy = "YouNeedToBe18ToDoThis")]其中策略在应用程序的Startup.cs中注册),以执行某个代码块(即确保用户具有年龄声明,其中年龄为18岁或更老)。

这项政策设计是框架的重要补充,ASP.Net Security Core团队应该因其引入而受到赞扬。然而,它并不适用于所有情况。这种方法的缺点是,在最常见的情况下,即断言给定控制器或操作需要给定声明类型时,它无法提供方便的解决方案。在应用程序可能有数百个离散权限来管理对个体REST资源的CRUD操作(“CanCreateOrder”,“CanReadOrder”,“CanUpdateOrder”,“CanDeleteOrder”等)的情况下,新方法要么需要在策略名称和声明名称之间进行重复的一对一映射(例如,options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));),要么编写一些代码在运行时执行这些注册(例如,从数据库中读取所有声明类型并在循环中执行上述调用)。对于大多数情况,这种方法的问题在于它是不必要的开销。

虽然ASP.Net Core安全团队建议永远不要创建自己的解决方案,但在某些情况下,这可能是最明智的起点。

以下是一种实现方式,它使用IAuthorizationFilter提供了一种简单的方式来表达对给定控制器或操作的声明要求:

public class ClaimRequirementAttribute : TypeFilterAttribute
{
    public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
    {
        Arguments = new object[] {new Claim(claimType, claimValue) };
    }
}

public class ClaimRequirementFilter : IAuthorizationFilter
{
    readonly Claim _claim;

    public ClaimRequirementFilter(Claim claim)
    {
        _claim = claim;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
        if (!hasClaim)
        {
            context.Result = new ForbidResult();
        }
    }
}


[Route("api/resource")]
public class MyController : Controller
{
    [ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
    [HttpGet]
    public IActionResult GetResource()
    {
        return Ok();
    }
}

1
如何注册ClaimRequirementFilter?它是否会自动处理? - deathcat05
3
很奇怪,有人删掉了这里所有答案的评论。无论如何,不需要注册。由于它扩展了TypeFilterAttribute,框架会自动调用过滤器。 - Derek Greer
5
如果我执行context.Result = new ForbiddenResult();这个方法,它会返回一个"InvalidOperationException"异常。在dotnet 6中应该如何处理这个问题? - Muhammad Mamoor Khan
1
这在多个方案下如何运作? - jjxtra
4
如果您需要异步行为,您可以实现IAsyncAuthorizationFilter。 - Derek Greer
显示剩余9条评论

318

我是asp.net安全专员。首先,让我道歉,除了音乐商店示例或单元测试之外,所有这些都还没有记录下来,并且在暴露的API方面仍在完善中。详细文档可以在这里找到。

我们不希望您编写自定义授权属性。如果您需要这样做,那么我们做错了什么。相反,您应该编写授权要求

授权作用于身份。身份由身份验证创建。

您在评论中说您想检查标头中的会话ID。您的会话ID将成为身份的基础。如果您想使用Authorize属性,您可以编写一个身份验证中间件将该标头转换为经过身份验证的ClaimsPrincipal。然后,您将在授权要求内进行检查。授权要求可以像您想要的那样复杂,例如,这里有一个授权要求,在当前身份上获取出生日期声明并在用户年满18岁时授权;

public class Over18Requirement : AuthorizationHandler<Over18Requirement>, IAuthorizationRequirement
{
  public override void Handle(AuthorizationHandlerContext context, Over18Requirement requirement)
  {
    if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
    {
      context.Fail();
      return;
    }

    var dobVal = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value;
    var dateOfBirth = Convert.ToDateTime(dobVal);
    int age = DateTime.Today.Year - dateOfBirth.Year;
    if (dateOfBirth > DateTime.Today.AddYears(-age))
    {
      age--;
    }

    if (age >= 18)
    {
      context.Succeed(requirement);
    }
    else
    {
      context.Fail();
    }
  }
}

然后在您的ConfigureServices()函数中连接它

services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", 
        policy => policy.Requirements.Add(new Authorization.Over18Requirement()));
});

最后,将其应用于控制器或操作方法中:

[Authorize(Policy = "Over18")]

154
我必须评论一下,这一切比实现自定义授权方法更加复杂。我知道想要怎么做授权,我可以直接在MVC 5中编写代码,但是在MVC 6中,他们添加了很多“完成”的代码,实际上比实现核心“事物”本身更加复杂,让我坐在电脑前试图理解某些东西,而不是直接编写代码,也对那些使用除微软(或无SQL)之外的关系型数据库管理系统的人来说是一个大问题。 - Felype
91
我和许多其他评论者一样,非常失望使用属性进行授权的能力被大幅削弱,远不如在Web API 2中所能实现的。很抱歉,但你们的“要求”抽象无法覆盖任何我们先前可以使用属性构造函数参数来通知基础授权算法的情况。以前我们可以轻而易举地做到像[CustomAuthorize(Operator.And, Permission.GetUser, Permission.ModifyUser)]这样的事情。我只需修改构造函数参数就可以将单个自定义属性用于无限数量的方式。 - NathanAldenSr
117
我也感到震惊,因为这个自称“ASP.NET安全主管”的人建议使用魔术字符串(篡改IAuthorizeData.Policy的含义)和自定义策略提供程序来克服这个明显的疏忽,而不是在框架内解决它。我原以为我们不应该创建自己的实现?你让我们几个人别无选择,只能再次从头开始重新实现授权,而且这一次甚至没有Web API旧版的Authorize属性的好处了。现在我们只能在动作过滤器或中间件级别上进行实现。 - NathanAldenSr
9
生活其实很简单,但我们坚持让它变得复杂。 - 孔子 - Bloggrammer
2
这里可以使用依赖服务吗?它们如何在处理程序中使用? - nhaberl
显示剩余5条评论

175

看起来在ASP.NET Core 2中,你可以再次继承AuthorizeAttribute,只需同时实现IAuthorizationFilter(或IAsyncAuthorizationFilter):

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class CustomAuthorizeAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    private readonly string _someFilterParameter;

    public CustomAuthorizeAttribute(string someFilterParameter)
    {
        _someFilterParameter = someFilterParameter;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        var user = context.HttpContext.User;

        if (!user.Identity.IsAuthenticated)
        {
            // it isn't needed to set unauthorized result 
            // as the base class already requires the user to be authenticated
            // this also makes redirect to a login page work properly
            // context.Result = new UnauthorizedResult();
            return;
        }

        // you can also use registered services
        var someService = context.HttpContext.RequestServices.GetService<ISomeService>();

        var isAuthorized = someService.IsUserAuthorized(user.Identity.Name, _someFilterParameter);
        if (!isAuthorized)
        {
            context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
            return;
        }
    }
}

7
所以你只能使用这个来“拒绝”授权,而不能使用它来“授予”授权? - MEMark
6
据我所知,默认情况下允许访问,因此您需要明确拒绝访问(例如,通过添加AuthorizeAttribute)。有关更多详细信息,请参阅此问题:https://dev59.com/HGQm5IYBdhLWcg3w4CIN - gius
35
请注意,在建议的示例中,您不必从AuthorizeAttribute继承。您可以从AttributeIAuthorizationFilter继承。这样,如果使用某些非标准身份验证机制,则不会出现以下异常: InvalidOperationException: 未指定authenticationScheme,并且找不到DefaultChallengeScheme。 - Anatolyevich
32
请注意,如果您的OnAuthorization实现需要等待异步方法,则应实现IAsyncAuthorizationFilter而不是IAuthorizationFilter,否则您的过滤器将同步执行,您的控制器操作将无论过滤器的结果如何都会执行。 - Codemunkie
1
@Tom 那个评论是2.5年前的。我不记得了。 =) - Anatolyevich
显示剩余10条评论

79

根据 Derek Greer 的 GREAT 回答,我使用枚举实现了它。

这是我的代码示例:

public enum PermissionItem
{
    User,
    Product,
    Contact,
    Review,
    Client
}

public enum PermissionAction
{
    Read,
    Create,
}


public class AuthorizeAttribute : TypeFilterAttribute
{
    public AuthorizeAttribute(PermissionItem item, PermissionAction action)
    : base(typeof(AuthorizeActionFilter))
    {
        Arguments = new object[] { item, action };
    }
}

public class AuthorizeActionFilter : IAuthorizationFilter
{
    private readonly PermissionItem _item;
    private readonly PermissionAction _action;
    public AuthorizeActionFilter(PermissionItem item, PermissionAction action)
    {
        _item = item;
        _action = action;
    }
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        bool isAuthorized = MumboJumboFunction(context.HttpContext.User, _item, _action); // :)

        if (!isAuthorized)
        {
            context.Result = new ForbidResult();
        }
    }
}

public class UserController : BaseController
{
    private readonly DbContext _context;

    public UserController( DbContext context) :
        base()
    {
        _logger = logger;
    }

    [Authorize(PermissionItem.User, PermissionAction.Read)]
    public async Task<IActionResult> Index()
    {
        return View(await _context.User.ToListAsync());
    }
}

1
谢谢您。我用稍微不同的实现方式创建了这篇文章,并请求验证。https://dev59.com/s6rka4cB1Zd3GeqPaDMF - Anton Swanevelder
6
MumboJumboFunction <3 - Marek Urbanowicz
你的回答中没有展示如何将这个应用到用户身上,也就是说你是如何存储当前用户的权限位的。 - c-sharp-and-swiftui-devni
@rogue39nin,你可以使用Claims(context.HttpContext.User.Claims)向令牌添加一些额外的公共元数据。你也可以使用数据库、调用外部服务或任何其他允许你获取该信息的方法。 - bruno.almeida
1
@bruno.almeida,这很好用。但我该如何在 Razor 视图中使用它呢? - Mike
显示剩余3条评论

57

您可以创建自己的AuthorizationHandler,该处理程序将查找控制器和操作上的自定义属性,并将它们传递给HandleRequirementAsync方法。

public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement> where TRequirement : IAuthorizationRequirement where TAttribute : Attribute
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
    {
        var attributes = new List<TAttribute>();

        var action = (context.Resource as AuthorizationFilterContext)?.ActionDescriptor as ControllerActionDescriptor;
        if (action != null)
        {
            attributes.AddRange(GetAttributes(action.ControllerTypeInfo.UnderlyingSystemType));
            attributes.AddRange(GetAttributes(action.MethodInfo));
        }

        return HandleRequirementAsync(context, requirement, attributes);
    }

    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable<TAttribute> attributes);

    private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
    {
        return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
    }
}

那么你就可以将其用于控制器或操作上需要的任何自定义属性,例如添加权限要求。只需创建您的自定义属性即可。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class PermissionAttribute : AuthorizeAttribute
{
    public string Name { get; }

    public PermissionAttribute(string name) : base("Permission")
    {
        Name = name;
    }
}

然后创建一个需求,添加到您的策略中。

public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
    //Add any custom requirement properties if you have them
}

然后创建您的自定义属性的 AuthorizationHandler,继承我们之前创建的 AttributeAuthorizationHandler。在 HandleRequirementsAsync 方法中,它将接收一个IEnumerable参数,其中包含从您的控制器和操作累积的所有自定义属性。

public class PermissionAuthorizationHandler : AttributeAuthorizationHandler<PermissionAuthorizationRequirement, PermissionAttribute>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, IEnumerable<PermissionAttribute> attributes)
    {
        foreach (var permissionAttribute in attributes)
        {
            if (!await AuthorizeAsync(context.User, permissionAttribute.Name))
            {
                return;
            }
        }

        context.Succeed(requirement);
    }

    private Task<bool> AuthorizeAsync(ClaimsPrincipal user, string permission)
    {
        //Implement your custom user permission logic here
    }
}

最后,在您的 Startup.cs 的 ConfigureServices 方法中,将自定义的 AuthorizationHandler 添加到服务中,并添加您的 Policy。

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

        services.AddAuthorization(options =>
        {
            options.AddPolicy("Permission", policyBuilder =>
            {
                policyBuilder.Requirements.Add(new PermissionAuthorizationRequirement());
            });
        });

现在你可以简单地使用自定义属性来装饰你的控制器和操作。

[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}

16
这个解决方案有点过于复杂了... 我使用一个简单的AuthorizationFilterAttribute解决了同样的问题,它接收一个参数。你不需要使用反射来实现这个,似乎比“官方”解决方案更加繁琐(我认为官方解决方案相当差)。 - Vi100
2
@Vi100 我在 ASP.NET Core 中找不到关于AuthorizationFilters的太多信息。 官方文档页面表示,他们目前正在探讨这个主题。https://learn.microsoft.com/zh-cn/aspnet/core/security/authorization/authorization-filters - Shawn
8
@Vi100,你能否分享你的解决方案?如果有更简单的方法实现这个目标,我很想知道。 - Shawn
2
我实际上很喜欢这个解决方案,它利用了新的策略系统并结合属性提供了一个相当干净的解决方案。我使用全局授权属性来确保用户已登录,然后在必要时应用权限策略。 - teatime
3
需要注意的一点是,在上面使用UnderlyingSystemType无法编译,但是删除它似乎可以解决问题。 - teatime
显示剩余9条评论

39

什么?!

我决定再添加一个简单的答案。因为我发现大多数答案都有点过于复杂了。而且我需要一种授权而不是拒绝它的方式。这里的大多数答案都提供了一种“加强”安全性的方法,但我想“放松”它。例如:“如果某个应用程序设置已配置,则允许匿名用户访问”。

public class MyAuthAttribute : Attribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        //check access 
        if (CheckPermissions())
        {
            //all good, add optional code if you want. Or don't
        }
        else
        {
            //DENIED!
            //return "ChallengeResult" to redirect to login page (for example)
            context.Result = new ChallengeResult(CookieAuthenticationDefaults.AuthenticationScheme);
        }
    }
}

就这样了。不需要与“政策”、“索赔”、“处理程序”和其他[嘟嘟声]混在一起。

用法:

// GET api/Get/5
[MyAuth]
public ActionResult<string> Get(int id)
{
    return "blahblah";
}

8
谢谢,终于有一个简单的解决方案了!在所有过度设计的混乱中很难找到。 - Julian Go
这个解决方案中缺少的一件事是,您不能在AuthorizationFilter内部使用基于Task的方法,因为它不提供实现基于Task的接口。这就是为什么这个非常简单的解决方案并不总是有效的原因。 - Peter Bons
7
针对异步操作,请使用IAsyncAuthorizationFilter,并在其中提及@PeterBons。 - Alex from Jitbit
似乎过滤器的主要缺点是我们不能使用 DI - 意思是,通常情况下不能使用 hacky 的方式。 - Niksr

31
以下是翻译内容:

如何创建自定义的AuthorizeAttribute

对于纯授权场景(如仅限制特定用户访问),建议使用新的授权块:https://github.com/aspnet/MusicStore/blob/1c0aeb08bb1ebd846726232226279bbe001782e1/samples/MusicStore/Startup.cs#L84-L92

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.Configure<AuthorizationOptions>(options =>
        {
            options.AddPolicy("ManageStore", policy => policy.RequireClaim("Action", "ManageStore"));
        });
    }
}

public class StoreController : Controller
{
    [Authorize(Policy = "ManageStore"), HttpGet]
    public async Task<IActionResult> Manage() { ... }
}

对于认证,最好在中间件层处理。

你到底想要实现什么?


1
我在头部授权中收到了一个会话ID。通过这个ID,我将知道某个特定操作是否有效。 - jltrem
1
那么这就不是一个授权问题。我猜你的“会话ID”实际上是一个包含调用者身份的令牌:这绝对应该在中间件层面完成。 - Kévin Chalet
5
这不是身份验证(确定用户是谁),而是授权(确定用户是否应该访问资源)。那么你建议我去哪里解决这个问题? - jltrem
4
@jltrem,同意,你所谈论的是授权而非认证。 - bopapa_1979
2
@Pinpoint 我不是。我查询另一个系统获取那些信息。该系统进行身份验证(确定用户)和授权(告诉我该用户可以访问什么)。目前,我通过在每个控制器操作中调用一个方法来使其工作,以便其他系统验证会话。我希望通过属性自动完成此操作。 - jltrem
显示剩余6条评论

20

现代的方式是使用AuthenticationHandlers进行身份验证

在startup.cs中添加

services.AddAuthentication("BasicAuthentication").AddScheme<AuthenticationSchemeOptions, BasicAuthenticationHandler>("BasicAuthentication", null);

public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
    {
        private readonly IUserService _userService;

        public BasicAuthenticationHandler(
            IOptionsMonitor<AuthenticationSchemeOptions> options,
            ILoggerFactory logger,
            UrlEncoder encoder,
            ISystemClock clock,
            IUserService userService)
            : base(options, logger, encoder, clock)
        {
            _userService = userService;
        }

        protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
        {
            if (!Request.Headers.ContainsKey("Authorization"))
                return AuthenticateResult.Fail("Missing Authorization Header");

            User user = null;
            try
            {
                var authHeader = AuthenticationHeaderValue.Parse(Request.Headers["Authorization"]);
                var credentialBytes = Convert.FromBase64String(authHeader.Parameter);
                var credentials = Encoding.UTF8.GetString(credentialBytes).Split(new[] { ':' }, 2);
                var username = credentials[0];
                var password = credentials[1];
                user = await _userService.Authenticate(username, password);
            }
            catch
            {
                return AuthenticateResult.Fail("Invalid Authorization Header");
            }

            if (user == null)
                return AuthenticateResult.Fail("Invalid User-name or Password");

            var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            };
            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
    }

IUserService是一个服务,用于存放用户名和密码。基本上它返回一个用户类,您可以使用该类来映射您的声明。

var claims = new[] {
                new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
                new Claim(ClaimTypes.Name, user.Username),
            }; 

然后您可以查询这些声明以及您映射的任何数据,有相当多的数据,请查看ClaimTypes类。

您可以在扩展方法中使用它并获取任何映射。

public int? GetUserId()
{
   if (context.User.Identity.IsAuthenticated)
    {
       var id=context.User.FindFirst(ClaimTypes.NameIdentifier);
       if (!(id is null) && int.TryParse(id.Value, out var userId))
            return userId;
     }
      return new Nullable<int>();
 }

我认为这种新方式比旧方法更好,就像这里展示的一样,两者都有效。

public class BasicAuthenticationAttribute : AuthorizationFilterAttribute
{
    public override void OnAuthorization(HttpActionContext actionContext)
    {
        if (actionContext.Request.Headers.Authorization != null)
        {
            var authToken = actionContext.Request.Headers.Authorization.Parameter;
            // decoding authToken we get decode value in 'Username:Password' format
            var decodeauthToken = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(authToken));
            // spliting decodeauthToken using ':'
            var arrUserNameandPassword = decodeauthToken.Split(':');
            // at 0th postion of array we get username and at 1st we get password
            if (IsAuthorizedUser(arrUserNameandPassword[0], arrUserNameandPassword[1]))
            {
                // setting current principle
                Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(arrUserNameandPassword[0]), null);
            }
            else
            {
                actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
            }
        }
        else
        {
            actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Unauthorized);
        }
    }

    public static bool IsAuthorizedUser(string Username, string Password)
    {
        // In this method we can handle our database logic here...
        return Username.Equals("test") && Password == "test";
    }
}

这个绝妙的答案简直就像魔法一样!非常感谢你,我希望它能得到赞同,因为在浏览博客、文档和 Stack 寻找基本身份验证加角色授权的最佳答案时,这是我找到的最好的答案,耗费了我将近六个小时。 - Piotr Śródka
1
这真的很有帮助。我需要做的另一件事是确保在 app.UseAuthorization(); 之前使用 app.UseAuthentication();。 - Robooto
这是正确的解决方案。有时人们会混淆授权和认证。这是如何处理认证的方法。 - Rosdi Kasim
如果我想调用一个外部认证,获取一个具有过期时间的令牌,那该怎么处理授权呢?在您的情况下,您需要调用数据库来检索用户,如果未检索到,则用户未经授权。我想使用令牌来实现这一点,但我不想将其保存在数据库中。 - Florent
@Florent,你喜欢执行的代码是你自己的,你对它进行比较也是你自己的,随意调用任何你喜欢的服务。 - Walter Verhoeven
显示剩余2条评论

11

如果有人只想在授权阶段使用当前的安全实践验证承载令牌,您可以将以下内容添加到您的Startup/ConfigureServices中:

    services.AddSingleton<IAuthorizationHandler, BearerAuthorizationHandler>();
    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer();

    services.AddAuthorization(options => options.AddPolicy("Bearer",
        policy => policy.AddRequirements(new BearerRequirement())
        )
    );

还需要在您的代码库中添加这个。

public class BearerRequirement : IAuthorizationRequirement
{
    public async Task<bool> IsTokenValid(SomeValidationContext context, string token)
    {
        // here you can check if the token received is valid 
        return true;
    }
}

public class BearerAuthorizationHandler : AuthorizationHandler<BearerRequirement> 
{

    public BearerAuthorizationHandler(SomeValidationContext thatYouCanInject)
    {
       ...
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, BearerRequirement requirement)
    {
        var authFilterCtx = (Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext)context.Resource;
        string authHeader = authFilterCtx.HttpContext.Request.Headers["Authorization"];
        if (authHeader != null && authHeader.Contains("Bearer"))
        {
            var token = authHeader.Replace("Bearer ", string.Empty);
            if (await requirement.IsTokenValid(thatYouCanInject, token))
            {
                context.Succeed(requirement);
            }
        }
    }
}
如果代码未到达context.Succeed(...),它仍将失败(401)。
然后在您的控制器中可以使用。
 [Authorize(Policy = "Bearer", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

4
JwtBearer中间件已经处理了令牌的验证,为何还要自行验证呢?此外,JwtBearer中间件会在身份验证或令牌验证/过期失败时,在WWW-Authenticate响应标头中放置正确的内容。如果您想访问身份验证管道,则可以在AddJwtBearer选项上调用特定事件(OnAuthenticationFailed、OnChallenge、OnMessageReceived和OnTokenValidated)。 - Darren Lewis
这比我见过的任何其他解决方案都要简单得多。特别是对于简单的 API 密钥用例。一个更新:对于 3.1 版本,由于端点路由的原因,将 AuthorizationFilterContext 强制转换为无效。您需要通过 HttpContextAccessor 获取上下文。 - JasonCoder

9
以下代码适用于我在 .Net Core 5 中使用:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AccessAuthorizationAttribute : AuthorizeAttribute, IAuthorizationFilter
{
    public string Module { get; set; } //Permission string to get from controller

    public AccessAuthorizationAttribute(string module)
    {
        Module = module;
    }
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        //Validate if any permissions are passed when using attribute at controller or action level

        if (string.IsNullOrEmpty(Module))
        {
            //Validation cannot take place without any permissions so returning unauthorized
            context.Result = new UnauthorizedResult();
            return;
        }
       
        if (hasAccess)
        {
            return;
        }

        context.Result = new UnauthorizedResult();
        return;
    }
}

1
当授权失败时,您希望返回403而不是401。 - Vqf5mG96cSTT

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接