Asp.Net Core 授权源码分析
说明
我们通过定义授权策略,查看源码发现,在对授权配置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<AuthorizationPolicyMarkerService>();
services.TryAddTransient<IPolicyEvaluator, PolicyEvaluator>();
services.TryAddTransient<IAuthorizationMiddlewareResultHandler, AuthorizationMiddlewareResultHandler>();
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<AuthorizationOptions> 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<IAuthorizationService, DefaultAuthorizationService>());
services.TryAdd(ServiceDescriptor.Transient<IAuthorizationPolicyProvider, DefaultAuthorizationPolicyProvider>());
services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerProvider, DefaultAuthorizationHandlerProvider>());
services.TryAdd(ServiceDescriptor.Transient<IAuthorizationEvaluator, DefaultAuthorizationEvaluator>()); services.TryAdd(ServiceDescriptor.Transient<IAuthorizationHandlerContextFactory, DefaultAuthorizationHandlerContextFactory>());
services.TryAddEnumerable(ServiceDescriptor.Transient<IAuthorizationHandler, PassThroughAuthorizationHandler>());
return services;
}
public static IServiceCollection AddAuthorizationCore(this IServiceCollection services, Action<AuthorizationOptions> 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<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(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<AuthorizationPolicyBuilder> 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<AuthorizationPolicyBuilder> configurePolicy);
重点讲解下下面的这个方法
这个方法可以通过一个有参构造来进行构建 AuthorizationPolicyBuilder主要作用就是用来创建AuthorizationPolicy,牵涉到的设计模式包含(建造者设计、单例);
1.3 IAuthorizationRequirement
授权要求
每一个AuthorizationPolicy
(策略)可以有多个IAuthorizationRequirement
授权要求。
每一个IAuthorizationRequirement
对应一个AuthorizationHandler
。
可以看到他是一个标记接口,主要是用来定位对应的处理Handler
。
public interface IAuthorizationRequirement
{
}
1.4 AuthorizationPolicy
表示授权要求和方案的集合。具体源码如下:
public class AuthorizationPolicy
{
public AuthorizationPolicy(IEnumerable<IAuthorizationRequirement> requirements, IEnumerable<string> 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<IAuthorizationRequirement>(requirements).AsReadOnly();
AuthenticationSchemes = new List<string>(authenticationSchemes).AsReadOnly();
}
public IReadOnlyList<IAuthorizationRequirement> Requirements { get; }
public IReadOnlyList<string> AuthenticationSchemes { get; }
public static AuthorizationPolicy Combine(params AuthorizationPolicy[] policies)
{
if (policies == null)
{
throw new ArgumentNullException(nameof(policies));
}
return Combine((IEnumerable<AuthorizationPolicy>)policies);
}
public static AuthorizationPolicy Combine(IEnumerable<AuthorizationPolicy> 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<AuthorizationPolicy> CombineAsync(IAuthorizationPolicyProvider policyProvider, IEnumerable<IAuthorizeData> 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<IAuthorizeData> 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
方法进行合并授权
1.5 AuthorizationPolicyBuilder
策略构建
主要完成对Authorizationpolicy
对象的构建。
整个对象就是对IAuthorizationRequirement
以及AuthenticationSchemes
的集合进行合并成一个。
public class AuthorizationPolicyBuilder
{
public AuthorizationPolicyBuilder(params string[] authenticationSchemes)
{
AddAuthenticationSchemes(authenticationSchemes);
}
public AuthorizationPolicyBuilder(AuthorizationPolicy policy)
{
Combine(policy);
}
public IList<IAuthorizationRequirement> Requirements { get; set; } = new List<IAuthorizationRequirement>();
public IList<string> AuthenticationSchemes { get; set; } = new List<string>();
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<string>)allowedValues);
}
public AuthorizationPolicyBuilder RequireClaim(string claimType, IEnumerable<string> 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<string>)roles);
}
public AuthorizationPolicyBuilder RequireRole(IEnumerable<string> 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<AuthorizationHandlerContext, bool> handler)
{
if (handler == null)
{
throw new ArgumentNullException(nameof(handler));
}
Requirements.Add(new AssertionRequirement(handler));
return this;
}
public AuthorizationPolicyBuilder RequireAssertion(Func<AuthorizationHandlerContext, Task<bool>> 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<DenyAnonymousAuthorizationRequirement>, 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 : AuthorizationHandler<NameAuthorizationRequirement>, 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;
}
}
1.6.3 ClaimsAuthorizationRequirement
基于Claim
声明类型的授权策略,检查当前用户是否声明ClaimType
以及ClaimValue
。
public class ClaimsAuthorizationRequirement : AuthorizationHandler<ClaimsAuthorizationRequirement>, IAuthorizationRequirement
{
public ClaimsAuthorizationRequirement(string claimType, IEnumerable<string> allowedValues)
{
if (claimType == null)
{
throw new ArgumentNullException(nameof(claimType));
}
ClaimType = claimType;
AllowedValues = allowedValues;
}
public string ClaimType { get; }
public IEnumerable<string> 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;
}
}
1.6.4 RolesAuthorizationRequirement
基于角色的授权策略,不建议使用,角色不能动态添加
public class RolesAuthorizationRequirement : AuthorizationHandler<RolesAuthorizationRequirement>, IAuthorizationRequirement
{
public RolesAuthorizationRequirement(IEnumerable<string> allowedRoles)
{
if (allowedRoles == null)
{
throw new ArgumentNullException(nameof(allowedRoles));
}
if (allowedRoles.Count() == 0)
{
throw new InvalidOperationException(Resources.Exception_RoleRequirementEmpty);
}
AllowedRoles = allowedRoles;
}
public IEnumerable<string> 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;
}
}
1.7 IAuthorizeData
授权特性标记
从上述可以看到上面有3种方式
当时这几种不满足我们企业开发的需求,后续会提供另外的一种方式。
-
AuthenticationSchemes 获取或设置一个逗号分隔的方案列表,从中构造用户信息。
-
Policy 获取或设置确定对资源的访问的策略名称。
-
Roles 获取或设置允许访问资源的角色的逗号分隔列表。
小结
当你想用某个或则自定义的IRequirement
的时候。
需要注入 services.AddSingleton<IAuthorizationHandler, IRequirementHander>()
。
由于内容篇幅太长,下一篇会讲解主要授权的代码以及流程。
最后也会放上企业级授权的方案。
另外官方地址给你们放上去 参考文档。