開篇提到過,認證主要解決的是who are you,受權解決的是 are you allowed的問題。各類認證架構能夠幫咱們知道用戶身份(claims),oauth等架構的scope字段可以控制api服務級別的訪問權限,可是更加細化和多變的功能受權不是它們的處理範圍。html
微軟的Authorization項目提供了基於策略的靈活的受權框架。設計模式
推薦看下面博客瞭解,我主要學習和梳理源碼。api
https://www.cnblogs.com/RainingNight/p/authorization-in-asp-net-core.htmlcookie
注入瞭如下接口,提供了默認實現架構
微軟的命名風格仍是比較一致的
Service:服務
Provider:某類的提供者
Evaluator:校驗預處理類
Factory:工廠
Handler:處理器
Context:上下文mvc
看源碼的過程,不只能夠學習框架背後原理,還能夠學習編碼風格和設計模式,仍是挺有用處的。框架
/// <summary> /// Adds authorization services to the specified <see cref="IServiceCollection" />. /// </summary> /// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> 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; } /// <summary> /// Adds authorization services to the specified <see cref="IServiceCollection" />. /// </summary> /// <param name="services">The <see cref="IServiceCollection" /> to add services to.</param> /// <param name="configure">An action delegate to configure the provided <see cref="AuthorizationOptions"/>.</param> /// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns> public static IServiceCollection AddAuthorizationCore(this IServiceCollection services, Action<AuthorizationOptions> configure) { if (services == null) { throw new ArgumentNullException(nameof(services)); } if (configure != null) { services.Configure(configure); } return services.AddAuthorizationCore(); }
/// <summary> /// Provides programmatic configuration used by <see cref="IAuthorizationService"/> and <see cref="IAuthorizationPolicyProvider"/>. /// </summary> public class AuthorizationOptions { private IDictionary<string, AuthorizationPolicy> PolicyMap { get; } = new Dictionary<string, AuthorizationPolicy>(StringComparer.OrdinalIgnoreCase); /// <summary> /// Determines whether authentication handlers should be invoked after a failure. /// Defaults to true. /// </summary> public bool InvokeHandlersAfterFailure { get; set; } = true; /// <summary> /// Gets or sets the default authorization policy. Defaults to require authenticated users. /// </summary> /// <remarks> /// The default policy used when evaluating <see cref="IAuthorizeData"/> with no policy name specified. /// </remarks> public AuthorizationPolicy DefaultPolicy { get; set; } = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build(); /// <summary> /// Gets or sets the fallback authorization policy used by <see cref="AuthorizationPolicy.CombineAsync(IAuthorizationPolicyProvider, IEnumerable{IAuthorizeData})"/> /// when no IAuthorizeData have been provided. As a result, the AuthorizationMiddleware uses the fallback policy /// if there are no <see cref="IAuthorizeData"/> instances for a resource. If a resource has any <see cref="IAuthorizeData"/> /// then they are evaluated instead of the fallback policy. By default the fallback policy is null, and usually will have no /// effect unless you have the AuthorizationMiddleware in your pipeline. It is not used in any way by the /// default <see cref="IAuthorizationService"/>. /// </summary> public AuthorizationPolicy FallbackPolicy { get; set; } /// <summary> /// Add an authorization policy with the provided name. /// </summary> /// <param name="name">The name of the policy.</param> /// <param name="policy">The authorization policy.</param> 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; } /// <summary> /// Add a policy that is built from a delegate with the provided name. /// </summary> /// <param name="name">The name of the policy.</param> /// <param name="configurePolicy">The delegate that will be used to build the policy.</param> 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(); } /// <summary> /// Returns the policy for the specified name, or null if a policy with the name does not exist. /// </summary> /// <param name="name">The name of the policy to return.</param> /// <returns>The policy for the specified name, or null if a policy with the name does not exist.</returns> public AuthorizationPolicy GetPolicy(string name) { if (name == null) { throw new ArgumentNullException(nameof(name)); } return PolicyMap.ContainsKey(name) ? PolicyMap[name] : null; } }
接口定義了受權方法,有兩個重載,一個是基於requirements校驗,一個是基於policyName校驗。less
Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements); Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);
看下默認實現DefaultAuthorizationService的處理,邏輯仍是比較簡單async
/// <summary> /// The default implementation of an <see cref="IAuthorizationService"/>. /// </summary> public class DefaultAuthorizationService : IAuthorizationService { private readonly AuthorizationOptions _options; private readonly IAuthorizationHandlerContextFactory _contextFactory; private readonly IAuthorizationHandlerProvider _handlers; private readonly IAuthorizationEvaluator _evaluator; private readonly IAuthorizationPolicyProvider _policyProvider; private readonly ILogger _logger; /// <summary> /// Creates a new instance of <see cref="DefaultAuthorizationService"/>. /// </summary> /// <param name="policyProvider">The <see cref="IAuthorizationPolicyProvider"/> used to provide policies.</param> /// <param name="handlers">The handlers used to fulfill <see cref="IAuthorizationRequirement"/>s.</param> /// <param name="logger">The logger used to log messages, warnings and errors.</param> /// <param name="contextFactory">The <see cref="IAuthorizationHandlerContextFactory"/> used to create the context to handle the authorization.</param> /// <param name="evaluator">The <see cref="IAuthorizationEvaluator"/> used to determine if authorization was successful.</param> /// <param name="options">The <see cref="AuthorizationOptions"/> used.</param> public DefaultAuthorizationService(IAuthorizationPolicyProvider policyProvider, IAuthorizationHandlerProvider handlers, ILogger<DefaultAuthorizationService> logger, IAuthorizationHandlerContextFactory contextFactory, IAuthorizationEvaluator evaluator, IOptions<AuthorizationOptions> options) { if (options == null) { throw new ArgumentNullException(nameof(options)); } if (policyProvider == null) { throw new ArgumentNullException(nameof(policyProvider)); } if (handlers == null) { throw new ArgumentNullException(nameof(handlers)); } if (logger == null) { throw new ArgumentNullException(nameof(logger)); } if (contextFactory == null) { throw new ArgumentNullException(nameof(contextFactory)); } if (evaluator == null) { throw new ArgumentNullException(nameof(evaluator)); } _options = options.Value; _handlers = handlers; _policyProvider = policyProvider; _logger = logger; _evaluator = evaluator; _contextFactory = contextFactory; } /// <summary> /// Checks if a user meets a specific set of requirements for the specified resource. /// </summary> /// <param name="user">The user to evaluate the requirements against.</param> /// <param name="resource">The resource to evaluate the requirements against.</param> /// <param name="requirements">The requirements to evaluate.</param> /// <returns> /// A flag indicating whether authorization has succeeded. /// This value is <value>true</value> when the user fulfills the policy otherwise <value>false</value>. /// </returns> public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements) { if (requirements == null) { throw new ArgumentNullException(nameof(requirements)); } var authContext = _contextFactory.CreateContext(requirements, user, resource); var handlers = await _handlers.GetHandlersAsync(authContext); foreach (var handler in handlers) { await handler.HandleAsync(authContext); if (!_options.InvokeHandlersAfterFailure && authContext.HasFailed) { break; } } var result = _evaluator.Evaluate(authContext); if (result.Succeeded) { _logger.UserAuthorizationSucceeded(); } else { _logger.UserAuthorizationFailed(); } return result; } /// <summary> /// Checks if a user meets a specific authorization policy. /// </summary> /// <param name="user">The user to check the policy against.</param> /// <param name="resource">The resource the policy should be checked with.</param> /// <param name="policyName">The name of the policy to check against a specific context.</param> /// <returns> /// A flag indicating whether authorization has succeeded. /// This value is <value>true</value> when the user fulfills the policy otherwise <value>false</value>. /// </returns> public async Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName) { if (policyName == null) { throw new ArgumentNullException(nameof(policyName)); } var policy = await _policyProvider.GetPolicyAsync(policyName); if (policy == null) { throw new InvalidOperationException($"No policy found: {policyName}."); } return await this.AuthorizeAsync(user, resource, policy); } }
默認策略添加了校驗條件DenyAnonymousAuthorizationRequirementide
public AuthorizationPolicyBuilder RequireAuthenticatedUser() { Requirements.Add(new DenyAnonymousAuthorizationRequirement()); return this; }
校驗上下文中是否存在認證用戶信息,驗證經過則在上下文中將校驗條件標記爲成功。
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; }
受權項目仍是比較好理解的,微軟提供了一個基於策略的受權模型,大部門的具體的業務代碼仍是須要本身去實現的。
開發不須要編寫UseAuthorization相似代碼,項目中也沒發現中間件,甚至找不到 使用AuthorizeAttribute的地方。那麼問題來了,框架怎麼知道某個方法標記了[Authorize]特性,而後執行校驗的呢?
答案是Mvc框架處理的,它讀取了節點的[Authorize]和[AllowAnonymous]特性,並觸發相應的邏輯。關於Mvc的就不細說了,感興趣能夠翻看源碼。
AspNetCore\src\Mvc\Mvc.Core\src\ApplicationModels\AuthorizationApplicationModelProvider.cs。
public void OnProvidersExecuting(ApplicationModelProviderContext context) { if (context == null) { throw new ArgumentNullException(nameof(context)); } if (_mvcOptions.EnableEndpointRouting) { // When using endpoint routing, the AuthorizationMiddleware does the work that Auth filters would otherwise perform. // Consequently we do not need to convert authorization attributes to filters. return; } foreach (var controllerModel in context.Result.Controllers) { var controllerModelAuthData = controllerModel.Attributes.OfType<IAuthorizeData>().ToArray(); if (controllerModelAuthData.Length > 0) { controllerModel.Filters.Add(GetFilter(_policyProvider, controllerModelAuthData)); } foreach (var attribute in controllerModel.Attributes.OfType<IAllowAnonymous>()) { controllerModel.Filters.Add(new AllowAnonymousFilter()); } foreach (var actionModel in controllerModel.Actions) { var actionModelAuthData = actionModel.Attributes.OfType<IAuthorizeData>().ToArray(); if (actionModelAuthData.Length > 0) { actionModel.Filters.Add(GetFilter(_policyProvider, actionModelAuthData)); } foreach (var attribute in actionModel.Attributes.OfType<IAllowAnonymous>()) { actionModel.Filters.Add(new AllowAnonymousFilter()); } } } }