使用 IdentityServer4 对 Web API 进行基于角色的授权

Posted

技术标签:

【中文标题】使用 IdentityServer4 对 Web API 进行基于角色的授权【英文标题】:Role Based Authorization for Web API with IdentityServer4 【发布时间】:2018-05-10 05:32:14 【问题描述】:

我将 IdentityServer4 (v2.2.1) 与 .Net Core 2.0 和 Asp.Net Core Identity 一起使用。 我的解决方案中有三个项目。

    身份服务器 网络 API MVC Web 应用程序

我正在尝试在我的 Web API 上实现基于角色的授权,以便任何客户端将访问令牌传递给 Web API 以访问资源。

目前我可以在 MVC 应用程序控制器上实现基于角色的授权,但我无法为 WEB API 控制器传递/配置相同的权限。

以下是身份服务器文件: 配置文件

public static IEnumerable<ApiResource> GetApiResources()
    
        return new List<ApiResource>
        
            //SCOPE - Resource to be protected by IDS 
            new ApiResource("TCSAPI", "TCS API")
            
                UserClaims =  "role" 
            
        ;
    


 public static IEnumerable<Client> GetClients()
    
        return new List<Client>
        
new Client
            
                ClientId = "TCSIdentity",
                ClientName = "TCS Mvc Client Application .",
                AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
                RequireConsent = false,
                ClientSecrets =
                
                    new Secret("secret".Sha256())
                ,

                RedirectUris =  "http://localhost:5002/signin-oidc" ,
                PostLogoutRedirectUris =  "http://localhost:5002/signout-callback-oidc" ,

                AlwaysSendClientClaims= true,
                AlwaysIncludeUserClaimsInIdToken = true,

                AllowedScopes =
                
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Email,
                    IdentityServerConstants.StandardScopes.Profile,
                    IdentityServerConstants.StandardScopes.OfflineAccess,
                    "TCSAPI",
                    "office",
                    "role",
                ,
                AllowOfflineAccess = true
            
  ;
    

public static IEnumerable<IdentityResource> GetIdentityResources()
    
        return new IdentityResource[]
        
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResources.Email(),
            new IdentityResource
            
                Name = "role",
                DisplayName="User Role",
                Description="The application can see your role.",
                UserClaims = new[]JwtClaimTypes.Role,ClaimTypes.Role,
                ShowInDiscoveryDocument = true,
                Required=true,
                Emphasize = true
            
        ;
    

Startup.cs

 public void ConfigureServices(IServiceCollection services)
    
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        // Add application services.
        services.AddTransient<IEmailSender, EmailSender>();

        services.AddMvc();

        // configure identity server with in-memory stores, keys, clients and scopes
        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddInMemoryPersistedGrants()
            .AddInMemoryIdentityResources(Config.GetIdentityResources())
            .AddInMemoryApiResources(Config.GetApiResources())
            .AddInMemoryClients(Config.GetClients())
            .AddAspNetIdentity<ApplicationUser>();
    

MVC WEB APP(Roles Base Authorization 适用于 MVC WEB APP):

RoleClaimAction.cs 使用此文件将角色添加到身份。

internal class RoleClaimAction : ClaimAction

    public RoleClaimAction()
        : base("role", ClaimValueTypes.String)
    
    

    public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
    
        var tokens = userData.SelectTokens("role");
        IEnumerable<string> roles;

        foreach (var token in tokens)
        
            if (token is JArray)
            
                var jarray = token as JArray;
                roles = jarray.Values<string>();
            
            else
                roles = new string[]  token.Value<string>() ;

            foreach (var role in roles)
            
                Claim claim = new Claim("role", role, ValueType, issuer);
                if (!identity.HasClaim(c => c.Subject == claim.Subject
                                         && c.Value == claim.Value))
                
                    identity.AddClaim(claim);
                
            
        
    
 

MVC WEB APP/Startup.cs

 public void ConfigureServices(IServiceCollection services)
    
        services.AddMvc();

        JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
        services.AddCors();
        services.AddAuthentication(options =>
        
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        )
            .AddCookie("Cookies")
            .AddOpenIdConnect("oidc", options =>
            
                options.SignInScheme = "Cookies";
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;

                options.ClientId = "TCSIdentity";

                //HYBRID FLOW
                options.ClientSecret = "secret";

                options.ClaimActions.Add(new RoleClaimAction()); // <-- 

                options.ResponseType = "code id_token token";
                options.GetClaimsFromUserInfoEndpoint = true;
                options.Scope.Add("TCSAPI");
                options.Scope.Add("offline_access");
                //END HYBRID FLOW
                options.SaveTokens = true;
                options.Scope.Add("role");

                options.TokenValidationParameters.NameClaimType = "name";
                options.TokenValidationParameters.RoleClaimType = "role";
            );

    

MVC WEB APP/HomeController.cs 此操作方法适用于角色基础授权,但是当我尝试将令牌传递给 Web Api 以使用角色基础授权访问任何内容时,它无法授权。例如 var content = await client.GetStringAsync("http://localhost:5001/user");

[Authorize(Roles = "User")]
    [Route("user")]
    public async Task<IActionResult> UserAccess()
    

        var tokenClient = new TokenClient("http://localhost:5000/connect/token", "RoleApi", "secret");
        var tokenResponse = await tokenClient.RequestClientCredentialsAsync("TCSAPI");

        var client = new HttpClient();
        client.SetBearerToken(tokenResponse.AccessToken);
        var content = await client.GetStringAsync("http://localhost:5001/user");

        ViewBag.Json = JArray.Parse(content).ToString();
        return View("json");
    
    [Authorize(Roles = "Admin")]
    [Route("admin")]
    public async Task<IActionResult> AdminAccess()
    
        var accessToken = await HttpContext.GetTokenAsync("id_token");

        var client = new HttpClient();
        client.SetBearerToken(accessToken);
        var content = await client.GetStringAsync("http://localhost:5001/admin");
        ViewBag.Json = JArray.Parse(content).ToString();
        return View("json");
    

WEBAPI/Startup.cs

 public void ConfigureServices(IServiceCollection services)
    
        services.AddMvcCore()
            .AddAuthorization()
            .AddJsonFormatters();
        services.AddAuthentication("Bearer")
            .AddIdentityServerAuthentication(options =>
            
                options.Authority = "http://localhost:5000";
                options.RequireHttpsMetadata = false;
                options.ApiName = "TCSAPI";
            );

        services.AddCors(options =>
        
            options.AddPolicy("default", policy =>
            
                policy.WithOrigins("http://localhost:5002")
                .AllowAnyHeader()
                .AllowAnyMethod();
            );
        );
    

WEB API/TestController.cs

[Route("admin")]
    [Authorize(Roles = "Admin")]
    public IActionResult AdminAccess()
    
        return new JsonResult(from c in User.Claims select new  c.Type, c.Value );
    
    [Route("user")]
    [Authorize(Roles = "User")]
    public IActionResult UserAccess()
    
        return new JsonResult(from c in User.Claims select new  c.Type, c.Value );
    
    [AllowAnonymous]
    [Route("public")]
    public IActionResult PublicAccess()
    
        return new JsonResult(from c in User.Claims select new  c.Type, c.Value );
    

【问题讨论】:

你是怎么解决这个问题的?如果您这样做,请提供信息。谢谢 我添加了一个 Menu Tabble 和一个 MenuPermissions 表。在 UserProfileService 中,我从数据库中读取用户权限并将每个权限添加为声明。在我的控制器操作中,我将声明用作许可。这样现在我可以控制控制器和单个操作方法的权限。 @你能提供最终代码吗? 无法确认。我会检查我是否可以提取所需的代码而不是我将分享的代码。 【参考方案1】:

您的代码不完全是基于策略的授权。你的看起来像 .NET Framework 基于角色的授权。

对于Policy Based Authorization,你需要做以下事情:

1.在您的 Web API 项目的 Startup.cs 中,您需要添加如下内容:

// more code
.AddMvcCore()
            .AddAuthorization(options =>
            
                options.AddPolicy("Policy1",
                    policy => policy.Requirements.Add(new Policy1Requirement()));
                options.AddPolicy("Policy2",
                    policy => policy.Requirements.Add(new Policy2Requirement()));
                .
                .
                .
                .
            )
 // more code

2. 那么你需要为每个Policy(X)Requirement 设置一个类:

public class Policy1Requirement : AuthorizationHandler<Policy1Requirement>, IAuthorizationRequirement

    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AdminUserRequirement requirement)
    
        if (!context.User.HasClaim(c => c.Type == "role" && c.Value == "<YOUR_ROLE_FOR_THIS_POLICY>"))
        
            context.Fail();
        
        else
        
            context.Succeed(requirement);
        
        return Task.FromResult(0);
    

3.最后,在您应用政策的地方,您需要:

[Authorize(Policy = "Policy1")]
public class MyController : Controller

.
.    

祝你好运!

PS:

名称Policy(X)Policy(X)Requirement 仅用于说明。你可以使用任何你想要的名字,只要你实现正确的接口IAuthorizationRequirement,并继承类AuthorizationHandler

【讨论】:

以上是关于使用 IdentityServer4 对 Web API 进行基于角色的授权的主要内容,如果未能解决你的问题,请参考以下文章

自己发行 JWT 令牌与使用 IdentityServer4(OIDC) 进行 Web API

IdentityServer4 + SignalR Core +RabbitMQ 构建web即时通讯

IdentityServer4 基于角色的 Web API 授权与 ASP.NET Core 身份

未包含在 JWT 中且未发送到 Web Api 的 IdentityServer4 用户声明

使用 IdentityServer4.AccessTokenValidation 包向 IdentityServer3 授权 .NET 5 Web API 引用令牌时遇到问题

是否可以在多个服务器上重复使用访问令牌 - IdentityServer4