簡體   English   中英

在 .NET 中使用自定義授權屬性實現基於聲明的動態身份驗證 6

[英]Implementing dynamic claims-based auth with custom authorization attribute in .NET 6

我正在創建一個 .NET 6 應用程序,它連接到外部身份提供者以使用 cookie 身份驗證和 OpenIdConnect 進行身份驗證。 外部提供者返回用戶的 JWT。在這個 JWT 中,有聲明表明用戶應該訪問哪些資源。 這些聲明具有自定義聲明類型“特權”和唯一值(類似於“特權”:“create_blog”)。

我看到您可以添加一項政策來檢查特定聲明,例如:

builder.Services.AddAuthorization(options =>
{
   options.AddPolicy("CreateBlog", policy => policy.RequireClaim("privilege", "create_blog"));
});

但是,在我們的應用程序中,我希望能夠通過屬性指定聲明值(因為我們的系統有數百種不同的權限),例如:

[PrivilegeAuthorize("create_blog")]
public IActionResult CreateBlog { ... }

我嘗試按照文檔中的描述創建自定義授權提供程序和授權處理程序。 這被觸發並執行得很好。 但是,當提供者失敗時(因為用戶未經身份驗證或聲明丟失),它不會觸發正確的響應。 用戶被重定向到 /Account/Login(CookieAuthenticationOptions 的默認登錄路徑)。 但如果用戶未通過身份驗證,我希望將它提供給 OIDC 身份提供者,如果聲明丟失,則顯示我的“拒絕訪問”頁面。

我終於能夠為這個問題開發一個解決方案,該解決方案符合 Microsoft 建議的創建身份驗證策略和自定義授權提供程序的方法。 我創建了一個自定義授權策略提供程序,它使用 Microsoft 提供的AuthorizationOptions.AddPolicy(string, Action<AuthorizationPolicyBuilder>)方法,而不是實例化一個新的 AuthorizationPolicyBuilder 來構建策略。 我使用自定義授權屬性實現了該策略提供者。

自定義授權提供者的內容如下:

public class PrivilegeAuthorizationProvider : IAuthorizationPolicyProvider
{
    private readonly AuthorizationOptions _options;
    private Task<AuthorizationPolicy>? _cachedDefaultPolicy;
    private Task<AuthorizationPolicy?>? _cachedFallbackPolicy;

    public PrivilegeAuthorizationProvider (IOptions<AuthorizationOptions> options)
    {
        this._options = options.Value;
    }

    public Task<AuthorizationPolicy> GetDefaultPolicyAsync()
    {
        if (this._cachedDefaultPolicy == null || this._cachedDefaultPolicy.Result != this._options.DefaultPolicy)
        {
            this._cachedDefaultPolicy = Task.FromResult(this._options.DefaultPolicy);
        }

        return this._cachedDefaultPolicy;
    }

    public Task<AuthorizationPolicy?> GetFallbackPolicyAsync()
    {
        if (this._cachedFallbackPolicy == null || this._cachedFallbackPolicy.Result != this._options.FallbackPolicy)
        {
            this._cachedFallbackPolicy = Task.FromResult(this._options.FallbackPolicy);
        }

        return this._cachedFallbackPolicy;
    }

    public Task<AuthorizationPolicy?> GetPolicyAsync(string policyName)
    {
        AuthorizationPolicy _policy = this._options.GetPolicy(policyName);
        if (_policy != null)
        {
            return Task.FromResult(_policy);
        }

        if (_policy == null && policyName.StartsWith(PrivilegeAuthorizeAttribute.PolicyPrefix, StringComparison.OrdinalIgnoreCase))
        {
            string[] _splitPolicyName = policyName.Split(PrivilegeAuthorizeAttribute.PolicyNameSeparator);

            if (_splitPolicyName.Length == 2)
            {
                string[] _values = _splitPolicyName[1].HasValue()
                    ? _splitPolicyName[1].Split(PrivilegeAuthorizeAttribute.ClaimValuesSeparator)
                    : Array.Empty<string>();

                if (_values.Length > 0)
                {
                    this._options.AddPolicy(policyName, policy => policy.RequireClaim("privilege", _values));
                    _policy = this._options.GetPolicy(policyName);
                }
            }
        }

        return Task.FromResult(_policy);
    }
}

在應用程序的 program.cs(或 startup.cs)中,自定義授權提供程序的注冊方式如下:

_builder.Services.AddSingleton<IAuthorizationPolicyProvider, PrivilegeAuthorizationProvider>();

_builder.Services.AddAuthorization();
_builder.Services.AddAuthentication(opt =>
    {
        opt.DefaultScheme = "Cookies";
        opt.DefaultChallengeScheme = "oidc";
    })
    .AddCookie("Cookies")
    .AddOpenIdConnect("oidc", opt => { ... });

自定義授權屬性如下:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class PrivilegeAuthorizeAttribute : AuthorizeAttribute
{
    internal const char ClaimValuesSeparator = ",";
    internal const char PolicyNameSeparator = ":";
    internal const string PolicyPrefix = "PrivilegeAuthorize";

    public PrivilegeAuthorizeAttribute(string value)
    {
        this.Values = new[] { value };
    }

    public PrivilegeAuthorizeAttribute(params string[] values)
    {
        this.Values = values;
    }

    protected string?[] Values
    {
        get
        {
            if (this.Policy.HasValue())
            {
                string[] _splitPolicy = this.Policy!.Split(PolicyNameSeparator);
                if (_splitPolicy.Length == 2 && _splitPolicy[1].HasValue()
                {
                    return _splitPolicy[1].Split(ClaimValuesSeparator);
                }
            }

            return Array.Empty<string>();
        }
        set
        {
            this.Policy = $"{PolicyPrefix}{PolicyNameSeparator}{string.Join(ClaimValuesSeparator, value)}";
        }
    }
}

然后該屬性可以應用於 controller、操作方法或 razor 頁面,例如:

[PrivilegeAuthorize("MySpecialPrivilegeClaimValue")]
public class Index : PageModel
{
   public void OnGet()
   { }
}

有了這個,應用程序檢查用戶的令牌是否有類型為“特權”的聲明和在屬性參數中指定的值。 如果用戶未通過身份驗證,他們將被重定向到 OIDC 提供商。 如果用戶已通過身份驗證,但聲明不存在,則用戶將被重定向到應用程序的內部“拒絕訪問”頁面。 如果用戶通過身份驗證並擁有適當的聲明,他們就可以看到所需的頁面。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM