.Net Core 2.1とAsp.Net Core IdentityでIdentityServer4を使用しています。私のソリューションには2つのプロジェクトがあります。
Web APIを保護したいのですが、postmanを使用して新しいトークンをリクエストします。それが機能し、トークンが正常に生成されます。役割なしのコントローラーで_[Authorize]
_を使用すると完全に機能しますが、[Authorize(Roles="Student")]
を使用すると([Authorize(Policy="Student")]
を使用しても)、常に_403 forbidden
_が返されます
私のコードの何が悪い
Web API startup.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 startup.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
APS.NET_USERSテーブル
APS.NET_USERS_Claimsテーブル
_[Authorize]
_使用中のクレーム
APIのservices.AddAuthentication("Bearer")
の前のどこかにJwtSecurityTokenHandler.InboundClaimTypeMap.Clear();
の行を追加します。
詳細は この投稿 をご覧ください。
EDIT:さらに、roles
IDリソースを使用してIDリソース構成を更新してみてください。
// 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
。
EDIT 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
問題は、クレームがアクセストークンに追加されないことです。
アクセストークンと識別トークンの2つのトークンがあります。
identityトークンにクレームを追加する場合は、IdentityResourceを構成する必要があります。 アクセストークンにクレームを追加する場合は、ApiResource(またはスコープ)を構成する必要があります。
これはあなたのためにそれを修正するはずです:
public static IEnumerable<ApiResource> GetApiResources()
{
return new List<ApiResource>
{
new ApiResource("api1", "My API"),
new ApiResource("roles", "My Roles", new[] { "role" })
};
}
クライアント(postman)がroles
スコープをリクエストしていることを確認してください。
IdentityServerの サンプルコード でテストしました。私のセットアップでは、「TestUser」ロールをaliceに追加しました。
new TestUser
{
SubjectId = "1",
Username = "alice",
Password = "password",
Claims = new List<Claim> { new Claim("role", "TestUser") }
},
郵便配達の呼び出し、要求されたスコープに注意してください:
役割の要求を含むアクセストークン: