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

Posted

技术标签:

【中文标题】IdentityServer4 基于角色的 Web API 授权与 ASP.NET Core 身份【英文标题】:IdentityServer4 Role Based Authorization for Web API with ASP.NET Core Identity 【发布时间】:2019-05-27 08:13:48 【问题描述】:

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

身份服务器 网络 API

我想保护我的 Web API,我使用邮递员来请求新的令牌,它可以工作并且成功生成了令牌。当我在没有角色的控制器上使用[Authorize] 时,它可以完美运行,但是当我使用[Authorize(Roles="Student")](即使使用[Authorize(Policy="Student")])时,它总是返回403 forbidden

我的代码有什么问题

Web API 启动.cs

  public class Startup
    
        public Startup(IConfiguration configuration)
        
            Configuration = configuration;
        

        public IConfiguration Configuration  get; 

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        
            services.AddMvcCore()
            .AddAuthorization(options => options.AddPolicy("Student", policy => policy.RequireClaim("Role", "Student")))
            .AddAuthorization(options => options.AddPolicy("Teacher", policy => policy.RequireClaim("Role", "Teacher")))
            .AddAuthorization(options => options.AddPolicy("Admin", policy => policy.RequireClaim("Role", "Admin")))
            .AddJsonFormatters();

            services.AddAuthentication("Bearer")
                .AddIdentityServerAuthentication(options =>
                
                    options.Authority = "http://localhost:5000";
                    options.RequireHttpsMetadata = false;
                    options.ApiName = "api1";
                );
        

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        
            if (env.IsDevelopment())
            
                app.UseDeveloperExceptionPage();
            
            app.UseAuthentication();
            app.UseMvc();
        
    

测试 API:

  [Route("api/[controller]")]
    [ApiController]
    [Authorize(Roles="Student")]
    public class ValuesController : ControllerBase
    
        // GET api/values
        [HttpGet]
        public ActionResult<IEnumerable<string>> Get()
        
            return new string[]  "value1", "value2" ;
        

        // GET api/values/5
        [HttpGet("id")]
        public ActionResult<string> Get(int id)
        
            return "value";
        

        // POST api/values
        [HttpPost]
        public void Post([FromBody] string value)
        
        

        // PUT api/values/5
        [HttpPut("id")]
        public void Put(int id, [FromBody] string value)
        
        

        // DELETE api/values/5
        [HttpDelete("id")]
        public void Delete(int id)
        
        
    

IdentityServer 启动.cs

  public class Startup
    

        public IConfiguration Configuration  get; 
        public IHostingEnvironment Environment  get; 

        public Startup(IConfiguration configuration, IHostingEnvironment environment)
        
            Configuration = configuration;
            Environment = environment;
        

        // This method gets called by the runtime. Use this method to add services to the container.
        // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
        public void ConfigureServices(IServiceCollection services)
        
            string connectionString = Configuration.GetConnectionString("DefaultConnection");

            string migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name;


            services.AddDbContext<ApplicationDbContext>(options =>
                             options.UseSqlServer(connectionString));

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

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);


            services.Configure<IISOptions>(iis =>
            
                iis.AuthenticationDisplayName = "Windows";
                iis.AutomaticAuthentication = false;
            );

            IIdentityServerBuilder builder = services.AddIdentityServer(options =>
            
                options.Events.RaiseErrorEvents = true;
                options.Events.RaiseInformationEvents = true;
                options.Events.RaiseFailureEvents = true;
                options.Events.RaiseSuccessEvents = true;
            )
                .AddAspNetIdentity<ApplicationUser>()

                // this adds the config data from DB (clients, resources)
                .AddConfigurationStore(options =>
                
                    options.ConfigureDbContext = b =>
                        b.UseSqlServer(connectionString,
                            sql => sql.MigrationsAssembly(migrationsAssembly));
                )
                // this adds the operational data from DB (codes, tokens, consents)
                .AddOperationalStore(options =>
                
                    options.ConfigureDbContext = b =>
                        b.UseSqlServer(connectionString,
                            sql => sql.MigrationsAssembly(migrationsAssembly));

                    // this enables automatic token cleanup. this is optional.
                    options.EnableTokenCleanup = true;
                    // options.TokenCleanupInterval = 15; // frequency in seconds to cleanup stale grants. 15 is useful during debugging
                )
                .AddProfileService<ProfileService>();

            if (Environment.IsDevelopment())
            
                builder.AddDeveloperSigningCredential();
            
            else
            
                throw new Exception("need to configure key material");
            

            services.AddAuthentication();

        

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        


          //  InitializeDatabase(app);



            if (env.IsDevelopment())
            
                app.UseDeveloperExceptionPage();
            

            app.UseIdentityServer();

            //app.Run(async (context) =>
            //
            //    await context.Response.WriteAsync("Hello World!");
            //);
        



        
    

IdentityServer4 config.cs

   public class Config
    
        // scopes define the resources in your system
        public static IEnumerable<IdentityResource> GetIdentityResources()
        
            return new List<IdentityResource>
            
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
            ;
        

        public static IEnumerable<ApiResource> GetApiResources()
        
            return new List<ApiResource>
            
                new ApiResource("api1", "My API"),
                 new ApiResource("roles", "My Roles"),
                 new IdentityResource("roles", new[]  "role" )
            ;
        

        // clients want to access resources (aka scopes)
        public static IEnumerable<Client> GetClients()
        
            // client credentials client
            return new List<Client>
            
                // resource owner password grant client
                new Client
                
                    ClientId = "ro.client",
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,

                    ClientSecrets =
                    
                        new Secret("secret".Sha256())
                    ,
                    AllowedScopes =  "api1","roles" 
                
            ;
        

    

代币样本

eyJhbGciOiJSUzI1NiIsImtpZCI6ImU0ZjczZDU5MjQ2YjVjMmFjOWVkNDI2ZGU4YzlhNGM2IiwidHlwIjoiSldUIn0.eyJuYmYiOjE1NDYyNTk0NTYsImV4cCI6MTU0NjI2MzA1NiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9yZXNvdXJjZXMiLCJhcGkxIl0sImNsaWVudF9pZCI6InJvLmNsaWVudCIsInN1YiI6IjIiLCJhdXRoX3RpbWUiOjE1NDYyNTk0NTYsImlkcCI6ImxvY2FsIiwic2NvcGUiOlsicm9sZXMiLCJhcGkxIl0sImFtciI6WyJwd2QiXX0.D6OvbrGx2LwrYSySne59VJ _-_ KZ-WriNUbDiETiHO4pknYJzBxKr307DxvBImlvP8w35Cxj3rKxwyWDqVxyhdFhFvFFuHmxqIAv_g2r37lYj3ExcGYAn23Q1i4PuXXBWQe2AHuwFsN2cfPcG39f-N-q7pfLFhoHacXe8vSWyvKxSD0Vj3qVz15cj5VMV1R8qhodXMO-5sZfY1wNfkcJmqmXnbpPnUK_KKUY1Pi6YJkU1nYRXGRoW7YLXc7Y2SFSfa9c1ubU3DDVJV0JqVxSBpfGnvydHEpk-gBx11yQgW5nsJdu6Bi2-DVGA5AdZ_-7pz0AVI-eZPwk2lNtlivmoeA P>

APS.NET_USERS 表

APS.NET_USERS_Claims 表

邮递员

ApiRequest

使用[Authorize]时的声明

【问题讨论】:

【参考方案1】:

问题是声明没有添加到访问令牌

有两个令牌,访问令牌身份令牌

当您想向身份令牌添加声明时,您必须配置IdentityResource。如果您想向 访问令牌 添加声明,则必须配置 ApiResource(或范围)。

这应该会为您解决问题:

public static IEnumerable<ApiResource> GetApiResources()

    return new List<ApiResource>
    
        new ApiResource("api1", "My API"),
        new ApiResource("roles", "My Roles", new[]  "role" )
    ;

确保客户端(邮递员)请求roles 范围。

我确实使用来自 IdentityServer 的 sample code 对其进行了测试。在我的设置中,我已将角色“TestUser”添加到 alice:

new TestUser

    SubjectId = "1",
    Username = "alice",
    Password = "password",
    Claims = new List<Claim>  new Claim("role", "TestUser")  
,

邮递员调用,请注意请求范围:

包含角色声明的访问令牌:

【讨论】:

不抱歉,它不起作用。我添加了问题的 github 链接,请检查【参考方案2】:

在您的 Api 中,在 services.AddAuthentication("Bearer") 之前的某处为 JwtSecurityTokenHandler.InboundClaimTypeMap.Clear(); 添加一行。

更多信息请访问this post。

编辑: 此外,尝试使用roles 身份资源更新您的身份资源配置。

    // scopes define the resources in your system
    public static IEnumerable<IdentityResource> GetIdentityResources()
    
        return new List<IdentityResource>
        
            new IdentityResources.OpenId(),
            new IdentityResources.Profile(),
            new IdentityResource("roles", new[]  "role" )
        ;
    

然后您的客户AllowedScopes 也需要添加roles

    AllowedScopes =  "api1", "roles" 

最后,您的邮递员请求应要求包含roles 范围scope: api1 roles

编辑 2: 此外,更新您的个人资料以在已发布的声明中包含角色:

    public async Task GetProfileDataAsync(ProfileDataRequestContext context)
    
        context.IssuedClaims.AddRange(context.Subject.Claims);

        var user = await _userManager.GetUserAsync(context.Subject);

        var roles = await _userManager.GetRolesAsync(user);

        foreach (var role in roles)
        
            context.IssuedClaims.Add(new Claim(JwtClaimTypes.Role, role));
        
    

以上内容可能应该更新为仅在请求时添加roles 声明。

确保您新发行的 JWT 令牌现在包含 roles 声明,如下所示:

eyJhbGciOiJSUzI1NiIsImtpZCI6ImU0ZjczZDU5MjQ2YjVjMmFjOWVkNDI2ZGU4YzlhNGM2IiwidHlwIjoiSldUIn0.eyJuYmYiOjE1NDY0Mzk0MTIsImV4cCI6MTU0NjQ0MzAxMiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo1MDAwIiwiYXVkIjpbImh0dHA6Ly9sb2NhbGhvc3Q6NTAwMC9yZXNvdXJjZXMiLCJhcGkxIiwicm9sZXMiXSwiY2xpZW50X2lkIjoicm8uY2xpZW50Iiwic3ViIjoiMiIsImF1dGhfdGltZSI6MTU0NjQzOTQxMSwiaWRwIjoibG9jYWwiLCJyb2xlIjpbIkFkbWluIiwiU3R1ZGVudCJdLCJzY29wZSI6WyJvcGVuaWQiLCJhcGkxIiwicm9sZXMiXSwiYW1yIjpbInB3ZCJdfQ.irLmhkyCTQB77hm3XczL4krGMUqAH8izllG7FmQhZIQaYRqI7smLIfrqd6UBDFWTDpD9q0Xx0oefUzjBrwq2XnhGSm83vxlZXaKfb0RdLbYKtC4BlypgTEj8OC-G0ktPqoN1C0lh2_Y2PfKyQYieSRlEXkOHeK6VWfpYKURx6bl33EVDcwe_bxPO1K4axdudtORpZ_4OOkx9b_HvreYaCkuUqzUzrNhYUMl028fPFwjRjMmZTmlDJDPu3Wz-jTaSZ9CHxELG5qIzmpbujCVknh3I0QxRU8bSti2bk7Q139zaiPP2vT5RWAqwnhIeuY9xZb_PnUsjBaxyRVQZ0vTPjQ P>

【讨论】:

谢谢,我添加了JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();,但没有工作... @Oli 好的,您能否分享由 idsrv4 生成的令牌示例(就像您的第一个屏幕截图中的一个,但以纯文本形式)。此外,您能否尝试仅保留您说有效的 [Authorize] 属性,然后在调试器中检查 ClaimsPrincipal 以查看实际存在哪些声明? 啊,好吧,这是有道理的,我想我们已经接近解决这个问题了。您能否将 roles 添加到 Postman 请求中的请求范围内,所以应该是 api1 roles 我添加的角色仍然无法正常工作。更新的代码、令牌和最后一张图片请查看。谢谢 好的,最后不确定的是用户。你能分享你的用户的快照吗?你能分享一下它说你的用户有特定角色的地方吗?【参考方案3】:

我已通过在 ApiClaims 表的 Type 列中添加“role”来解决此问题,请参见下图。

ApiClaims 表中找到的

ApiResourceId 列名称是具有 Id 列名称的 ApiResources 表的主键。

【讨论】:

截至今天,这实际上是所有需要的。您可以将声明类型放在 ApiClaims 表中,使其对 API 的所有范围都是全局的,或者您可以将其添加到 API 的特定范围并在 ApiScopeClaims 表下创建它。

以上是关于IdentityServer4 基于角色的 Web API 授权与 ASP.NET Core 身份的主要内容,如果未能解决你的问题,请参考以下文章

IdentityServer4 基于角色的授权

如何使用 Identity Server 4 (JWT) 进行基于角色的 Web API 授权

Asp.net core IdentityServer4与传统基于角色的权限系统的集成

Asp.Net Core 中IdentityServer4 实战之角色授权详解

Asp.net core IdentityServer4与传统基于角色的权限系统的集成

IdentityServer4 ADFS 外部不返回角色