我试图使一个自定义授权属性在ASP。净的核心。在以前的版本中,可以重写bool AuthorizeCore(HttpContextBase httpContext)。但是这在AuthorizeAttribute中不再存在。

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

我想要完成的:我正在头授权中接收会话ID。通过该ID,我将知道特定操作是否有效。


当前回答

我是asp.net安全人员。首先,让我道歉,除了音乐存储样本或单元测试之外,这些都还没有被记录下来,而且还在公开的api方面进行改进。详细的文档在这里。

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

授权作用于身份。身份通过认证创建。

You say in comments you want to check a session ID in a header. Your session ID would be the basis for identity. If you wanted to use the Authorize attribute you'd write an authentication middleware to take that header and turn it into an authenticated ClaimsPrincipal. You would then check that inside an authorization requirement. Authorization requirements can be as complicated as you like, for example here's one that takes a date of birth claim on the current identity and will authorize if the user is over 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")]

其他回答

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

什么? !

我决定再补充一个简单的答案。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";
}

只是给@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的方式已经改变。

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

基于Derek Greer伟大的答案,我用枚举来做。

下面是我的代码示例:

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