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

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

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


当前回答

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

其他回答

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

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

例如,如果我们想检查登录的医生是否可以查看病人的预约,我们将传递“View_Appointment”到自定义授权属性,并在DB服务中检查该权利,并根据结果进行审查。下面是这个场景的代码:

    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()
    {

    }

如果有人只是想在授权阶段使用当前的安全实践验证一个承载令牌,

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

下面的代码适合我在。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;
    }
}

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