ASP.NET Core 6 身分驗證 JWT 登入時自動賦予角色和權限

在 ASP.NET Core 實作角色權限授權後,能夠在 Controller 或是 Action 上使用 Authorize 註解(Attribute) 綁定角色(Roles)或權限(Policy,應該翻譯為策略,但筆者覺得翻譯為權限似乎比較容易理解)
    
[Authorize(Roles = "Admin")] // 指定角色為 Admin 才可存取
[Authorize(Policy = "ProductEdit")] // 指定具有 ProductEdit 權限才可存取
public IActionResult Test1(){
	// 省略
}
    

若使用 API JWT 登入,需要在 JWT 內包含權限和策略才可以通過 Authorize 的驗證

JWT 內容範例:
    
{
  "sub": "1234567890",
  "name": "Ruyut",
  "iat": 1516239022,
  "exp": 1516239122,
  "roles": [
    "Admin",
    "User"
  ],
  "policies": [
    "ProductEdit"
  ]
}
    

若權限數量眾多,則 JWT 就需要夾帶很多資料,且權限內容都被看光光(雖然 JWT 需要發行者簽章),那有沒有更好的方式?

有的,在筆者研究很久,使用中介軟體(Middleware) 怎麼都失敗之後,最後終於嘗試出來了,就是在 JwtBearerEvents 的 OnTokenValidated 事件中動態賦予角色和權限!

註:筆者在測試時發現如果「預設授權策略」(在每個 Controller 或 Action 上使用 AuthorizeAttribute 指定不會有問題)使用 IdentityConstants.ApplicationScheme 和 JwtBearerDefaults.AuthenticationScheme 等多個時,下面的方式會失效,目前還未能解決,如果有解決的方式,還請留言指點,感激不盡!

    
builder.Services
    .AddAuthentication(options => options.DefaultScheme = IdentityConstants.ApplicationScheme)
    .AddJwtBearer(options =>
    {
        options.Events = new JwtBearerEvents()
        {
            OnTokenValidated = async (context) =>
            {
                var userName = context.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
                var userManager = context.HttpContext.RequestServices.GetService<UserManager<User>>();
                var roleManager = context.HttpContext.RequestServices.GetService<RoleManager<Role>>();

                var user = await userManager.FindByNameAsync(userName);

                if (user == null) return;

                var roles = await userManager.GetRolesAsync(user);
                foreach (var role in roles)
                {
                    ((ClaimsIdentity)context.Principal.Identity).AddClaim(new Claim(ClaimTypes.Role, role));

                    if (roleManager != null)
                    {
                        var roleObj = await roleManager.FindByNameAsync(role);
                        var permissions = await roleManager.GetClaimsAsync(roleObj);
                        foreach (var permission in permissions)
                        {
                            ((ClaimsIdentity)context.Principal.Identity).AddClaim(permission);
                        }
                    }
                }
            },
        };
    });

    

留言