C# 如何在identityserver中基于客户端保护api资源上的端点
我有自己的授权服务器,构建在topidentityserver4上,我希望在这里保护主机上的所有API。该系统只是google开发者或facebook开发者的简单模仿,应用程序所有者注册并获取客户端id和客户端机密,以便在API上获得访问许可 因此,我在identityserver4示例上遵循了客户端凭据流。一切正常。我为应用程序所有者构建了一个公共UI,用于创建应用程序并选择从应用程序访问哪些API。我使用C# 如何在identityserver中基于客户端保护api资源上的端点,c#,identityserver4,asp.net-core-3.0,C#,Identityserver4,Asp.net Core 3.0,我有自己的授权服务器,构建在topidentityserver4上,我希望在这里保护主机上的所有API。该系统只是google开发者或facebook开发者的简单模仿,应用程序所有者注册并获取客户端id和客户端机密,以便在API上获得访问许可 因此,我在identityserver4示例上遵循了客户端凭据流。一切正常。我为应用程序所有者构建了一个公共UI,用于创建应用程序并选择从应用程序访问哪些API。我使用IConfigurationDbContext在identityserver的内部表上执
IConfigurationDbContext
在identityserver的内部表上执行CRUD过程
问题是,我找不到一种基于应用程序所有者选择的保护API的方法,当开发人员包装一个应用程序并选择几个逻辑端点进行访问时,它们仍然可以访问所有enpoint。我所做的工作如下:
new ApiResource("commonserviceapi", "Common Service API")
{
Scopes = {
new Scope("calender_api", "Calender Api"),
new Scope("employee_api", "Employee Api"),
new Scope("organization_api", "Organization Api"),
}
}
授权服务器启动
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryCaching()
.AddOperationalStore(storeOpitons =>
{
storeOpitons.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("Default"),
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddConfigurationStore(storeOptions =>
{
storeOptions.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("Default"),
sql => sql.MigrationsAssembly(migrationsAssembly));
});
services.AddAuthentication("Bearer").AddJwtBearer(opt => {
opt.Authority = "http://localhost:5000";
opt.Audience = "employee_api";
opt.RequireHttpsMetadata = false;
});
services.AddAuthentication("Bearer").AddJwtBearer(opt =>
{
opt.Authority = "http://localhost:5000";
opt.Audience = "commonserviceapi";
opt.RequireHttpsMetadata = false;
});
services.AddAuthorization(options =>
{
options.AddPolicy("ApiEmployee", builder =>
{
builder.RequireScope("employee_api");
});
options.AddPolicy("ApiOrganization", builder =>
{
builder.RequireScope("organization_api");
});
});
保存客户端方法
public IActionResult SaveApp(ClientViewModel model, List<SelectedApi> selectedApis)
{
//ommited for brevity
Client client = new Client
{
Description = model.Description,
ClientName = model.Name,
RedirectUris = new[] { model.CallBackUri }
};
client.AllowedScopes = selectedApis.Where(a => a.apiValue == "true").Select(a => a.apiName).ToList();
//e.g : client.AllowedScopes = {"employee_api"};
_isRepository.SaveClient(client, userApp);
}
Api样本控制器
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : BaseController
{
private readonly IEmployeeRepository _employeeRepoistory;
public EmployeeController(IServiceProvider provider, IEmployeeRepository employeeRepository) : base(provider)
{
_employeeRepoistory = employeeRepository;
}
[HttpGet]
public IActionResult GetEmployees([FromQuery] EmployeeResourceParameter parameter)
{
return Ok(_mapper.Map<IEnumerable<EmployeeModel>>(_employeeRepoistory.GetAll(parameter)));
}
[HttpGet("{id:int}")]
public IActionResult GetEmployeeById(int id)
{
var emp = _employeeRepoistory.GetById(id);
return Ok(_mapper.Map<EmployeeModel>(emp));
}
}
[授权]
[路由(“api/[控制器]”)]
[ApiController]
公共类EmployeeController:BaseController
{
私人只读雇员档案(employeerepository);;
公共EmployeeController(IServiceProvider提供程序、IEEmployeeRepository employeeRepository):基本(提供程序)
{
_employeeRepository=employeeRepository;
}
[HttpGet]
public IActionResult GetEmployees([FromQuery]EmployeeResourceParameter参数)
{
返回Ok(_mapper.Map(_employeerepostory.GetAll(参数));
}
[HttpGet(“{id:int}”)]
公共IActionResult GetEmployeeById(内部id)
{
var emp=_employeerepostory.GetById(id);
返回Ok(_mapper.Map(emp));
}
}
我想要的是,如果开发人员选择employee_api,他们应该只到达EmployeeController的端点。但是现在,他们可以访问所有API,无论他们选择什么。
在api端或身份验证服务器端,需要采取哪些步骤
我终于完成了。。首先,我意识到掌握
apirource->Scopes
,客户机->允许的Scopes
之间的关系很重要。我建议您阅读和中有关它们的部分
当客户机注册到identityserver,然后选择api端点(例如:组织、员工、日历)时,它们应该注册为客户机的allowedScopes(它们位于ClientScopes表中),我这样做是正确的。我做错的是,我假设所有这些作用域都是ApiResources(就我而言,因为我所有的api都位于同一个主机上,我称之为CommonServiceApi,只是一个web api应用程序)。我重新定义了我的ApiResources及其范围,如下所示
new ApiResource("commonserviceapi", "Common Service API")
{
Scopes = {
new Scope("calender_api", "Calender Api"),
new Scope("employee_api", "Employee Api"),
new Scope("organization_api", "Organization Api"),
}
}
在api方面,应该使用所示的策略对端点进行授权。
在访问令牌中,请求客户端的允许范围被传递给api应用程序,因此api根据这些值授予访问权限
因此Api启动
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryCaching()
.AddOperationalStore(storeOpitons =>
{
storeOpitons.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("Default"),
sql => sql.MigrationsAssembly(migrationsAssembly));
})
.AddConfigurationStore(storeOptions =>
{
storeOptions.ConfigureDbContext = builder =>
builder.UseSqlServer(Configuration.GetConnectionString("Default"),
sql => sql.MigrationsAssembly(migrationsAssembly));
});
services.AddAuthentication("Bearer").AddJwtBearer(opt => {
opt.Authority = "http://localhost:5000";
opt.Audience = "employee_api";
opt.RequireHttpsMetadata = false;
});
services.AddAuthentication("Bearer").AddJwtBearer(opt =>
{
opt.Authority = "http://localhost:5000";
opt.Audience = "commonserviceapi";
opt.RequireHttpsMetadata = false;
});
services.AddAuthorization(options =>
{
options.AddPolicy("ApiEmployee", builder =>
{
builder.RequireScope("employee_api");
});
options.AddPolicy("ApiOrganization", builder =>
{
builder.RequireScope("organization_api");
});
});
和Api控制器
[Authorize(Policy = "ApiEmployee")]
[Route("api/[controller]")]
[ApiController]
public class EmployeeController : BaseController
{
...
RequireScope
是IdentityServer4.AccessTokenValidation
包的扩展方法。您应该将此包包含到api项目中
最后,这对我来说是一个令人困惑的问题;从服务器请求访问令牌时,作用域参数应为空,因为identityserver从客户端的allowdScopes值获取该参数。几乎所有样本都填写了此字段,因此您认为应该填写。最后一句话不正确。根据规范,scope参数可能为空,但也允许它请求特定的作用域。它不必是空的。不知道你为什么认为应该这样。文档中令人困惑的是,它们使用了一个同名的资源/范围。确保使用的是作用域名称,而不是资源名称。作用域名称用于确定访问群体(资源名称),而每个请求的作用域被添加为声明
scope=employee\u api
。请看更多信息。我希望我能在之前找到你的答案,解释得很好。我并不是说你的scope参数应该总是空的,只是在这种情况下,因为这个allowedscope动态地来自客户端允许的scope,所以你必须让这个scope参数为空。这是我指出的。