.Net Core [Authorize] - 或者代替 And 进行权限测试
.Net Core [Authorize] - Or instead of And for permission test
如果用户同时是 "admin" 的 Role
AND
和 "New York" 的 Policy
,则以下代码授权过滤器通过。我如何将其更改为 "admin" OR
"New York"
[Authorize(Policy = "New York", Roles = "admin")]
在这种特定情况下,我想要一个 OR
语句,而不是 AND
。
没有 built-in 函数可以做到这一点。
但您可以轻松实现自定义 LogicalOrPolicyProvider
(以及处理程序)来实现相同的目标。 LogicalOrPolicyProvider
会根据策略名称动态构建策略,例如:
[Authorize(Policy="Choice: policy='New York'| role= ADMIN")]
以上属性将生成一个新策略,该策略应满足 'New York' 的策略或需要 ADMIN
的角色
此外,我们可以定义一些规则来处理更一般的情况。假设要编写以下要求:
Choice: policy='New York'| role= ADMIN
Choice: policy='New York'| role= 'ADMIN'
Choice: policy='New York'| policy = 'WC' | role= root | role = 'GVN'
你可以根据自己的喜好定义自己的规则,我个人更喜欢:
- 以标记
Choice
开头,后跟分隔符 :
(可能有几个可选的 space 字符 ' '
)
- 一个策略由
policy=policyName
定义,如果策略名称包含space,你应该用''
包围它。您可以根据需要定义多个 policy
- 该角色由
role = roleName
定义。您还可以定义任意数量的角色。
- 所有
policy
和 role
定义由 |
分隔。
上述设计的实现:
让我们定义一个 LogicalOrRequirement
来保存所有可能的策略:
public class LogicalOrRequirement : IAuthorizationRequirement
{
public IList<AuthorizationPolicy> Policies { get; }
public LogicalOrRequirement(IList<AuthorizationPolicy> policies)
{
this.Policies = policies;
}
}
如果这些策略中的任何一个成功,就跳过:
public class LogicalOrAuthorizationHandler : AuthorizationHandler<LogicalOrRequirement>
{
public LogicalOrAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
{
this._httpContextAccessor = httpContextAccessor;
}
private readonly IHttpContextAccessor _httpContextAccessor;
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, LogicalOrRequirement requirement)
{
var httpContext = this._httpContextAccessor.HttpContext;
var policyEvaluator = httpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();
foreach (var policy in requirement.Policies)
{
var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, httpContext);
if (authenticateResult.Succeeded)
{
context.Succeed(requirement);
}
}
}
}
现在让我们通过 PolicyProvider
:
动态构建策略
public class LogicalOrPolicyProvider : IAuthorizationPolicyProvider
{
const string POLICY_PREFIX = "Choice";
const string TOKEN_POLICY="policy";
const string TOKEN_ROLE="role";
public const string Format = "Choice: policy='p3' | policy='p2' | role='role1' | ...";
private AuthorizationOptions _authZOpts { get; }
public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
public LogicalOrPolicyProvider(IOptions<AuthorizationOptions> options )
{
_authZOpts = options.Value;
FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
// Choice: policy= | policy= | role= | role = ...
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase))
{
var policyNames = policyName.Substring(POLICY_PREFIX.Length);
var startIndex = policyNames.IndexOf(":");
if(startIndex == -1 || startIndex == policyNames.Length)
{
throw new ArgumentException($"invalid syntax, must contains a ':' before tokens. The correct format is {Format}");
}
// skip the ":" , and turn it into the following list
// [[policy,policyName],[policy,policName],...[role,roleName],...,]
var list= policyNames.Substring(startIndex+1)
.Split("|")
.Select(p => p.Split("=").Select(e => e.Trim().Trim('\'')).ToArray() )
;
// build policy for roleNames
var rolesPolicyBuilder = new AuthorizationPolicyBuilder();
var roleNames =list.Where(arr => arr[0].ToLower() == TOKEN_ROLE)
.Select(arr => arr[1])
.ToArray();
var rolePolicy = rolesPolicyBuilder.RequireRole(roleNames).Build();
// get policies with all related names
var polices1= list.Where(arr => arr[0].ToLower() == TOKEN_POLICY);
var polices=polices1
.Select(arr => arr[1])
.Select(name => this._authZOpts.GetPolicy(name)) // if the policy with the name doesn exit => null
.Where(p => p != null) // filter null policy
.Append(rolePolicy)
.ToList();
var pb= new AuthorizationPolicyBuilder();
pb.AddRequirements(new LogicalOrRequirement(polices));
return Task.FromResult(pb.Build());
}
return FallbackPolicyProvider.GetPolicyAsync(policyName);
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
return FallbackPolicyProvider.GetDefaultPolicyAsync();
}
}
最后,不要忘记在您的 Startup.cs
中注册这两项服务:
services.AddSingleton<IAuthorizationPolicyProvider, LogicalOrPolicyProvider>();
services.AddSingleton<IAuthorizationHandler, LogicalOrAuthorizationHandler>();
现在,每当你想要逻辑or
组合时,只需添加一个[Authorize(Policy="Choice: ...")]
:
[Authorize(Policy="Choice: policy='New York'| role= ADMIN")]
public IActionResult Privacy()
{
return View();
}
如果用户同时是 "admin" 的 Role
AND
和 "New York" 的 Policy
,则以下代码授权过滤器通过。我如何将其更改为 "admin" OR
"New York"
[Authorize(Policy = "New York", Roles = "admin")]
在这种特定情况下,我想要一个 OR
语句,而不是 AND
。
没有 built-in 函数可以做到这一点。
但您可以轻松实现自定义 LogicalOrPolicyProvider
(以及处理程序)来实现相同的目标。 LogicalOrPolicyProvider
会根据策略名称动态构建策略,例如:
[Authorize(Policy="Choice: policy='New York'| role= ADMIN")]
以上属性将生成一个新策略,该策略应满足 'New York' 的策略或需要 ADMIN
此外,我们可以定义一些规则来处理更一般的情况。假设要编写以下要求:
Choice: policy='New York'| role= ADMIN
Choice: policy='New York'| role= 'ADMIN'
Choice: policy='New York'| policy = 'WC' | role= root | role = 'GVN'
你可以根据自己的喜好定义自己的规则,我个人更喜欢:
- 以标记
Choice
开头,后跟分隔符:
(可能有几个可选的 space 字符' '
) - 一个策略由
policy=policyName
定义,如果策略名称包含space,你应该用''
包围它。您可以根据需要定义多个policy
- 该角色由
role = roleName
定义。您还可以定义任意数量的角色。 - 所有
policy
和role
定义由|
分隔。
上述设计的实现:
让我们定义一个 LogicalOrRequirement
来保存所有可能的策略:
public class LogicalOrRequirement : IAuthorizationRequirement
{
public IList<AuthorizationPolicy> Policies { get; }
public LogicalOrRequirement(IList<AuthorizationPolicy> policies)
{
this.Policies = policies;
}
}
如果这些策略中的任何一个成功,就跳过:
public class LogicalOrAuthorizationHandler : AuthorizationHandler<LogicalOrRequirement>
{
public LogicalOrAuthorizationHandler(IHttpContextAccessor httpContextAccessor)
{
this._httpContextAccessor = httpContextAccessor;
}
private readonly IHttpContextAccessor _httpContextAccessor;
protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, LogicalOrRequirement requirement)
{
var httpContext = this._httpContextAccessor.HttpContext;
var policyEvaluator = httpContext.RequestServices.GetRequiredService<IPolicyEvaluator>();
foreach (var policy in requirement.Policies)
{
var authenticateResult = await policyEvaluator.AuthenticateAsync(policy, httpContext);
if (authenticateResult.Succeeded)
{
context.Succeed(requirement);
}
}
}
}
现在让我们通过 PolicyProvider
:
public class LogicalOrPolicyProvider : IAuthorizationPolicyProvider
{
const string POLICY_PREFIX = "Choice";
const string TOKEN_POLICY="policy";
const string TOKEN_ROLE="role";
public const string Format = "Choice: policy='p3' | policy='p2' | role='role1' | ...";
private AuthorizationOptions _authZOpts { get; }
public DefaultAuthorizationPolicyProvider FallbackPolicyProvider { get; }
public LogicalOrPolicyProvider(IOptions<AuthorizationOptions> options )
{
_authZOpts = options.Value;
FallbackPolicyProvider = new DefaultAuthorizationPolicyProvider(options);
}
// Choice: policy= | policy= | role= | role = ...
public Task<AuthorizationPolicy> GetPolicyAsync(string policyName)
{
if (policyName.StartsWith(POLICY_PREFIX, StringComparison.OrdinalIgnoreCase))
{
var policyNames = policyName.Substring(POLICY_PREFIX.Length);
var startIndex = policyNames.IndexOf(":");
if(startIndex == -1 || startIndex == policyNames.Length)
{
throw new ArgumentException($"invalid syntax, must contains a ':' before tokens. The correct format is {Format}");
}
// skip the ":" , and turn it into the following list
// [[policy,policyName],[policy,policName],...[role,roleName],...,]
var list= policyNames.Substring(startIndex+1)
.Split("|")
.Select(p => p.Split("=").Select(e => e.Trim().Trim('\'')).ToArray() )
;
// build policy for roleNames
var rolesPolicyBuilder = new AuthorizationPolicyBuilder();
var roleNames =list.Where(arr => arr[0].ToLower() == TOKEN_ROLE)
.Select(arr => arr[1])
.ToArray();
var rolePolicy = rolesPolicyBuilder.RequireRole(roleNames).Build();
// get policies with all related names
var polices1= list.Where(arr => arr[0].ToLower() == TOKEN_POLICY);
var polices=polices1
.Select(arr => arr[1])
.Select(name => this._authZOpts.GetPolicy(name)) // if the policy with the name doesn exit => null
.Where(p => p != null) // filter null policy
.Append(rolePolicy)
.ToList();
var pb= new AuthorizationPolicyBuilder();
pb.AddRequirements(new LogicalOrRequirement(polices));
return Task.FromResult(pb.Build());
}
return FallbackPolicyProvider.GetPolicyAsync(policyName);
}
public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
{
return FallbackPolicyProvider.GetDefaultPolicyAsync();
}
}
最后,不要忘记在您的 Startup.cs
中注册这两项服务:
services.AddSingleton<IAuthorizationPolicyProvider, LogicalOrPolicyProvider>();
services.AddSingleton<IAuthorizationHandler, LogicalOrAuthorizationHandler>();
现在,每当你想要逻辑or
组合时,只需添加一个[Authorize(Policy="Choice: ...")]
:
[Authorize(Policy="Choice: policy='New York'| role= ADMIN")]
public IActionResult Privacy()
{
return View();
}