bool AuthorizeCore(HttpContextBase httpContext)
。 但在AuthorizeAttribute
中不再存在此功能。当前制定自定义AuthorizeAttribute的方法是什么?
我想要实现的目标是:从Header Authorization中获取会话ID,然后根据该ID确定特定操作是否有效。
bool AuthorizeCore(HttpContextBase httpContext)
。 但在AuthorizeAttribute
中不再存在此功能。ASP.Net Core团队推荐的方法是使用全面记录在此处的新策略设计。 新方法背后的基本思想是使用新的[Authorize]
属性来指定“策略”(例如,[Authorize( Policy = "YouNeedToBe18ToDoThis")]
其中策略在应用程序的Startup.cs
中注册),以执行某个代码块(即确保用户具有年龄声明,其中年龄为18岁或更老)。
这项政策设计是框架的重要补充,ASP.Net Security Core团队应该因其引入而受到赞扬。然而,它并不适用于所有情况。这种方法的缺点是,在最常见的情况下,即断言给定控制器或操作需要给定声明类型时,它无法提供方便的解决方案。在应用程序可能有数百个离散权限来管理对个体REST资源的CRUD操作(“CanCreateOrder”,“CanReadOrder”,“CanUpdateOrder”,“CanDeleteOrder”等)的情况下,新方法要么需要在策略名称和声明名称之间进行重复的一对一映射(例如,options.AddPolicy("CanUpdateOrder", policy => policy.RequireClaim(MyClaimTypes.Permission, "CanUpdateOrder));
),要么编写一些代码在运行时执行这些注册(例如,从数据库中读取所有声明类型并在循环中执行上述调用)。对于大多数情况,这种方法的问题在于它是不必要的开销。
虽然ASP.Net Core安全团队建议永远不要创建自己的解决方案,但在某些情况下,这可能是最明智的起点。
以下是一种实现方式,它使用IAuthorizationFilter
提供了一种简单的方式来表达对给定控制器或操作的声明要求:
public class ClaimRequirementAttribute : TypeFilterAttribute
{
public ClaimRequirementAttribute(string claimType, string claimValue) : base(typeof(ClaimRequirementFilter))
{
Arguments = new object[] {new Claim(claimType, claimValue) };
}
}
public class ClaimRequirementFilter : IAuthorizationFilter
{
readonly Claim _claim;
public ClaimRequirementFilter(Claim claim)
{
_claim = claim;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
var hasClaim = context.HttpContext.User.Claims.Any(c => c.Type == _claim.Type && c.Value == _claim.Value);
if (!hasClaim)
{
context.Result = new ForbidResult();
}
}
}
[Route("api/resource")]
public class MyController : Controller
{
[ClaimRequirement(MyClaimTypes.Permission, "CanReadResource")]
[HttpGet]
public IActionResult GetResource()
{
return Ok();
}
}
context.Result = new ForbiddenResult();
这个方法,它会返回一个"InvalidOperationException"异常。在dotnet 6中应该如何处理这个问题? - Muhammad Mamoor Khan我是asp.net安全专员。首先,让我道歉,除了音乐商店示例或单元测试之外,所有这些都还没有记录下来,并且在暴露的API方面仍在完善中。详细文档可以在这里找到。
我们不希望您编写自定义授权属性。如果您需要这样做,那么我们做错了什么。相反,您应该编写授权要求。
授权作用于身份。身份由身份验证创建。
您在评论中说您想检查标头中的会话ID。您的会话ID将成为身份的基础。如果您想使用Authorize
属性,您可以编写一个身份验证中间件将该标头转换为经过身份验证的ClaimsPrincipal
。然后,您将在授权要求内进行检查。授权要求可以像您想要的那样复杂,例如,这里有一个授权要求,在当前身份上获取出生日期声明并在用户年满18岁时授权;
public class Over18Requirement : AuthorizationHandler<Over18Requirement>, IAuthorizationRequirement
{
public override void Handle(AuthorizationHandlerContext context, Over18Requirement requirement)
{
if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
{
context.Fail();
return;
}
var dobVal = context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value;
var dateOfBirth = Convert.ToDateTime(dobVal);
int age = DateTime.Today.Year - dateOfBirth.Year;
if (dateOfBirth > DateTime.Today.AddYears(-age))
{
age--;
}
if (age >= 18)
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
}
}
然后在您的ConfigureServices()
函数中连接它
services.AddAuthorization(options =>
{
options.AddPolicy("Over18",
policy => policy.Requirements.Add(new Authorization.Over18Requirement()));
});
最后,将其应用于控制器或操作方法中:
[Authorize(Policy = "Over18")]
[CustomAuthorize(Operator.And, Permission.GetUser, Permission.ModifyUser)]
这样的事情。我只需修改构造函数参数就可以将单个自定义属性用于无限数量的方式。 - NathanAldenSrIAuthorizeData.Policy
的含义)和自定义策略提供程序来克服这个明显的疏忽,而不是在框架内解决它。我原以为我们不应该创建自己的实现?你让我们几个人别无选择,只能再次从头开始重新实现授权,而且这一次甚至没有Web API旧版的Authorize
属性的好处了。现在我们只能在动作过滤器或中间件级别上进行实现。 - NathanAldenSr看起来在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;
}
}
}
OnAuthorization
实现需要等待异步方法,则应实现IAsyncAuthorizationFilter
而不是IAuthorizationFilter
,否则您的过滤器将同步执行,您的控制器操作将无论过滤器的结果如何都会执行。 - Codemunkie根据 Derek Greer 的 GREAT 回答,我使用枚举实现了它。
这是我的代码示例:
public enum PermissionItem
{
User,
Product,
Contact,
Review,
Client
}
public enum PermissionAction
{
Read,
Create,
}
public class AuthorizeAttribute : TypeFilterAttribute
{
public AuthorizeAttribute(PermissionItem item, PermissionAction action)
: base(typeof(AuthorizeActionFilter))
{
Arguments = new object[] { item, action };
}
}
public class AuthorizeActionFilter : IAuthorizationFilter
{
private readonly PermissionItem _item;
private readonly PermissionAction _action;
public AuthorizeActionFilter(PermissionItem item, PermissionAction action)
{
_item = item;
_action = action;
}
public void OnAuthorization(AuthorizationFilterContext context)
{
bool isAuthorized = MumboJumboFunction(context.HttpContext.User, _item, _action); // :)
if (!isAuthorized)
{
context.Result = new ForbidResult();
}
}
}
public class UserController : BaseController
{
private readonly DbContext _context;
public UserController( DbContext context) :
base()
{
_logger = logger;
}
[Authorize(PermissionItem.User, PermissionAction.Read)]
public async Task<IActionResult> Index()
{
return View(await _context.User.ToListAsync());
}
}
您可以创建自己的AuthorizationHandler,该处理程序将查找控制器和操作上的自定义属性,并将它们传递给HandleRequirementAsync方法。
public abstract class AttributeAuthorizationHandler<TRequirement, TAttribute> : AuthorizationHandler<TRequirement> where TRequirement : IAuthorizationRequirement where TAttribute : Attribute
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement)
{
var attributes = new List<TAttribute>();
var action = (context.Resource as AuthorizationFilterContext)?.ActionDescriptor as ControllerActionDescriptor;
if (action != null)
{
attributes.AddRange(GetAttributes(action.ControllerTypeInfo.UnderlyingSystemType));
attributes.AddRange(GetAttributes(action.MethodInfo));
}
return HandleRequirementAsync(context, requirement, attributes);
}
protected abstract Task HandleRequirementAsync(AuthorizationHandlerContext context, TRequirement requirement, IEnumerable<TAttribute> attributes);
private static IEnumerable<TAttribute> GetAttributes(MemberInfo memberInfo)
{
return memberInfo.GetCustomAttributes(typeof(TAttribute), false).Cast<TAttribute>();
}
}
那么你就可以将其用于控制器或操作上需要的任何自定义属性,例如添加权限要求。只需创建您的自定义属性即可。
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class PermissionAttribute : AuthorizeAttribute
{
public string Name { get; }
public PermissionAttribute(string name) : base("Permission")
{
Name = name;
}
}
然后创建一个需求,添加到您的策略中。
public class PermissionAuthorizationRequirement : IAuthorizationRequirement
{
//Add any custom requirement properties if you have them
}
然后创建您的自定义属性的 AuthorizationHandler,继承我们之前创建的 AttributeAuthorizationHandler。在 HandleRequirementsAsync 方法中,它将接收一个IEnumerable参数,其中包含从您的控制器和操作累积的所有自定义属性。
public class PermissionAuthorizationHandler : AttributeAuthorizationHandler<PermissionAuthorizationRequirement, PermissionAttribute>
{
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionAuthorizationRequirement requirement, IEnumerable<PermissionAttribute> attributes)
{
foreach (var permissionAttribute in attributes)
{
if (!await AuthorizeAsync(context.User, permissionAttribute.Name))
{
return;
}
}
context.Succeed(requirement);
}
private Task<bool> AuthorizeAsync(ClaimsPrincipal user, string permission)
{
//Implement your custom user permission logic here
}
}
最后,在您的 Startup.cs 的 ConfigureServices 方法中,将自定义的 AuthorizationHandler 添加到服务中,并添加您的 Policy。
services.AddSingleton<IAuthorizationHandler, PermissionAuthorizationHandler>();
services.AddAuthorization(options =>
{
options.AddPolicy("Permission", policyBuilder =>
{
policyBuilder.Requirements.Add(new PermissionAuthorizationRequirement());
});
});
现在你可以简单地使用自定义属性来装饰你的控制器和操作。
[Permission("AccessCustomers")]
public class CustomersController
{
[Permission("AddCustomer")]
IActionResult AddCustomer([FromBody] Customer customer)
{
//Add customer
}
}
什么?!
我决定再添加一个简单的答案。因为我发现大多数答案都有点过于复杂了。而且我需要一种授权而不是拒绝它的方式。这里的大多数答案都提供了一种“加强”安全性的方法,但我想“放松”它。例如:“如果某个应用程序设置已配置,则允许匿名用户访问”。
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";
}
IAsyncAuthorizationFilter
,并在其中提及@PeterBons。 - Alex from Jitbit如何创建自定义的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() { ... }
}
对于认证,最好在中间件层处理。
你到底想要实现什么?
现代的方式是使用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";
}
}
如果有人只想在授权阶段使用当前的安全实践验证承载令牌,您可以将以下内容添加到您的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)]
[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;
}
}