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

jlt*_*rem 362 c# asp.net authorization asp.net-core-mvc asp.net-core

我正在尝试在ASP.NET Core中创建自定义授权属性.在以前的版本中,可以覆盖bool AuthorizeCore(HttpContextBase httpContext).但这不再存在于AuthorizeAttribute.

制作自定义AuthorizeAttribute的当前方法是什么?

我想要完成的任务:我在Header Authorization中收到一个会话ID.从该ID我将知道特定动作是否有效.

Der*_*eer 362

ASP.Net核心团队建议的方法是使用新的策略设计,此处已完整记录.新方法背后的基本思想是使用新的[Authorize]属性来指定"策略"(例如[Authorize( Policy = "YouNeedToBe18ToDoThis")],在应用程序的Startup.cs中注册策略以执行某些代码块(即确保用户具有年龄声明)年龄在18岁或以上的地方).

策略设计是框架的一个很好的补充,ASP.Net安全核心团队的引入应该受到赞扬.也就是说,它并不适合所有情况.这种方法的缺点在于它无法为最简单地断言给定控制器或动作需要给定声明类型的最常见需求提供方便的解决方案.如果应用程序可能具有数百个离散权限来管理各个REST资源上的CRUD操作("CanCreateOrder","CanReadOrder","CanUpdateOrder","CanDeleteOrder"等),则新方法要么重复一次到一次 - 策略名称和声明名称之间的一个映射(例如options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));),或者编写一些代码以在运行时执行这些注册(例如,从数据库读取所有声明类型并在循环中执行上述调用).对于大多数情况,这种方法的问题在于它是不必要的开销.

虽然ASP.Net核心安全团队建议永远不要创建自己的解决方案,但在某些情况下,这可能是最谨慎的选择.

以下是使用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();
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这应该标记为正确的答案.在这里,您可以看到Microsoft的人员如何看待开发人员的反馈.我不明白他们是如此"封闭"的原因,因为这是一个非常普遍的情况,拥有不同权限的镜像,必须为每一个编写一个策略是完全矫枉过正.我这么长时间都在寻找这个...(我差不多两年前已经问过这个问题,当时vNext仍然在这里赌注:http://stackoverflow.com/questions/32181400/pass-parameters-to- a-requirement-policy-in-asp-net-mvc-6但我们仍然坚持在那里) (64认同)
  • 所以 `new ForbidResult()` 不起作用(导致异常/500),因为它没有关联的授权方案。我会用什么来处理这种情况? (5认同)
  • 如果我执行“context.Result = new ForbiddenResult();”,这种方法将返回“InvalidOperationException”,我们应该如何在 dotnet 6 中执行此操作? (5认同)
  • @JacobPhan你是对的,使用IAuthorizationFilter接口可以更好地实现.我已更新代码以反映更改. (4认同)
  • 如果您需要异步行为,您可以实现 IAsyncAuthorizationFilter (4认同)
  • 这是件好事.我们在Web API上有身份验证中间件,但是按角色分配了授权权限的安全性; 所以不得不抛出一个属性,如:[MyAuthorize(MyClaimTypes.Permission,MyClaimValueTypes.Write,MyPermission.Employee)]看起来很好. (3认同)
  • 奇怪的是有人删除了这里所有答案中的评论。无论如何,不​​需要注册。框架通过扩展 TypeFilterAttribute 自动调用过滤器。 (3认同)
  • @Derek Greer:这是最好的答案。但是,您要实现一个ActionFilter,该动作将在Authorize Action Filter之后运行。无论如何,有没有实施和授权动作过滤器? (2认同)
  • 如何注册 ClaimRequirementFilter?是自动处理的吗? (2认同)

blo*_*art 236

我是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 dateOfBirth = Convert.ToDateTime(context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value);
            int age = DateTime.Today.Year - dateOfBirth.Year;
            if (dateOfBirth > DateTime.Today.AddYears(-age))
            {
                age--;
            }

            if (age >= 18)
            {
                context.Succeed(requirement);
            }
            else
            {
                context.Fail();
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后在你的ConfigureServices()功能中,你将它连接起来

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

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

[Authorize(Policy = "Over18")]
Run Code Online (Sandbox Code Playgroud)

  • 我想知道......如何实现细粒度的访问控制呢?让我们说一下Music Store样本中的`ManageStore`要求.正如样本中所示,只有"允许全部或全部"方式才能实现.那么我们是否必须为每种可能的排列创建一个新策略?即"用户/阅读","用户/创建","用户/分配角色","用户/删除",如果我们想要细粒度的声明?听起来像是为了让它正常工作而且只是为了管理声明的丰富策略,而不是`[ClaimsAutzorization("用户","读取","创建","删除","分配")]属性? (75认同)
  • 我必须评论说,所有这些都比实现自定义授权方法更复杂.我知道我希望如何完成授权我可以在MVC 5中编写它,在MVC 6中它们添加了许多"完成"代码,实际上比实现核心"事物"本身更复杂.让我坐在一个页面前试图找出一些东西,而不是直接编写代码,对于使用除Microsoft(或No-Sql)以外的RDBMS的人来说也是一个巨大的痛苦. (74认同)
  • 我也感到震惊的是,自称"领导ASP.NET安全人员"实际上建议使用魔术字符串(黑客的"IAuthorizeData.Policy"的意思)和自定义政策提供者来克服这种明显的疏忽,而不是在内部解决它框架.我以为我们不应该创建自己的实现?你让我们中的几个人别无选择,只能从头开始重新实现授权(再次),而这次甚至没有Web API的旧`Authorize`属性的好处.现在我们必须在动作过滤器或中间件级别上执行此操作. (53认同)
  • 我和这些评论中的许多其他人一样,非常失望的是,使用属性进行授权已经在Web API 2中被大大扼杀了.抱歉,但是你的"需求"抽象无法涵盖我们以前可以使用的任何情况属性构造函数参数,用于通知基础授权算法.它过去很简单,就像`[CustomAuthorize(Operator.And,Permission.GetUser,Permission.ModifyUser)]`这样的东西.我可以通过修改构造函数参数以无限多种方式使用单个自定义属性. (33认同)
  • 从我的角度来看,这并不能解决所有情况.在MVC 6之前,我使用了自定义授权属性来实现我自己的"权限系统".我可以将Authorize属性添加到所有操作,并传递一个特定的必需权限(作为枚举值).权限本身已映射到数据库中的组/用户.所以,我没有办法用政策处理这个问题!? (11认同)
  • “生活其实很简单,但我们坚持把它变得复杂。” - 孔子 (9认同)
  • blowdart:如果针对每个特定操作的"Over18"要求是任意年龄,您将如何编写此AuthorizationHandler?我是否必须为每个可能的年龄编写一份注册要求?@Gerwal,Tseng和我的情况就是如此.如何将参数传递给属性的具体"实例"??? (8认同)
  • 对不起,如果我们必须编写我们需要的东西,你会觉得你会做错事.我真的需要这个功能,它被不必要地带走了.你不需要为我们制作程序,我们是程序员. (8认同)
  • 现在,最后,记录.https://docs.asp.net/en/latest/security/authorization/index.html (5认同)
  • 这才是重点.您可以参数化需求,但不能参数处理程序,也不能参数...您不能这样做:[授权(MinAge = 21)]当然年龄是一个例子,我需要传递一个超过一百个枚举的枚举处理程序的值.这样我就不得不为每个可能的需求值编写一个处理程序("Over18","Over19","Over20"等). (5认同)
  • @blowdart:不是问题. (4认同)
  • 我在https://github.com/aspnet/Mvc/issues/5532上发布了一个GitHub问题,它不仅总结了这个问题,还总结了我在尝试从Web API 2迁移到MVC 6时注意到的其他几个糟糕的设计选择. (3认同)
  • @Shawn这是一个相当过度的工程......我使用一个接收参数的简单AuthorizationFilterAttribute解决了这个问题.你不需要对此进行反思,它似乎比"官方"解决方案(我觉得很差)更具有人工性. (3认同)
  • 我想听听更多有关如何打破属性方法以及使用它们会引起哪些问题的信息,这是您建议不使用该方法的原因。 (3认同)
  • 政策根本不够。如果您需要检查声明以查看用户是否是某个组或组织的成员,但组或组织可能因请求而异,那么策略将完全失败。这个新系统几乎没有什么价值。 (3认同)
  • 噢,天哪,伙计...我的截止日期非常紧迫,我没有时间或精力去学习如何设计中间件或声明,或者其中任何一个...框架的重点是抽象这些东西给我们解决了,因此我们可以仅关注核心业务逻辑(在我的情况下,使用Firebase Admin SDK执行一些代码以确保承载令牌有效)。本来应该是三行代码的东西已经变成了更多东西。 (3认同)
  • @blowdart,尽管你收到了这么多狗屎,但我喜欢现在授权的工作方式。我为什么这么说?当人们只在沮丧时而不是在快乐时提供反馈时,这很糟糕。至少我现在可以添加一个需求并在该需求的逻辑中执行基于属性的细节,这使我能够做我以前能够做的同样的事情(除了直接使用 IAsyncAuthorizationFilter 属性方式) -嗯是的。谢谢。 (3认同)
  • 是的,请不要低头行动过滤器的路线.我们希望新的基于策略的系统应该足够灵活,适用于99%的场景,并且作为奖励,也可以在第三方框架中使用.如果您发现案例不适合您,请通过MSFT给我发电子邮件. (2认同)
  • 我同意,授权属性似乎不可能,因为人们无权访问属性中的权限值。@blowdart:不要认为它会起作用,策略和处理程序无权访问属性中编码的权限值。 (2认同)
  • *我想了解更多有关如何破坏属性方法的信息*-@DerekGreer:我知道这是一个老问题,但是我相信您可以在[Passive Attributes]中找到答案(http://blog.ploeh .dk / 2014/06/13 / passive-attributes /)。使属性包含行为的方法不是一种好的设计方法,因为它们具有受约束的构造函数,这使得无法使用DI模式注入依赖项。将行为分解为可以在启动时注册(并由被动属性控制)的单个组件是一种更好的方法。 (2认同)
  • @ Dave3of5,您可以使用nameof(SomeEnum.SomeValue)。这是一个hack,但可以。 (2认同)
  • “我们做错了一些事” 在我喜欢 .Net Core 的所有事情中……授权故事肯定是你做错了一些事。真是一簇F*** (2认同)
  • @NathanAldenSr WernerCD 我必须全心全意地同意你的观点。MSFT 应该取代 Blowdart,并让其他人负责倾听社区和行业的需求。其中,Microsoft 自己的机制与 Microsoft 自己的 Active Directory 组不一致。安全、租户、用户界面、组完全崩溃,这让微软敞开了大门,有一天会反噬他们! (2认同)
  • 在这里使用依赖服务怎么样?它们如何在处理程序中使用......? (2认同)

giu*_*ius 85

看来,使用ASP.NET Core 2,你可以再次继承AuthorizeAttribute,你只需要实现IAuthorizationFilter:

[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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 还要注意,在建议的示例中,不必继承AuthorizeAttribute。您可以继承** Attribute **和** IAuthorizationFilter **。这样,如果使用某些非标准的身份验证机制,您将不会获得以下异常:** InvalidOperationException:未指定authenticationScheme,并且未找到DefaultChallengeScheme。** (16认同)
  • 请注意,如果您的OnAuthorization实现需要等待异步方法,则应该实现IAsyncAuthorizationFilter而不是IAuthorizationFilter,否则过滤器将同步执行,并且无论过滤器的结果如何,控制器动作都会执行。 (8认同)
  • 因此,您只能将其用于_deny_授权,而不是_grant_吗? (3认同)
  • AFAIK,默认情况下允许访问,因此您需要明确拒绝它(例如,通过添加AuthorizeAttribute)。检查此问题以获取更多详细信息:/sf/ask/1209069571/ (2认同)

Ale*_*lex 41

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

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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

就是这样。

无需搞乱“政策”、“索赔”、“处理程序”和其他[嘟嘟]

用法:

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

2024 年的一点(可选)更新

@Nikstr 在评论中询问了如何使用依赖注入。一探究竟:

//basically same class as above, but just a filter, not an attribute
public class MyAuthFilter : IAuthorizationFilter
{
    private SomeService _service; //let's say you need a service for your auth

    //dependency ibjection in constructor
    public MyAuthAttribute(SomeService service)
    {
        _service = service;
    }

    public void OnAuthorization(AuthorizationFilterContext context)
    {
        //check access 
        if (CheckPermissions())
        {
            //all good, add optional code if you want. Or don't
        }
        else
        {
            //DENIED!
            context.Result = new ChallengeResult(CookieAuthenticationDefaults.AuthenticationScheme);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

现在确保您在 Startup.cs 中注册此服务,例如.AddScoped(blahblah)

但用法略有不同:

// GET api/Get/5
[TypeFilter<MyAuthFilter>] // <-- note this
public ActionResult<string> Get(int id)
Run Code Online (Sandbox Code Playgroud)

TypeFilter只是告诉框架每次实例化属性并在这样做时使用 DI。只需谷歌搜索“TypeFilterAttribute”文档即可获取更多信息

聚苯乙烯

如果你不喜欢这条线的丑陋,你可以像这样[TypeFilter<MyAuthFilter>]简单地继承TypeFilter

//empty one-liner "wrapper" class that does nothing
public class MyAuthAttribute : TypeFilterAttribute<MyAuthFilter> { }
Run Code Online (Sandbox Code Playgroud)

然后像以前一样使用它

[MyAuth] // <-- looks simpler huh
public ActionResult<string> Get(int id)
Run Code Online (Sandbox Code Playgroud)

  • 谢谢,终于有一个简单的解决方案了!在所有过度设计的混乱中很难找到。 (9认同)
  • @PeterBons 用于异步 - 使用 `IAsyncAuthorizationFilter` (7认同)
  • 过滤器的主要缺点似乎是我们不能使用 DI - 意思是,通常不能以 hacky 的方式使用。 (2认同)

Sha*_*awn 26

您可以创建自己的AuthorizationHandler,它将在Controllers和Actions上找到自定义属性,并将它们传递给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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后,您可以将其用于控制​​器或操作所需的任何自定义属性.例如,添加权限要求.只需创建自定义属性即可.

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

    public PermissionAttribute(string name) : base("Permission")
    {
        Name = name;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后创建要添加到策略的要求

public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
    //Add any custom requirement properties if you have them
}
Run Code Online (Sandbox Code Playgroud)

然后为自定义属性创建AuthorizationHandler,继承我们之前创建的AttributeAuthorizationHandler.这将传递一个IEnumerable所有在HandleRequirementsAsync方法,从您的控制器和行动积累了您的自定义属性.

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
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,在Startup.cs ConfigureServices方法中,将自定义AuthorizationHandler添加到服务中,然后添加策略.

        services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();

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

现在,您可以使用自定义属性简单地修饰控制器和操作.

[Permission("AccessCustomers")]
public class CustomersController
{
    [Permission("AddCustomer")]
    IActionResult AddCustomer([FromBody] Customer customer)
    {
        //Add customer
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这是过度设计的......我使用一个接收参数的简单AuthorizationFilterAttribute解决了这个问题.你不需要对此进行反思,它似乎比"官方"解决方案(我觉得很差)更具有人工性. (4认同)
  • @ Vi100,如果有更简单的方法可以实现,我想知道。 (3认同)
  • @Vi100 我在 ASP.NET Core 中找不到关于 AuthorizationFilters 的很多信息。官方文档页面说他们目前正在研究这个主题。https://docs.microsoft.com/en-us/aspnet/core/security/authorization/authorization-filters (2认同)
  • 我实际上很喜欢这个解决方案,它利用了新的策略系统并结合了 Attributes 来提供一个非常干净的解决方案。我使用全局 Authorize 属性来确保用户已登录,然后在需要时应用权限策略。 (2认同)
  • 需要注意的一点是,上面的UnderlyingSystemType的使用无法编译,但是删除它似乎可以正常工作。 (2认同)

bru*_*ida 24

基于德里克·格里尔伟大的答案,我用枚举做到了.

这是我的代码示例:

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());
    }
}
Run Code Online (Sandbox Code Playgroud)

  • MumboJumbo函数 &lt;3 (3认同)

Kév*_*let 23

制作自定义AuthorizeAttribute的当前方法是什么

容易:不要创建自己的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() { ... }
}
Run Code Online (Sandbox Code Playgroud)

对于身份验证,最好在中间件级别处理.

你想要完成什么?

  • @jltrem,同意,你所谈论的是授权,而不是身份验证. (3认同)
  • 它不是身份验证(确定用户是谁),而是身份验证(确定用户是否应有权访问资源)。那么,您在哪里建议我寻求解决此问题? (2认同)
  • @Pinpoint我不是。我向另一个系统查询该信息。该系统进行身份验证(确定用户)并进行授权(告诉我该用户可以访问的内容)。现在,我通过在每个控制器操作中调用一个方法让其他系统验证会话来使其工作。我想通过属性自动发生这种情况。 (2认同)

PPa*_*ann 12

现代方式是 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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

IUserService 是您在拥有用户名和密码的地方创建的服务。基本上它返回一个用户类,用于映射您的声明。

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

然后你可以查询这些声明和她你映射的任何数据,有很多,看看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>();
 }
Run Code Online (Sandbox Code Playgroud)

这种新方式,我认为比这里显示的旧方式更好,两者都有效

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";
    }
}
Run Code Online (Sandbox Code Playgroud)


Pri*_*sad 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;
    }
}
Run Code Online (Sandbox Code Playgroud)


Gab*_* P. 7

如果有人只想使用当前的安全实践在授权阶段验证不记名令牌,您可以,

将此添加到您的 Startup/ConfigureServices

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

    services.AddAuthorization(options => options.AddPolicy("Bearer",
        policy => policy.AddRequirements(new BearerRequirement())
        )
    );
Run Code Online (Sandbox Code Playgroud)

这在你的代码库中,

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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

如果代码没有到达context.Succeed(...)它无论如何都会失败(401)。

然后在你的控制器中你可以使用

 [Authorize(Policy = "Bearer", AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
Run Code Online (Sandbox Code Playgroud)

  • 当 JwtBearer 中间件已经处理好这个问题时,为什么您还要选择执行自己的令牌验证呢?它还将正确的内容放入 WWW-Authenticate 响应标头中,以应对身份验证/令牌验证/过期失败。如果您想访问身份验证管道,可以在 AddJwtBearer 选项上利用特定事件(OnAuthenticationFailed、OnChallenge、OnMessageReceived 和 OnTokenValidated)。 (4认同)

No *_*rns 6

截至撰写本文时,我相信这可以通过 asp.net core 2 及更高版本中的 IClaimsTransformation 接口来完成。我刚刚实现了一个概念证明,该概念证明足以在此处发布。

\n\n
public class PrivilegesToClaimsTransformer : IClaimsTransformation\n{\n    private readonly IPrivilegeProvider privilegeProvider;\n    public const string DidItClaim = "http://foo.bar/privileges/resolved";\n\n    public PrivilegesToClaimsTransformer(IPrivilegeProvider privilegeProvider)\n    {\n        this.privilegeProvider = privilegeProvider;\n    }\n\n    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)\n    {\n        if (principal.Identity is ClaimsIdentity claimer)\n        {\n            if (claimer.HasClaim(DidItClaim, bool.TrueString))\n            {\n                return principal;\n            }\n\n            var privileges = await this.privilegeProvider.GetPrivileges( ... );\n            claimer.AddClaim(new Claim(DidItClaim, bool.TrueString));\n\n            foreach (var privilegeAsRole in privileges)\n            {\n                claimer.AddClaim(new Claim(ClaimTypes.Role /*"http://schemas.microsoft.com/ws/2008/06/identity/claims/role" */, privilegeAsRole));\n            }\n        }\n\n        return principal;\n    }\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

要在您的控制器中使用它,只需[Authorize(Roles="whatever")]在您的方法中添加适当的内容即可。

\n\n
[HttpGet]\n[Route("poc")]\n[Authorize(Roles = "plugh,blast")]\npublic JsonResult PocAuthorization()\n{\n    var result = Json(new\n    {\n        when = DateTime.UtcNow,\n    });\n\n    result.StatusCode = (int)HttpStatusCode.OK;\n\n    return result;\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

在我们的例子中,每个请求都包含一个 JWT 授权标头。这是原型,我相信下周我们将在我们的生产系统中做一些与此非常接近的事情。

\n\n

未来的选民,投票时请考虑写作日期。截至今天,这个works on my machine.\xe2\x84\xa2 您可能需要更多的错误处理和日志记录您的实现。

\n


小智 6

这是一个简单的 5 步指南,介绍如何使用所有复制和粘贴的策略来实现自定义角色授权:)。我使用了这些文档

创建需求:

public class RoleRequirement : IAuthorizationRequirement
{
    public string Role { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

创建一个处理程序:

public class RoleHandler : AuthorizationHandler<RoleRequirement>
{
    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RoleRequirement requirement)
    {
        var requiredRole = requirement.Role;

        //custom auth logic
        //  you can use context to access authenticated user,
        //  you can use dependecy injection to call custom services 

        var hasRole = true;

        if (hasRole)
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail(new AuthorizationFailureReason(this, $"Role {requirement.Role} missing"));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

在 Program.cs 中添加处理程序:

builder.Services.AddSingleton<IAuthorizationHandler, RoleHandler>();
Run Code Online (Sandbox Code Playgroud)

在program.cs中添加包含您的角色要求的策略:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Read", policy => policy.Requirements.Add(new RoleRequirement{Role = "ReadAccess_Custom_System"}));
});
Run Code Online (Sandbox Code Playgroud)

使用您的政策:

[Authorize("Read")]
public class ExampleController : ControllerBase
{
}
Run Code Online (Sandbox Code Playgroud)


Olu*_*ide 5

已接受的答案(/sf/answers/2894375361/)实际上不可维护或不合适,因为“CanReadResource”被用作声明(但实际上应该是一项政策,IMO)。答案中的方法在使用方式上并不好,因为如果一个操作方法需要许多不同的声明设置,那么对于该答案,您将不得不重复编写类似...

[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")] 
[ClaimRequirement(MyClaimTypes.AnotherPermision, "AnotherClaimVaue")]
//and etc. on a single action.
Run Code Online (Sandbox Code Playgroud)

所以,想象一下这需要多少编码。理想情况下,“CanReadResource”应该是一种使用许多声明来确定用户是否可以读取资源的策略。

我所做的是将我的策略创建为枚举,然后循环并设置像这样的要求......

services.AddAuthorization(authorizationOptions =>
        {
            foreach (var policyString in Enum.GetNames(typeof(Enumerations.Security.Policy)))
            {
                authorizationOptions.AddPolicy(
                    policyString,
                    authorizationPolicyBuilder => authorizationPolicyBuilder.Requirements.Add(new DefaultAuthorizationRequirement((Enumerations.Security.Policy)Enum.Parse(typeof(Enumerations.Security.Policy), policyWrtString), DateTime.UtcNow)));

      /* Note that thisn does not stop you from 
          configuring policies directly against a username, claims, roles, etc. You can do the usual.
     */
            }
        }); 
Run Code Online (Sandbox Code Playgroud)

DefaultAuthorizationRequirement 类看起来像...

public class DefaultAuthorizationRequirement : IAuthorizationRequirement
{
    public Enumerations.Security.Policy Policy {get; set;} //This is a mere enumeration whose code is not shown.
    public DateTime DateTimeOfSetup {get; set;} //Just in case you have to know when the app started up. And you may want to log out a user if their profile was modified after this date-time, etc.
}

public class DefaultAuthorizationHandler : AuthorizationHandler<DefaultAuthorizationRequirement>
{
    private IAServiceToUse _aServiceToUse;

    public DefaultAuthorizationHandler(
        IAServiceToUse aServiceToUse
        )
    {
        _aServiceToUse = aServiceToUse;
    }

    protected async override Task HandleRequirementAsync(AuthorizationHandlerContext context, DefaultAuthorizationRequirement requirement)
    {
        /*Here, you can quickly check a data source or Web API or etc. 
           to know the latest date-time of the user's profile modification...
        */
        if (_aServiceToUse.GetDateTimeOfLatestUserProfileModication > requirement.DateTimeOfSetup)
        {
            context.Fail(); /*Because any modifications to user information, 
            e.g. if the user used another browser or if by Admin modification, 
            the claims of the user in this session cannot be guaranteed to be reliable.
            */
            return;
        }

        bool shouldSucceed = false; //This should first be false, because context.Succeed(...) has to only be called if the requirement specifically succeeds.

        bool shouldFail = false; /*This should first be false, because context.Fail() 
        doesn't have to be called if there's no security breach.
        */

        // You can do anything.
        await doAnythingAsync();

       /*You can get the user's claims... 
          ALSO, note that if you have a way to priorly map users or users with certain claims 
          to particular policies, add those policies as claims of the user for the sake of ease. 
          BUT policies that require dynamic code (e.g. checking for age range) would have to be 
          coded in the switch-case below to determine stuff.
       */

        var claims = context.User.Claims;

        // You can, of course, get the policy that was hit...
        var policy = requirement.Policy

        //You can use a switch case to determine what policy to deal with here...
        switch (policy)
        {
            case Enumerations.Security.Policy.CanReadResource:
                 /*Do stuff with the claims and change the 
                     value of shouldSucceed and/or shouldFail.
                */
                 break;
            case Enumerations.Security.Policy.AnotherPolicy:
                 /*Do stuff with the claims and change the 
                    value of shouldSucceed and/or shouldFail.
                 */
                 break;
                // Other policies too.

            default:
                 throw new NotImplementedException();
        }

        /* Note that the following conditions are 
            so because failure and success in a requirement handler 
            are not mutually exclusive. They demand certainty.
        */

        if (shouldFail)
        {
            context.Fail(); /*Check the docs on this method to 
            see its implications.
            */
        }                

        if (shouldSucceed)
        {
            context.Succeed(requirement); 
        } 
     }
}
Run Code Online (Sandbox Code Playgroud)

请注意,上面的代码还可以启用用户到数据存储中的策略的预映射。因此,在为用户编写声明时,您基本上是检索直接或间接预先映射到用户的策略(例如,因为用户具有特定声明值并且该声明值已被识别并映射到策略,例如它也为具有该声明值的用户提供自动映射),并将策略登记为声明,这样在授权处理程序中,您可以简单地检查用户的声明是否包含 require.Policy 作为他们的声明项的值索赔。这是用于满足策略要求的静态方式,例如“名字”要求本质上是相当静态的。所以,

[Authorize(Policy = nameof(Enumerations.Security.Policy.ViewRecord))] 
Run Code Online (Sandbox Code Playgroud)

动态需求可以是关于检查年龄范围等,使用此类需求的策略不能预先映射到用户。

@blowdart ( /sf/answers/2202565921/ )已经给出了一个动态策略声明检查的例子(例如检查用户是否超过 18 岁)。

PS:我用手机打的。请原谅任何错别字和缺乏格式。


归档时间:

查看次数:

190298 次

最近记录:

5 年,11 月 前