AspNetCore3.1_Secutiry源碼解析_8_Authorization_授權框架

来源:https://www.cnblogs.com/holdengong/archive/2020/03/26/12575914.html
-Advertisement-
Play Games

目錄 "AspNetCore3.1_Secutiry源碼解析_1_目錄" "AspNetCore3.1_Secutiry源碼解析_2_Authentication_核心流程" "AspNetCore3.1_Secutiry源碼解析_3_Authentication_Cookies" "AspNetC ...


目錄

簡介

開篇提到過,認證主要解決的是who are you,授權解決的是 are you allowed的問題。各種認證架構可以幫我們知道用戶身份(claims),oauth等架構的scope欄位能夠控制api服務級別的訪問許可權,但是更加細化和多變的功能授權不是它們的處理範圍。

微軟的Authorization項目提供了基於策略的靈活的授權框架。

推薦看下麵博客瞭解,我主要學習和梳理源碼。

https://www.cnblogs.com/RainingNight/p/authorization-in-asp-net-core.html

依賴註入

註入了以下介面,提供了預設實現

  • IAuthorizationService :授權服務,主幹服務
  • IAuthorizationPolicyProvider : 策略提供類
  • IAuthorizationHandlerProvider:處理器提供類
  • IAuthorizationEvaluator:校驗類
  • IAuthorizationHandlerContextFactory:授權上下文工廠
  • IAuthorizationHandler:授權處理器,這個是註入的集合,一個策略可以有多個授權處理器,依次執行
  • 配置類:AuthorizationOptions

微軟的命名風格還是比較一致的
Service:服務
Provider:某類的提供者
Evaluator:校驗預處理類
Factory:工廠
Handler:處理器
Context:上下文

看源碼的過程,不僅可以學習框架背後原理,還可以學習編碼風格和設計模式,還是挺有用處的。

/// <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();
}

配置類 - AuthorizationOptions

  • PolicyMap:策略名稱&策略的字典數據
  • InvokeHandlersAfterFailure: 授權處理器失敗後是否觸發下一個處理器,預設true
  • DefaultPolicy:預設策略,構造了一個RequireAuthenticatedUser策略,即需要認證用戶,不允許匿名訪問。現在有點線索了,為什麼api一加上[Authorize],就會校驗授權。
  • FallbackPolicy:保底策略。沒有任何策略的時候會使用保底策略。感覺有點多此一舉,不是給了個預設策略嗎?
  • AddPolicy:添加策略
  • GetPolicy:獲取策略
/// <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;
    }
}

IAuthorizationService - 授權服務 - 主幹邏輯

介面定義了授權方法,有兩個重載,一個是基於requirements校驗,一個是基於policyName校驗。

Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements);

Task<AuthorizationResult> AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName);

看下預設實現DefaultAuthorizationService的處理,邏輯還是比較簡單

  • 獲取策略
  • 獲取策略的授權條件
  • 獲取授權上下文
  • 獲取處理器集合
  • 處理器依次執行,結果存入上下文
  • 校驗器驗證上下文
  • 返回授權結果類
 /// <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);
    }
}

預設策略 - 需要認證用戶

預設策略添加了校驗條件DenyAnonymousAuthorizationRequirement

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;
    }

授權時序圖

授權項目還是比較好理解的,微軟提供了一個基於策略的授權模型,大部門的具體的業務代碼還是需要自己去實現的。

classDiagram class AuthorizationPolicy{ Requirements } class Requirement{ } class AuthorizationHandler{ } class IAuthorizationHandler{ +HandleAsync(AuthorizationHandlerContext context) } class IAuthorizationRequirement{ } Requirement-->AuthorizationHandler AuthorizationHandler-->IAuthorizationHandler Requirement-->IAuthorizationHandler Requirement-->IAuthorizationRequirement

中間件去哪了?

開發不需要編寫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());
            }
        }
    }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 目錄 "identityserver4源碼解析_1_項目結構" "identityserver4源碼解析_2_元數據介面" "identityserver4源碼解析_3_認證介面" "identityserver4源碼解析_4_令牌發放介面" "identityserver4源碼解析_5_查詢用戶信 ...
  • QRCoder是一個簡單的庫,用C#.NET編寫,可讓您創建QR碼。 它與其他庫沒有任何依賴關係,並且可以在NuGet上以.NET Framework和.NET Core PCL版本獲得。 有關更多信息,請參見:QRCode Wiki | 創作者的博客(英語)| 創作者的博客(德語) QRCo... ...
  • 一直以來都是更新為一些簡單的基礎類型,直到有一天寫了一個覆蓋某一個欄位(這個欄位為數組)的更新操作。出問題了,資料庫中出現了_t,_v……有點懵了。當然如果我們更新的時候設置類型是不會出現這個問題的,出現這種問題的一個前提是我們將數組賦值給了object類型的變數;由於時間關係問了一下同事,她給出了 ...
  • 1.生成的Dockerfile 拿到外層根目錄(ps:生成Dockerfile文件需要選擇linux) 2.控制台進入對應文件夾 3.docker build -t debugbox/api(debugbox/api是給項目起的名)a . (這裡的點代表當前目錄)(loading…… 生成鏡像) 4 ...
  • 分散式部署服務的情況下,由於網路狀況不可預期,消息有可能發送成功,但是消費端消費失敗;也有可能消息根本沒有發出去,如何保證消息是否發送成功是經常遇到的問題。最近有時間研究了一下,具體方法如下圖: 表結構設計如下: 具體思路: 正常流程(網路都正常) 1.消息生產方,將消息信息與業務數據在同一個事務中 ...
  • 目錄 "identityserver4源碼解析_1_項目結構" "identityserver4源碼解析_2_元數據介面" "identityserver4源碼解析_3_認證介面" "identityserver4源碼解析_4_令牌發放介面" "identityserver4源碼解析_5_查詢用戶信 ...
  • 目錄 "identityserver4源碼解析_1_項目結構" "identityserver4源碼解析_2_元數據介面" "identityserver4源碼解析_3_認證介面" "identityserver4源碼解析_4_令牌發放介面" "identityserver4源碼解析_5_查詢用戶信 ...
  • 目錄 "identityserver4源碼解析_1_項目結構" "identityserver4源碼解析_2_元數據介面" "identityserver4源碼解析_3_認證介面" "identityserver4源碼解析_4_令牌發放介面" "identityserver4源碼解析_5_查詢用戶信 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...