C# 如何在identityserver中基于客户端保护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的内部表上执

我有自己的授权服务器,构建在topidentityserver4上,我希望在这里保护主机上的所有API。该系统只是google开发者或facebook开发者的简单模仿,应用程序所有者注册并获取客户端id和客户端机密,以便在API上获得访问许可

因此,我在identityserver4示例上遵循了客户端凭据流。一切正常。我为应用程序所有者构建了一个公共UI,用于创建应用程序并选择从应用程序访问哪些API。我使用
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参数为空。这是我指出的。