ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

我要完成的工作:我在标头授权中收到一个会话 ID。从那个 ID 我会知道一个特定的动作是否有效。

我不知道该怎么做,但 MVC 是开源的。您可以拉取 github 存储库并查找 IAuthorizationFilter 的实现。如果我今天有时间,我会寻找您并发布实际答案,但没有承诺。 github 仓库:github.com/aspnet/Mvc
好的,没时间了,但请在此处的 aspnet/Security 存储库中查找使用 AuthorizeAttribute 的 MVC 存储库中 AuthorizationPolicy 的用法:github.com/aspnet/Security。或者,在 MVC 存储库中查找您关心的安全内容似乎所在的命名空间,即 Microsoft.AspNet.Authorization。抱歉,我不能提供更多帮助。祝你好运!

G
Grigory Zhadko

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

策略设计是对框架的一个很好的补充,应该赞扬 ASP.Net 安全核心团队的引入。也就是说,它并不适合所有情况。这种方法的缺点是它无法为简单地断言给定控制器或动作需要给定声明类型的最常见需求提供方便的解决方案。在应用程序可能拥有数百个离散权限来控制对单个 REST 资源(“CanCreateOrder”、“CanReadOrder”、“CanUpdateOrder”、“CanDeleteOrder”等)的 CRUD 操作的情况下,新方法要么需要重复的一对一策略名称和声明名称之间的一种映射(例如 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();
    }
}

如何注册 ClaimRequirementFilter?是自动处理的吗?
奇怪的是有人从这里的所有答案中删除了评论。无论如何,不需要注册。框架通过扩展 TypeFilterAttribute 自动调用过滤器。
这很棒!在控制器中工作。如何签入 Razor 页面?
如果我执行 context.Result = new ForbiddenResult();,此方法将返回 InvalidOperationException,我们应该如何在 dotnet 6 中执行此操作?
由于不可能注册所有策略,因此存在 IAuthorizationPolicyProvider。你需要实现它。请参阅文档 here
r
rianjs

我是 asp.net 安全人员。 首先让我很抱歉,除了音乐商店示例或单元测试之外,还没有任何文档记录,并且在公开的 API 方面仍在完善。 详细文档是 here

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

授权作用于身份。身份是通过身份验证创建的。

您在评论中说您想在标题中检查会话 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")]

我不得不评论说,这一切都比实现自定义授权方法更复杂。我知道我希望如何完成授权,我可以直接在 MVC 5 中编写它,在 MVC 6 中,他们添加了许多“完成”代码,这些代码实际上比实现核心“事物”本身更难理解。让我坐在一个页面前试图找出一些东西而不是直接编写代码,这对于使用 Microsoft(或 No-Sql)以外的 RDBMS 的人来说也是一个很大的痛苦。
我和这些评论中的许多其他人一样,非常失望的是,使用属性进行授权已经大大中性化了 Web API 2 中的可能性。对不起,伙计们,但您的“要求”抽象无法涵盖我们以前可以使用的任何情况属性构造函数参数通知底层授权算法。过去,执行 [CustomAuthorize(Operator.And, Permission.GetUser, Permission.ModifyUser)] 之类的操作非常简单。只需修改构造函数参数,我就可以通过无数种方式使用单个自定义属性。
我也很震惊,自称“领先的 ASP.NET 安全专家”实际上建议使用魔术字符串(破解 IAuthorizeData.Policy 的含义)和自定义策略提供程序来克服这种公然的疏忽,而不是在框架内解决它.我以为我们不应该创建自己的实现?您让我们中的一些人别无选择,只能(再次)从头重新实现授权,而这一次甚至没有 Web API 的旧 Authorize 属性的好处。现在我们必须在动作过滤器或中间件级别上执行此操作。
“生活真的很简单,但我们坚持让它变得复杂。” - 孔子
在这里使用依赖服务怎么样?它们如何在处理程序中使用......?
g
gius

似乎使用 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;
        }
    }
}

所以只能用这个拒绝授权,不能授权?
@MEMark通过授予,您的意思是覆盖另一个授权属性?
AFAIK,默认情况下允许访问,因此您需要明确拒绝它(例如,通过添加 AuthorizeAttribute)。检查此问题以获取更多详细信息:stackoverflow.com/questions/17272422/…
另请注意,在建议的示例中,不必从 AuthorizeAttribute 继承。您可以从 Attribute 和 IAuthorizationFilter 继承。这样,如果使用了一些非标准的身份验证机制,您将不会得到以下异常: InvalidOperationException: No authenticationScheme is specified, and there is no DefaultChallengeScheme found.
请注意,如果您的 OnAuthorization 实现需要等待异步方法,您应该实现 IAsyncAuthorizationFilter 而不是 IAuthorizationFilter,否则您的过滤器将同步执行,并且无论过滤器的结果如何,您的控制器操作都将执行。
b
bruno.almeida

基于 Derek Greer GREAT answer,我用枚举做到了。

这是我的代码示例:

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());
    }
}

谢谢你。我创建了这篇文章,但实现方式和验证请求略有不同stackoverflow.com/questions/49551047/…
MumboJumboFunction <3
您没有在您的答案中显示如何将其应用于用户,即您如何针对当前用户存储权限位
@rogue39nin,您可以使用 Claims (context.HttpContext.User.Claims) 将一些额外的公共元数据添加到您的令牌中。您还可以使用数据库、调用外部服务或任何其他允许您获取该信息的方法。
@bruno.almeida,这很好用。我将如何在 Razor 视图中使用它?
S
Shawn

您可以创建自己的 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,这些属性是从您的 Controller 和 Action 累积的。

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
    }
}

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

当前制作自定义 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() { ... }
}

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

你到底想达到什么目的?


我在标头授权中收到一个会话 ID。从那个 ID 我会知道一个特定的动作是否有效。
那么这不是授权问题。我猜你的“会话 ID”实际上是一个包含调用者身份的令牌:这绝对应该在中间件级别完成。
这不是身份验证(确定用户是谁),而是授权(确定用户是否应该有权访问资源)。那么你建议我在哪里解决这个问题?
@jltrem,同意,您所说的是授权,而不是身份验证。
@Pinpoint 我不是。我向另一个系统查询该信息。该系统进行身份验证(确定用户)并授权(告诉我该用户可以访问什么)。现在,我通过在每个控制器操作中调用一个方法让其他系统验证会话来让它工作。我想通过一个属性自动发生这种情况。
A
Alex from Jitbit

什么?!

我决定添加另一个简单的答案。 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);
        }
    }
}

而已。无需搞乱“政策”、“索赔”、“处理程序”等[哔]

用法:

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

谢谢,终于有一个简单的解决方案了!在所有过度设计的混乱中很难找到。
W
Walter Verhoeven

现代方式是 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";
    }
}

这个绝妙的答案就像一个魅力!谢谢你,我希望你能得到支持,因为这是我在搜索博客、文档和堆栈以进行基本身份验证和角色授权六个小时后找到的最佳答案。
@PiotrŚródka,不客气,请注意答案有点“简化”,测试文本中是否有“:”,因为恶意用户可能会尝试通过简单地在索引中没有播放好结尾来使您的服务崩溃范围异常。一如既往地测试外部资源给你的东西
这真的很有帮助。我需要做的另一件事是确保 app.UseAuthentication();在 app.UseAuthorization(); 之前
这是正确的解决方案。有时人们会混淆授权和身份验证。这是处理身份验证的方法。
如果我想调用外部身份验证并获得一个有过期时间的令牌怎么办?我将如何处理授权?在您的情况下,您调用数据库以检索用户,如果未检索到,则该用户未经授权。我想使用令牌执行此操作,但我不想将其保存在数据库中。
G
Gabriel P.

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

将此添加到您的 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)]

当 JwtBearer 中间件已经解决了这个问题时,为什么还要选择执行自己的令牌验证?它还将正确的内容放入 WWW-Authenticate 响应标头中,以应对身份验证/令牌验证/过期失败。如果您想访问身份验证管道,您可以在 AddJwtBearer 选项(OnAuthenticationFailed、OnChallenge、OnMessageReceived 和 OnTokenValidated)上利用特定事件。
这比我见过的任何其他解决方案都简单得多。特别是对于简单的 api 密钥用例。一个更新:对于 3.1,由于端点路由的东西,对 AuthorizationFilterContext 的强制转换不再有效。您需要通过 HttpContextAccessor 获取上下文。
9
9 revs

接受的答案 (https://stackoverflow.com/a/41348219/4974715) 实际上是不可维护或不适合的,因为“CanReadResource”被用作声明(但实际上应该是实际的政策,IMO)。答案的方法在使用方式上并不好,因为如果一个操作方法需要许多不同的声明设置,那么使用该答案你将不得不重复编写类似...

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

所以,想象一下这需要多少编码。理想情况下,“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.
     */
            }
        }); 

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); 
        } 
     }
}

请注意,上面的代码还可以将用户预映射到数据存储中的策略。因此,在为用户撰写声明时,您基本上会直接或间接检索已预先映射到用户的策略(例如,因为用户具有特定声明值并且该声明值已被识别并映射到策略,例如它为也具有该声明值的用户提供自动映射),并将策略作为声明登记,这样在授权处理程序中,您可以简单地检查用户的声明是否包含 require.Policy 作为其声明项的值索赔。那是为了满足策略要求的静态方式,例如“名字”要求本质上是相当静态的。因此,对于上面的示例(我在之前对此答案的更新中忘记提供有关 Authorize 属性的示例),使用具有 Authorize 属性的策略如下所示,其中 ViewRecord 是枚举成员:

[Authorize(Policy = nameof(Enumerations.Security.Policy.ViewRecord))] 

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

@blowdart (https://stackoverflow.com/a/31465227/4974715) 给出了一个动态策略声明检查的示例(例如,检查用户是否超过 18 岁)。

PS:我在手机上输入的。请原谅任何拼写错误和格式缺失。


恕我直言,该策略更像是一个具有自定义逻辑的静态验证过程,目前它无法像旧 AuthorizeAttribute 中那样简单地进行参数化。您必须在应用程序启动期间生成所有可能的 DefaultAuthorizationRequirement 实例才能在控制器中使用它们。我希望有一个可以接受一些标量参数(可能是无限组合)的策略。这样我就不会破坏开闭原则。你的例子确实如此。 (无论如何我很感激)
@neleus,您必须使用接受资源的要求。例如,在原始问题中,该资源是 SessionID。在您的评论中,资源是您正在谈论的标量属性。因此,在需求内部,将根据用户的声明对资源进行评估,然后确定授权是成功还是失败。
@neleus,已经,用户应该已经过身份验证并且还被授权调用控制器操作,但是我刚刚描述的要求将在控制器操作中使用,以确定用户是否可以根据资源中包含的信息进一步提供给它。资源可以来自请求标头、查询字符串、从数据库中获取的数据等。如果您对此感兴趣,我可以编写代码。
你的意思是具体的授权决定是控制器的工作而不是要求?
我真的不明白这能解决什么问题。我个人会避免在这里传递两件事,只使用参数传递,但是需要许多权限枚举。如果您需要传入大量权限,那么我可以看到通过这些静态枚举创建策略是可以的。这并不难,要么你需要策略,要么你不需要。没有“正确”的方式。
P
Prince Prasad

下面的代码在.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;
    }
}

当授权失败时,您希望返回 403,而不是 401。
N
No Refunds No Returns

在撰写本文时,我相信这可以通过 asp.net core 2 及更高版本中的 IClaimsTransformation 接口来完成。我刚刚实现了一个概念证明,它可以分享到这里。

public class PrivilegesToClaimsTransformer : IClaimsTransformation
{
    private readonly IPrivilegeProvider privilegeProvider;
    public const string DidItClaim = "http://foo.bar/privileges/resolved";

    public PrivilegesToClaimsTransformer(IPrivilegeProvider privilegeProvider)
    {
        this.privilegeProvider = privilegeProvider;
    }

    public async Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        if (principal.Identity is ClaimsIdentity claimer)
        {
            if (claimer.HasClaim(DidItClaim, bool.TrueString))
            {
                return principal;
            }

            var privileges = await this.privilegeProvider.GetPrivileges( ... );
            claimer.AddClaim(new Claim(DidItClaim, bool.TrueString));

            foreach (var privilegeAsRole in privileges)
            {
                claimer.AddClaim(new Claim(ClaimTypes.Role /*"http://schemas.microsoft.com/ws/2008/06/identity/claims/role" */, privilegeAsRole));
            }
        }

        return principal;
    }
}

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

[HttpGet]
[Route("poc")]
[Authorize(Roles = "plugh,blast")]
public JsonResult PocAuthorization()
{
    var result = Json(new
    {
        when = DateTime.UtcNow,
    });

    result.StatusCode = (int)HttpStatusCode.OK;

    return result;
}

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

未来的选民,请在投票时考虑写作日期。到今天为止,这个 works on my machine.™ 您可能需要更多的错误处理和登录您的实现。


配置服务呢?是否需要添加一些东西?
正如其他地方所讨论的,是的。
R
Rtype

只是添加来自@Shawn 的出色答案。如果您使用的是 dotnet 5,则需要将类更新为:

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>();
        
        if (context.Resource is HttpContext httpContext)
        {
            var endPoint = httpContext.GetEndpoint();

            var action = endPoint?.Metadata.GetMetadata<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) => memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
}

注意获取 ControllerActionDescriptor 的方式已经改变。


E
Ergin Çelik

我有不记名令牌,我可以阅读索赔。我在控制器和动作上使用该属性

public class CustomAuthorizationAttribute : ActionFilterAttribute
{
    public string[] Claims;

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        // check user 
        var contextUser = context?.HttpContext?.User;
        if (contextUser == null)
        {
            throw new BusinessException("Forbidden");
        }


        // check roles
        var roles = contextUser.FindAll("http://schemas.microsoft.com/ws/2008/06/identity/claims/role").Select(c => c.Value).ToList();
        if (!roles.Any(s => Claims.Contains(s)))
        {
            throw new BusinessException("Forbidden");
        }

        base.OnActionExecuting(context);
    }
}

例子

[CustomAuthorization(Claims = new string[]
    {
        nameof(AuthorizationRole.HR_ADMIN),
        nameof(AuthorizationRole.HR_SETTING)
    })]
[Route("api/[controller]")]
[ApiController]
public class SomeAdminController : ControllerBase
{
    private readonly IMediator _mediator;

    public SomeAdminController(IMediator mediator)
    {
        _mediator = mediator;
    }

    [HttpGet("list/SomeList")]
    public async Task<IActionResult> SomeList()
        => Ok(await _mediator.Send(new SomeListQuery()));
}

那是角色

public struct AuthorizationRole
{
    public static string HR_ADMIN;
    public static string HR_SETTING;
}

J
Joost00719

这里很多人已经告诉过这一点,但是使用策略处理程序,您可以在 .NET Framework 中使用旧方法实现的目标方面取得很大进展。

我在 SO 上的这个答案中快速写了一篇文章:https://stackoverflow.com/a/61963465/7081176 对我来说,在制作了一些课程后它可以完美地工作:

编辑用户要求:

public class EditUserRequirement : IAuthorizationRequirement
{
    public EditUserRequirement()
    {
    }
}

一个让我的生活更轻松的抽象处理程序:

public abstract class AbstractRequirementHandler<T> : IAuthorizationHandler
    where T : IAuthorizationRequirement
{
    public async Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();
        foreach (var requirement in pendingRequirements)
        {
            if (requirement is T typedRequirement)
            {
                await HandleRequirementAsync(context, typedRequirement);
            }
        }
    }

    protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, T requirement);
}

抽象处理程序的实现:

public class EditUserRequirementHandler : AbstractRequirementHandler<EditUserRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, EditUserRequirement requirement)
    {
        // If the user is owner of the resource, allow it.
        if (IsOwner(context.User, g))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }

    private static bool IsOwner(ClaimsPrincipal user, Guid userIdentifier)
    {
        return user.GetUserIdentifier() == userIdentifier;
    }
}

注册我的处理程序和要求: services.AddSingleton();

        services.AddAuthorization(options =>
        {
            options.AddPolicy(Policies.Policies.EditUser, policy =>
            {
                policy.Requirements.Add(new EditUserRequirement());
            });
        });

然后在 Blazor 中使用我的策略:

<AuthorizeView Policy="@Policies.EditUser" Resource="@id">
    <NotAuthorized>
        <Unauthorized />
    </NotAuthorized>
    <Authorized Context="Auth">
        ...
    </Authorized>
</AuthorizeView>

我希望这对任何面临这个问题的人有用。


W
WilWa

我一直在研究解决一个非常相似的问题,并决定创建一个自定义 ActionFilterAttribute(我将称之为 AuthorizationFilterAttribute)而不是 AuthorizeAttribute 来实施此处的指导:https://docs.microsoft.com/en-us/aspnet/core/security/authorization/resourcebased?view=aspnetcore-6.0#challenge-and-forbid-with-an-operational-resource-handler


A
Abdullah

在我们的应用程序中进行授权。我们必须根据授权属性中传递的参数调用服务。

例如,如果我们想检查登录的医生是否可以查看患者预约,我们会将“View_Appointment”传递给自定义授权属性,并在数据库服务中检查该权限,并根据结果进行授权。这是此场景的代码:

    public class PatientAuthorizeAttribute : TypeFilterAttribute
    {
    public PatientAuthorizeAttribute(params PatientAccessRights[] right) : base(typeof(AuthFilter)) //PatientAccessRights is an enum
    {
        Arguments = new object[] { right };
    }

    private class AuthFilter : IActionFilter
    {
        PatientAccessRights[] right;

        IAuthService authService;

        public AuthFilter(IAuthService authService, PatientAccessRights[] right)
        {
            this.right = right;
            this.authService = authService;
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
        }

        public void OnActionExecuting(ActionExecutingContext context)
        {
            var allparameters = context.ActionArguments.Values;
            if (allparameters.Count() == 1)
            {
                var param = allparameters.First();
                if (typeof(IPatientRequest).IsAssignableFrom(param.GetType()))
                {
                    IPatientRequest patientRequestInfo = (IPatientRequest)param;
                    PatientAccessRequest userAccessRequest = new PatientAccessRequest();
                    userAccessRequest.Rights = right;
                    userAccessRequest.MemberID = patientRequestInfo.PatientID;
                    var result = authService.CheckUserPatientAccess(userAccessRequest).Result; //this calls DB service to check from DB
                    if (result.Status == ReturnType.Failure)
                    {
                        //TODO: return apirepsonse
                        context.Result = new StatusCodeResult((int)System.Net.HttpStatusCode.Forbidden);
                    }
                }
                else
                {
                    throw new AppSystemException("PatientAuthorizeAttribute not supported");
                }
            }
            else
            {
                throw new AppSystemException("PatientAuthorizeAttribute not supported");
            }
        }
    }
}

在 API 操作中,我们像这样使用它:

    [PatientAuthorize(PatientAccessRights.PATIENT_VIEW_APPOINTMENTS)] //this is enum, we can pass multiple
    [HttpPost]
    public SomeReturnType ViewAppointments()
    {

    }

请注意,当您想对 SignalR 中的集线器方法使用相同的属性时,IActionFilter 将成为问题。SignalR 集线器期望 IAuthorizationFilter
谢谢(你的)信息。我现在没有在我的应用程序中使用 SignalR,所以我没有用它测试它。
同样的原理我猜你仍然必须使用标头的授权条目,实现会有所不同