基于角色的授权与IdentityServer4

35

我正在尝试使用IdentityServer4实现“基于角色的授权”,根据用户角色来授予访问我的API的权限。

例如,我想为用户设置两个角色:FreeUser和PaidUser,并希望通过使用[Authorize(Roles = "FreeUser")]的授权属性来访问API。 请帮助我如何实现这一点。

我有以下解决方案结构:

  1. IdentityServer
  2. WebApi
  3. Javascript客户端

我已经注册了我的Javascript客户端,如下所示:

 new Client
            {
                ClientId = "js",
                ClientName = "javascript client",
                AllowedGrantTypes = GrantTypes.Implicit,
                AllowAccessTokensViaBrowser= true,
                RedirectUris = {"http://localhost:5004/callback.html"},
                PostLogoutRedirectUris = {"http://localhost:5004/index.html"},
                AllowedCorsOrigins = {"http://localhost:5004"},

                AllowedScopes =
                {
                    StandardScopes.OpenId.Name,
                    StandardScopes.Profile.Name,
                    "api1",
                    "role",
                    StandardScopes.AllClaims.Name
                }
            }

作用域

 return new List<Scope>
        {
            StandardScopes.OpenId,
            StandardScopes.Profile,

            new Scope
            {
                Name = "api1",
                Description = "My API"
            },
           new Scope
           {
               Enabled = true,
               Name  = "role",
               DisplayName = "Role(s)",
               Description = "roles of user",
               Type = ScopeType.Identity,
               Claims = new List<ScopeClaim>
               {
                   new ScopeClaim("role",false)
               }
           },
           StandardScopes.AllClaims
        };

用户

 return new List<InMemoryUser>
        {
            new InMemoryUser
            {
                Subject = "1",
                Username = "alice",
                Password = "password",

                Claims = new List<Claim>
                {
                    new Claim("name", "Alice"),
                    new Claim("website", "https://alice.com"),
                    new Claim("role","FreeUser")
                }
            },
            new InMemoryUser
            {
                Subject = "2",
                Username = "bob",
                Password = "password",

                Claims = new List<Claim>
                {
                    new Claim("name", "Bob"),
                    new Claim("website", "https://bob.com"),
                    new Claim("role","PaidUser")
                }
            }
        };

WebApi 启动文件 Startup.cs

  public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();


        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        app.UseCors("default");
        app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
        {
            Authority = "http://localhost:5000",
            ScopeName = "api1",
            //  AdditionalScopes = new List<string> { "openid","profile", "role" },
            RequireHttpsMetadata = false
        });

        app.UseMvc();
    }

Web Api 控制器

namespace Api.Controllers
{
 [Route("[controller]")]

public class IdentityController : ControllerBase
{
    [HttpGet]
    [Authorize(Roles = "PaidUser")]
    public IActionResult Get()
    {
        return new JsonResult(from c in User.Claims select new { c.Type,    c.Value });
    }

    [Authorize(Roles = "FreeUser")]
    [HttpGet]
    [Route("getfree")]
    public IActionResult GetFreeUser()
    {
        return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
    }
}
}

Javascript客户端 app.js 在这里,我正在尝试通过IdentityServer登录用户并进行API请求。

var mgr = new Oidc.UserManager(config);
mgr.getUser().then(function (user) {
if (user) {
    log("User logged in", user.profile);
} else {
    log("User is not logged in.");
}
});

function login() {
  mgr.signinRedirect();
 }

function api() {
mgr.getUser().then(function (user) {
    var url = "http://localhost:5001/identity/getfree";

    var xhr = new XMLHttpRequest();
    xhr.open("GET", url);
    xhr.onload = function () {
        log(xhr.status, JSON.parse(xhr.responseText));
    };

    xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
    xhr.send();
  });
 }

 function logout() {
   mgr.signoutRedirect();
 }

登录流程正常,我可以成功登录,且在访问令牌中接收到角色。

图片描述

当我点击按钮(调用API)请求API时,出现以下错误... 图片描述


这是一个较旧的问题,但您是否同时使用了 IdentityServer4Asp.Net Core Identity,还是只使用了 IdentityServer4 - WBuck
4个回答

14

鉴于您没有为JavaScript客户端提供配置对象,我假设您已按如下配置范围。

scope:"openid profile api1 role"

我认为你遇到问题的主要原因是你的访问令牌中没有包含角色声明。

按照以下方式将角色声明添加到api1范围,以便在访问令牌中包含它。

             new Scope
                {
                    Name = "api1",
                    DisplayName = "API1 access",
                    Description = "My API",
                    Type = ScopeType.Resource,
                    IncludeAllClaimsForUser = true,
                    Claims = new List<ScopeClaim>
                    {
                        new ScopeClaim(ClaimTypes.Name),
                        new ScopeClaim(ClaimTypes.Role)
                    }
                }
你可以在这里阅读我的答案以获取帮助来调试问题。 使用ASP.NET Identity在Identity Server 4中实现角色 完整的可工作解决方案在此处提供。 https://github.com/weliwita/IdentityServer4.Samples/tree/40844310

太好了,现在它可以正常工作了。我简直不敢相信我怎么会忘记将它添加到作用域中。无论如何,还是谢谢你。 - muhammad waqas
@muhammadwaqas 我认为你应该接受这个答案。 - Learner
3
在2018年的今天,IdentityServer4.AspNetIdentity版本为"2.1.0",Scope类被ApiResource所取代,因此我正在尝试进行一些适应性调整,但无法使其工作。有人能够将其实现在"2.1.0"版本中吗? - Alexsandro

8

new Claim("role","FreeUser")改为new Claim(ClaimTypes.Role, "FreeUser")

或者创建如下策略:

services.AddAuthorization(options =>
{
    options.AddPolicy("FreeUser", policy => policy.RequireClaim("role", "FreeUser"));
});

并使用它:
[Authorize(Policy = "FreeUser")]

谢谢您的回复,我已经尝试了这两种方法,但结果都是一样的。在添加策略时,当我发出API请求时,它会向API发出两个请求,第一个请求返回204 [无内容]状态码,而第二个请求则失败并返回403。在声明方面,结果也是同样的403。我是否漏掉了什么? - muhammad waqas

1
我在这篇文章中写了一个示例。 Identity Server 4:向访问令牌添加声明 我已经测试了角色和声明,同时我可以在客户端Web应用程序和API应用程序中使用[Authorize(Role="SuperAdmin, Admin")]。

0
我在.NET 5中这样使角色起作用:
在Startup.cs中的services.AddAuthentication之前添加JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
我还添加了:
services.AddScoped<IProfileService, ProfileService>();

还有一个名为ProfileService.cs的文件,它的作用是将角色映射到声明:

public sealed class ProfileService : IProfileService
{
    private readonly IUserClaimsPrincipalFactory<ApplicationUser> _userClaimsPrincipalFactory;
    private readonly UserManager<ApplicationUser> _userMgr;
    private readonly RoleManager<IdentityRole> _roleMgr;

    public ProfileService(
        UserManager<ApplicationUser> userMgr,
        RoleManager<IdentityRole> roleMgr,
        IUserClaimsPrincipalFactory<ApplicationUser> userClaimsPrincipalFactory)
    {
        _userMgr = userMgr;
        _roleMgr = roleMgr;
        _userClaimsPrincipalFactory = userClaimsPrincipalFactory;
    }

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    {
        string sub = context.Subject.GetSubjectId();
        ApplicationUser user = await _userMgr.FindByIdAsync(sub);
        ClaimsPrincipal userClaims = await _userClaimsPrincipalFactory.CreateAsync(user);

        List<Claim> claims = userClaims.Claims.ToList();
        claims = claims.Where(claim => context.RequestedClaimTypes.Contains(claim.Type)).ToList();

        if (_userMgr.SupportsUserRole)
        {
            IList<string> roles = await _userMgr.GetRolesAsync(user);
            foreach (var roleName in roles)
            {
                claims.Add(new Claim(JwtClaimTypes.Role, roleName));
                if (_roleMgr.SupportsRoleClaims)
                {
                    IdentityRole role = await _roleMgr.FindByNameAsync(roleName);
                    if (role != null)
                    {
                        claims.AddRange(await _roleMgr.GetClaimsAsync(role));
                    }
                }
            }
        }

        context.IssuedClaims = claims;
    }

    public async Task IsActiveAsync(IsActiveContext context)
    {
        string sub = context.Subject.GetSubjectId();
        ApplicationUser user = await _userMgr.FindByIdAsync(sub);
        context.IsActive = user != null;
    }
}

源代码:

https://ffimnsr.medium.com/adding-identity-roles-to-identity-server-4-in-net-core-3-1-d42b64ff6675


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接