我正在尝试在 ASP.NET Core 中创建自定义授权属性。在以前的版本中,可以覆盖 bool AuthorizeCore(HttpContextBase httpContext)
。但这在 AuthorizeAttribute
中不再存在。
当前制作自定义 AuthorizeAttribute 的方法是什么?
我要完成的工作:我在标头授权中收到一个会话 ID。从那个 ID 我会知道一个特定的动作是否有效。
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();
}
}
我是 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")]
[CustomAuthorize(Operator.And, Permission.GetUser, Permission.ModifyUser)]
之类的操作非常简单。只需修改构造函数参数,我就可以通过无数种方式使用单个自定义属性。
IAuthorizeData.Policy
的含义)和自定义策略提供程序来克服这种公然的疏忽,而不是在框架内解决它.我以为我们不应该创建自己的实现?您让我们中的一些人别无选择,只能(再次)从头重新实现授权,而这一次甚至没有 Web API 的旧 Authorize
属性的好处。现在我们必须在动作过滤器或中间件级别上执行此操作。
似乎使用 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
,否则您的过滤器将同步执行,并且无论过滤器的结果如何,您的控制器操作都将执行。
基于 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());
}
}
您可以创建自己的 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
}
}
当前制作自定义 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() { ... }
}
对于身份验证,最好在中间件级别处理。
你到底想达到什么目的?
什么?!
我决定添加另一个简单的答案。 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";
}
现代方式是 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)]
接受的答案 (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
实例才能在控制器中使用它们。我希望有一个可以接受一些标量参数(可能是无限组合)的策略。这样我就不会破坏开闭原则。你的例子确实如此。 (无论如何我很感激)
下面的代码在.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;
}
}
在撰写本文时,我相信这可以通过 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.
™ 您可能需要更多的错误处理和登录您的实现。
只是添加来自@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 的方式已经改变。
我有不记名令牌,我可以阅读索赔。我在控制器和动作上使用该属性
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;
}
这里很多人已经告诉过这一点,但是使用策略处理程序,您可以在 .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>
我希望这对任何面临这个问题的人有用。
我一直在研究解决一个非常相似的问题,并决定创建一个自定义 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。
在我们的应用程序中进行授权。我们必须根据授权属性中传递的参数调用服务。
例如,如果我们想检查登录的医生是否可以查看患者预约,我们会将“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()
{
}
context.Result = new ForbiddenResult();
,此方法将返回InvalidOperationException
,我们应该如何在 dotnet 6 中执行此操作?