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 身份的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 Identity Server 4 (JWT) 进行基于角色的 Web API 授权
Asp.net core IdentityServer4与传统基于角色的权限系统的集成
Asp.Net Core 中IdentityServer4 实战之角色授权详解