- 说明
- 源码分析
- 1.1 AddAuthorization
- 1.2 AuthorizationOptions
- 1.3 IAuthorizationRequirement
- 1.4 AuthorizationPolicy
- 1.5 AuthorizationPolicyBuilder
- 1.6 常见的Requirement
- 1.6.1 DenyAnonymousAuthorizationRequirement
- 1.6.2 NameAuthorizationRequirement
- 1.6.3 ClaimsAuthorizationRequirement
- 1.6.4 RolesAuthorizationRequirement
- 1.7 IAuthorizeData
- 小结
我们通过定义授权策略,查看源码发现,在对授权配置AuthorizationOptions之后,授权系统通过DI的方式注册了几个核心的默认实现。
源码分析 1.1 AddAuthorization添加授权策略服务使用AddAuthorization方法,以便调用。
从源码可以发现,从core3.0后,由之前在core2.0中的AuthorizationServiceCollectionExtensions.cs文件中,原来的AddAuthorization的方法变为了AddAuthorizationCore方法,微软在这一块进行了封装在PolicyServiceCollectionExtensions.cs文件中,沿用了之前AddAuthorization拓展名称,不影响之前版本的使用。
public static class PolicyServiceCollectionExtensions
{
public static IServiceCollection AddAuthorizationPolicyEvaluator(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.TryAddSingleton();
services.TryAddTransient();
services.TryAddTransient();
return services;
}
public static IServiceCollection AddAuthorization(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddAuthorizationCore();
services.AddAuthorizationPolicyEvaluator();
return services;
}
public static IServiceCollection AddAuthorization(this IServiceCollection services, Action configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.AddAuthorizationCore(configure);
services.AddAuthorizationPolicyEvaluator();
return services;
}
}
public static class AuthorizationServiceCollectionExtensions
{
public static IServiceCollection AddAuthorizationCore(this IServiceCollection services)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.TryAdd(ServiceDescriptor.Transient());
services.TryAdd(ServiceDescriptor.Transient());
services.TryAdd(ServiceDescriptor.Transient());
services.TryAdd(ServiceDescriptor.Transient()); services.TryAdd(ServiceDescriptor.Transient());
services.TryAddEnumerable(ServiceDescriptor.Transient());
return services;
}
public static IServiceCollection AddAuthorizationCore(this IServiceCollection services, Action configure)
{
if (services == null)
{
throw new ArgumentNullException(nameof(services));
}
services.Configure(configure);
return services.AddAuthorizationCore();
}
}
由上可知,在调用AddAuthorization方法进行授权配置的时候,需要使用到AuthorizationOptions委托方式传参。
所以我们再来看看下面这一行代码,通过AddPolicy实现添加策略方式。
options.AddPolicy("customizePermisson",policy => policy.Requirements.Add(new PermissionRequirement("user")));
通过上图可以把Policy(策略)加入到了AuthorizationOptions中的PolicyMap,Policy具体啥时候使用呢,后面会介绍到。
1.2 AuthorizationOptions授权选项实现添加和授权配置,提供授权服务的配置。
public class AuthorizationOptions
{
private Dictionary PolicyMap { get; } = new Dictionary(StringComparer.OrdinalIgnoreCase);
public bool InvokeHandlersAfterFailure { get; set; } = true;
public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
public AuthorizationPolicy? FallbackPolicy { get; set; }
public void AddPolicy(string name, AuthorizationPolicy policy)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
PolicyMap[name] = policy;
}
public void AddPolicy(string name, Action configurePolicy)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (configurePolicy == null)
{
throw new ArgumentNullException(nameof(configurePolicy));
}
var policyBuilder = new AuthorizationPolicyBuilder();
configurePolicy(policyBuilder);
PolicyMap[name] = policyBuilder.Build();
}
public AuthorizationPolicy GetPolicy(string name)
{
if (name == null)
{
throw new ArgumentNullException(nameof(name));
}
if (PolicyMap.TryGetValue(name, out var value))
{
return value;
}
return null;
}
}
它主要是用来保存Policy和设置默认的DefaultPolicy,
常用的2个方法
public void AddPolicy(string name, AuthorizationPolicy policy);
public void AddPolicy(string name, Action configurePolicy);
重点讲解下下面的这个方法
这个方法可以通过一个有参构造来进行构建 AuthorizationPolicyBuilder主要作用就是用来创建AuthorizationPolicy,牵涉到的设计模式包含(建造者设计、单例);
1.3 IAuthorizationRequirement授权要求
每一个AuthorizationPolicy(策略)可以有多个IAuthorizationRequirement授权要求。
每一个IAuthorizationRequirement对应一个AuthorizationHandler。
可以看到他是一个标记接口,主要是用来定位对应的处理Handler。
public interface IAuthorizationRequirement
{
}
1.4 AuthorizationPolicy
表示授权要求和方案的集合。具体源码如下:
public class AuthorizationPolicy
{
public AuthorizationPolicy(IEnumerable requirements, IEnumerable authenticationSchemes)
{
if (requirements == null)
{
throw new ArgumentNullException(nameof(requirements));
}
if (authenticationSchemes == null)
{
throw new ArgumentNullException(nameof(authenticationSchemes));
}
if (requirements.Count() == 0)
{
throw new InvalidOperationException(Resources.Exception_AuthorizationPolicyEmpty);
}
Requirements = new List(requirements).AsReadOnly();
AuthenticationSchemes = new List(authenticationSchemes).AsReadOnly();
}
public IReadOnlyList Requirements { get; }
public IReadOnlyList AuthenticationSchemes { get; }
public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies)
{
if (policies == null)
{
throw new ArgumentNullException(nameof(policies));
}
return Combine((IEnumerable)policies);
}
public static AuthorizationPolicy Combine(IEnumerable policies)
{
if (policies == null)
{
throw new ArgumentNullException(nameof(policies));
}
var builder = new AuthorizationPolicyBuilder();
foreach (var policy in policies)
{
builder.Combine(policy);
}
return builder.Build();
}
public static async Task CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable authorizeData)
{
if (policyProvider == null)
{
throw new ArgumentNullException(nameof(policyProvider));
}
if (authorizeData == null)
{
throw new ArgumentNullException(nameof(authorizeData));
}
// Avoid allocating enumerator if the data is known to be empty
var skipEnumeratingData = false;
if (authorizeData is IList dataList)
{
skipEnumeratingData = dataList.Count == 0;
}
AuthorizationPolicyBuilder policyBuilder = null;
if (!skipEnumeratingData)
{
foreach (var authorizeDatum in authorizeData)
{
if (policyBuilder == null)
{
policyBuilder = new AuthorizationPolicyBuilder();
}
var useDefaultPolicy = true;
if (!string.IsNullOrWhiteSpace(authorizeDatum.Policy))
{
var policy = await policyProvider.GetPolicyAsync(authorizeDatum.Policy);
if (policy == null)
{
throw new InvalidOperationException(Resources.FormatException_AuthorizationPolicyNotFound(authorizeDatum.Policy));
}
policyBuilder.Combine(policy);
useDefaultPolicy = false;
}
var rolesSplit = authorizeDatum.Roles?.Split(',');
if (rolesSplit != null && rolesSplit.Any())
{
var trimmedRolesSplit = rolesSplit.Where(r => !string.IsNullOrWhiteSpace(r)).Select(r => r.Trim());
policyBuilder.RequireRole(trimmedRolesSplit);
useDefaultPolicy = false;
}
var authTypesSplit = authorizeDatum.AuthenticationSchemes?.Split(',');
if (authTypesSplit != null && authTypesSplit.Any())
{
foreach (var authType in authTypesSplit)
{
if (!string.IsNullOrWhiteSpace(authType))
{
policyBuilder.AuthenticationSchemes.Add(authType.Trim());
}
}
}
if (useDefaultPolicy)
{
policyBuilder.Combine(await policyProvider.GetDefaultPolicyAsync());
}
}
}
// If we have no policy by now, use the fallback policy if we have one
if (policyBuilder == null)
{
var fallbackPolicy = await policyProvider.GetFallbackPolicyAsync();
if (fallbackPolicy != null)
{
return fallbackPolicy;
}
}
return policyBuilder?.Build();
}
}
我们从源码中可以发现
AuthorizationPolicy对象的CombineAsync方法(该方法会在AuthorizationMiddleware中间件中去调用)
- 它主要完成对控制器或则方法进行IAuthorizeData标记特性中PolicyName属性以及Roles属性值的获取
- 通过PolicyName从AuthorizationPolicyProvider中获取AuthorizationPolicy对象
- 通过AuthorizationPolicyBuilder的Combine方法进行合并授权
策略构建
主要完成对Authorizationpolicy对象的构建。
整个对象就是对IAuthorizationRequirement以及AuthenticationSchemes的集合进行合并成一个。
public class AuthorizationPolicyBuilder
{
public AuthorizationPolicyBuilder(params string[] authenticationSchemes)
{
AddAuthenticationSchemes(authenticationSchemes);
}
public AuthorizationPolicyBuilder(AuthorizationPolicy policy)
{
Combine(policy);
}
public IList Requirements { get; set; } = new List();
public IList AuthenticationSchemes { get; set; } = new List();
public AuthorizationPolicyBuilder AddAuthenticationSchemes(params string[] schemes)
{
foreach (var authType in schemes)
{
AuthenticationSchemes.Add(authType);
}
return this;
}
public AuthorizationPolicyBuilder AddRequirements(params IAuthorizationRequirement[] requirements)
{
foreach (var req in requirements)
{
Requirements.Add(req);
}
return this;
}
public AuthorizationPolicyBuilder Combine(AuthorizationPolicy policy)
{
if (policy == null)
{
throw new ArgumentNullException(nameof(policy));
}
AddAuthenticationSchemes(policy.AuthenticationSchemes.ToArray());
AddRequirements(policy.Requirements.ToArray());
return this;
}
public AuthorizationPolicyBuilder RequireClaim(string claimType, params string[] allowedValues)
{
if (claimType == null)
{
throw new ArgumentNullException(nameof(claimType));
}
return RequireClaim(claimType, (IEnumerable)allowedValues);
}
public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable allowedValues)
{
if (claimType == null)
{
throw new ArgumentNullException(nameof(claimType));
}
Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues));
return this;
}
public AuthorizationPolicyBuilder RequireClaim(string claimType)
{
if (claimType == null)
{
throw new ArgumentNullException(nameof(claimType));
}
Requirements.Add(new ClaimsAuthorizationRequirement(claimType, allowedValues: null));
return this;
}
public AuthorizationPolicyBuilder RequireRole(params string[] roles)
{
if (roles == null)
{
throw new ArgumentNullException(nameof(roles));
}
return RequireRole((IEnumerable)roles);
}
public AuthorizationPolicyBuilder RequireRole(IEnumerable roles)
{
if (roles == null)
{
throw new ArgumentNullException(nameof(roles));
}
Requirements.Add(new RolesAuthorizationRequirement(roles));
return this;
}
public AuthorizationPolicyBuilder RequireUserName(string userName)
{
if (userName == null)
{
throw new ArgumentNullException(nameof(userName));
}
Requirements.Add(new NameAuthorizationRequirement(userName));
return this;
}
public AuthorizationPolicyBuilder RequireAuthenticatedUser()
{
Requirements.Add(new DenyAnonymousAuthorizationRequirement());
return this;
}
public AuthorizationPolicyBuilder RequireAssertion(Func handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}
Requirements.Add(new AssertionRequirement(handler));
return this;
}
public AuthorizationPolicyBuilder RequireAssertion(Func> handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}
Requirements.Add(new AssertionRequirement(handler));
return this;
}
public AuthorizationPolicy Build()
{
return new AuthorizationPolicy(Requirements, AuthenticationSchemes.Distinct());
}
}
1.6 常见的Requirement
1.6.1 DenyAnonymousAuthorizationRequirement
阻止匿名用户操作,没有通过认证的用户是不允许访问资源的
public class DenyAnonymousAuthorizationRequirement : AuthorizationHandler, IAuthorizationRequirement { protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, DenyAnonymousAuthorizationRequirement requirement) { var user = context.User; var userIsAnonymous = user?.Identity == null || !user.Identities.Any(i => i.IsAuthenticated); if (!userIsAnonymous) { context.Succeed(requirement); } return Task.CompletedTask; } }
就是检查用户的Identities是否存在一个通过认证的。
1.6.2 NameAuthorizationRequirement顾名思义指定名称来判断是否匹配授权访问资源。
public class NameAuthorizationRequirement : AuthorizationHandler1.6.3 ClaimsAuthorizationRequirement, IAuthorizationRequirement { public NameAuthorizationRequirement(string requiredName) { if (requiredName == null) { throw new ArgumentNullException(nameof(requiredName)); } RequiredName = requiredName; } public string RequiredName { get; } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, NameAuthorizationRequirement requirement) { if (context.User != null) { if (context.User.Identities.Any(i => string.Equals(i.Name, requirement.RequiredName))) { context.Succeed(requirement); } } return Task.CompletedTask; } }
基于Claim声明类型的授权策略,检查当前用户是否声明ClaimType以及ClaimValue。
public class ClaimsAuthorizationRequirement : AuthorizationHandler1.6.4 RolesAuthorizationRequirement, IAuthorizationRequirement { public ClaimsAuthorizationRequirement(string claimType, IEnumerable allowedValues) { if (claimType == null) { throw new ArgumentNullException(nameof(claimType)); } ClaimType = claimType; AllowedValues = allowedValues; } public string ClaimType { get; } public IEnumerable AllowedValues { get; } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, ClaimsAuthorizationRequirement requirement) { if (context.User != null) { var found = false; if (requirement.AllowedValues == null || !requirement.AllowedValues.Any()) { found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase)); } else { found = context.User.Claims.Any(c => string.Equals(c.Type, requirement.ClaimType, StringComparison.OrdinalIgnoreCase) && requirement.AllowedValues.Contains(c.Value, StringComparer.Ordinal)); } if (found) { context.Succeed(requirement); } } return Task.CompletedTask; } }
基于角色的授权策略,不建议使用,角色不能动态添加
public class RolesAuthorizationRequirement : AuthorizationHandler1.7 IAuthorizeData, IAuthorizationRequirement { public RolesAuthorizationRequirement(IEnumerable allowedRoles) { if (allowedRoles == null) { throw new ArgumentNullException(nameof(allowedRoles)); } if (allowedRoles.Count() == 0) { throw new InvalidOperationException(Resources.Exception_RoleRequirementEmpty); } AllowedRoles = allowedRoles; } public IEnumerable AllowedRoles { get; } protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement) { if (context.User != null) { bool found = false; if (requirement.AllowedRoles == null || !requirement.AllowedRoles.Any()) { // Review: What do we want to do here? No roles requested is auto success? } else { found = requirement.AllowedRoles.Any(r => context.User.IsInRole(r)); } if (found) { context.Succeed(requirement); } } return Task.CompletedTask; } }
授权特性标记
从上述可以看到上面有3种方式
当时这几种不满足我们企业开发的需求,后续会提供另外的一种方式。
-
AuthenticationSchemes 获取或设置一个逗号分隔的方案列表,从中构造用户信息。
-
Policy 获取或设置确定对资源的访问的策略名称。
-
Roles 获取或设置允许访问资源的角色的逗号分隔列表。
当你想用某个或则自定义的IRequirement的时候。
需要注入 services.AddSingleton
由于内容篇幅太长,下一篇会讲解主要授权的代码以及流程。
最后也会放上企业级授权的方案。
另外官方地址给你们放上去 参考文档。



