.NET Core 反射獲取所有控制器及方法上特定標籤

、天上有木月OvO發表於2020-05-28

.NET Core 反射獲取所有控制器及方法上特定標籤

有個需求,就是在. NET Core中,我們想在專案 啟動時,獲取LinCmsAuthorizeAttribute這個特性標籤所有出現的地方,把他的引數,放入一個集合並快取起來,以便後面使用此資料用於許可權驗證。

我們通過反射獲取所有控制器下及方法的Attribute。

LinCmsAuthorizeAttribute是什麼

其程式碼非常簡單,用於自定義許可權驗證,通過重寫OnAuthorizationAsync方法,實現固定許可權可分配給動態角色(也能分配給動態使用者)。主要就基於許可權的授權的實現進行研究,實現方法級別的許可權驗證。

當然,這個只是部分程式碼,完整程式碼請檢視最下方開源地址,其中LinCmsAuthorizeAttribute繼承AuthorizeAttribute,擁有指定角色許可權控制,當Permission未指定時,當過濾器與Authorize功能相同。Module是指模組,即多個許可權,屬於同一個模組,方便前臺展示為樹型結構。Permission屬性的值不可重複。

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = true)]
public class LinCmsAuthorizeAttribute : AuthorizeAttribute, IAsyncAuthorizationFilter
{
    public string Permission { get; set; }
    public string Module { get; set; }

    public LinCmsAuthorizeAttribute()
    {

    }

    public LinCmsAuthorizeAttribute(string permission,string module)
    {
        Permission = permission;
        Module = module;
    }

    public LinCmsAuthorizeAttribute(string permission,string module, string policy) : base(policy)
    {
        Permission = permission;
        Module = module;
    }

    public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
    {
        if (Permission == null) return;
        var authorizationService = (IAuthorizationService)context.HttpContext.RequestServices.GetService(typeof(IAuthorizationService));
        var authorizationResult = await authorizationService.AuthorizeAsync(context.HttpContext.User, null, new OperationAuthorizationRequirement() { Name = Permission });
        if (!authorizationResult.Succeeded)
        {
            context.Result = new ForbidResult();
        }
    }

    public override string ToString()
    {
        return $"\"{base.ToString()}\",\"Permission:{Permission}\",\"Module:{Module}\",\"Roles:{Roles}\",\"Policy:{Policy}\",\"AuthenticationSchemes:{AuthenticationSchemes}\"";
    }
}

Controller

在 LinCms.Web中的Controller,至於為什麼Permission為中文,目前的主要原因,此專案用於適配 Lin-CMS-VUE專案,所以於平常我們以某個字串作為許可權名不同,但不須大精小怪,道理相同。

[Route("cms/log")]
[ApiController]
public class LogController : ControllerBase
{
    private readonly ILogService _logService;

    public LogController(ILogService logService)
    {
        _logService = logService;
    }

    [HttpGet("users")]
    [LinCmsAuthorize("查詢日誌記錄的使用者", "日誌")]
    public List<string> GetLoggedUsers([FromQuery]PageDto pageDto)
    {
        return _logService.GetLoggedUsers(pageDto);
    }

 
    [HttpGet]
    [LinCmsAuthorize("查詢所有日誌", "日誌")]
    public PagedResultDto<LinLog> GetLogs([FromQuery]LogSearchDto searchDto)
    {
        return _logService.GetLogUsers(searchDto);
    }

    [HttpGet("search")]
    [LinCmsAuthorize("搜尋日誌", "日誌")]
    public PagedResultDto<LinLog> SearchLogs([FromQuery]LogSearchDto searchDto)
    {
        return _logService.GetLogUsers(searchDto);
    }
}

測試類獲取方法上的特定標籤

in xunit test 專案工程中,開始我們的測試

[Fact]
public void GetAssemblyMethodsAttributes()
{
    var assembly = typeof(Startup).Assembly.GetTypes().AsEnumerable()
        .Where(type => typeof(ControllerBase).IsAssignableFrom(type)).ToList();

    assembly.ForEach(r =>
    {
        foreach (var methodInfo in r.GetMethods())
        {
            foreach (Attribute attribute in methodInfo.GetCustomAttributes())
            {
                if (attribute is LinCmsAuthorizeAttribute linCmsAuthorize)
                {
                    _testOutputHelper.WriteLine(linCmsAuthorize.ToString());
                }
            }
        }
    });
}    

方法結果

可在輸出文字中檢視,正是我們想要的東西,最後一行,是其他Controller中的內容,而且我們重寫了ToString(),所以我們能看到其屬性。

"LinCms.Zero.Authorization.LinCmsAuthorizeAttribute","Permission:查詢日誌記錄的使用者","Module:日誌","Roles:","Policy:","AuthenticationSchemes:"
"LinCms.Zero.Authorization.LinCmsAuthorizeAttribute","Permission:查詢所有日誌","Module:日誌","Roles:","Policy:","AuthenticationSchemes:"
"LinCms.Zero.Authorization.LinCmsAuthorizeAttribute","Permission:搜尋日誌","Module:日誌","Roles:","Policy:","AuthenticationSchemes:"
"LinCms.Zero.Authorization.LinCmsAuthorizeAttribute","Permission:檢視lin的資訊","Module:資訊","Roles:","Policy:","AuthenticationSchemes:"

獲取控制器上特性標籤

/// <summary>
/// 獲取控制器上的LinCmsAuthorizeAttribute
/// </summary>
/// "LinCms.Zero.Authorization.LinCmsAuthorizeAttribute","Permission:","Module:","Roles:Administrator","Policy:","AuthenticationSchemes:"
[Fact]
public void GetControllerAttributes()
{
    var assembly = typeof(Startup).Assembly.GetTypes().AsEnumerable()
        .Where(type => typeof(ControllerBase).IsAssignableFrom(type)).ToList();

    assembly.ForEach(d =>
    {
        var linCmsAuthorize = d.GetCustomAttribute<LinCmsAuthorizeAttribute>();
        if (linCmsAuthorize != null)
        {
            _testOutputHelper.WriteLine(linCmsAuthorize.ToString());
        }
    });
}

Controller結果

只有AdminController加了此標籤,所以只有一行。

"LinCms.Zero.Authorization.LinCmsAuthorizeAttribute","Permission:","Module:","Roles:Administrator","Policy:","AuthenticationSchemes:"

此時Roles為Administrator,Permission及Module都是null,
這是因為只有AdminController中加了LinGroup.Administrator="Administrator"字串,在登入過程中,已經給當前登入使用者設定了 new Claim(ClaimTypes.Role,user.IsAdmin()?LinGroup.Administrator:user.GroupId.ToString()),即"Administrator,當使用者訪問AdminController中的方法時,LinCmsAuthorize並沒有做相關驗證,都是AuthorizeAttribute,實現了固定角色許可權的判斷及登入的判斷。LinCmsAuthorize完成了固定許可權設定為不同的動態角色後,判斷使用者是否擁有此許可權。

[LinCmsAuthorize(Roles = LinGroup.Administrator)]
public class AdminController : ControllerBase
{
    ...
}

參考

開源地址

相關文章