使用 IdentityServer 和 C# 保护 API

Posted

技术标签:

【中文标题】使用 IdentityServer 和 C# 保护 API【英文标题】:Secure API with IdentityServer and C# 【发布时间】:2021-11-23 11:54:44 【问题描述】:

我的 ASP.NET Core (.NET5) 项目带有 API 控制器。我想使用 Identity Server 保护我的 API。我的目标是让一些客户能够访问基于client_idclient_secret 的API,并在此基础上定义他们可以调用的API。为此,我在 Startup.cs 中添加了以下代码

public void ConfigureServices(IServiceCollection services)

    // ...
    services.AddAuthentication(
        IdentityServerAuthenticationDefaults.AuthenticationScheme)
        .AddIdentityServerAuthentication(options =>
        
            options.Authority = apiSettings.Authority;
            options.ApiName = apiSettings.ApiName;
            options.ApiSecret = apiSettings.ApiSecret;
        );

所以,我在每个控制器中添加了[Authorize] 属性。现在,我想使用 HttpClient 从控制台应用程序或 Web 应用程序调用此 API。

private static async Task<string> GetAccessToken()

    using (var client = new HttpClient())
    
        client.BaseAddress = new Uri(baseUrl);

        // We want the response to be JSON.
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));

        // Build up the data to POST.
        List<KeyValuePair<string, string>> postData = 
            new List<KeyValuePair<string, string>>();
        postData.Add(new KeyValuePair<string, string>("scope", "myscope"));
        postData.Add(new KeyValuePair<string, string>("client_id", clientId));
        postData.Add(new KeyValuePair<string, string>("client_secret", 
            clientSecret));

        FormUrlEncodedContent content = new FormUrlEncodedContent(postData);

        // Post to the Server and parse the response.
        HttpResponseMessage response = await 
            client.PostAsync("/api/v1/Test", content);
        string jsonString = await response.Content.ReadAsStringAsync();
        object responseData = JsonConvert.DeserializeObject(jsonString);

        // return the Access Token.
        return ((dynamic)responseData).data;
    

调用总是返回 401Unauthorized。使用client_idclient_secret 调用API 的正确方法是什么? scope 是必需的吗?

更新

为了澄清,我认为是机器对机器的场景。因此,没有用户参与。当HttpClient调用API时,它必须通过认证。在机器对机器的场景中,我想检查请求是否具有特定的范围,例如 api_readapi_writeapi_full 和基于调用是否可以访问某个函数。

例如,在 Blazor 中,我创建了一个函数来传递 scope,但不传递 clinet_idclient_secret

public class MyAuthorizationMessageHandler : 
       AuthorizationMessageHandler

    public MyAuthorizationMessageHandler(IAccessTokenProvider provider, 
        NavigationManager navigation, IConfiguration configuration) :
        base(provider, navigation)
    
        string apiEndpoint = configuration["Api:EndpointsUrl"];
        string apiScope = configuration["Api:Scope"];

        ConfigureHandler(authorizedUrls: new[]  apiEndpoint ,
            scopes: new[]  apiScope );
    

【问题讨论】:

您从哪里获得客户端 ID 和客户端密码?那些是在 IdentityServer 的数据库中注册的吗?日志对 IdentityServer 应用程序上的 401 有什么说明?此外,据我记得,您不能只在任何地方添加 client_id 和 client_secret,而且您的请求甚至没有身份验证类型(指定客户端凭据)。您应该改用 IdentityServer 客户端库。 【参考方案1】:

如果您想对某些操作进行访问控制,我建议您使用策略/角色。

Authentication 不应该限制控制,它应该检查 与您合作(并且 who 我的意思是具体实体,带有标识符,而不是他的头衔,例如“经理”、“管理员”等,可以从实体中添加/删除)。重要的是要提到身份验证不会阻止用户登录,如果他没有某些角色 - 检查用户是否正确,就是这样,它不应该禁止他们停止服务,授权会为你做。

授权另一方面,特别是 ASP NET 策略/角色的使用是关于检查“权限”/“标题”。或者完全禁止某人。

对于我不建议使用的权限/策略,但如果您希望在服务中使用动态角色,例如在数据库中自己指定它们,它会很有用:

//you decide which functionality should be restricted
[Authorize(Policy="CanAddStuff")]
public IActionResult AddSomeStuff()...

[Authorize(Policy="CanGetStuff")]
public IActionResult GetSomeStuff()...

//you decide what concrete role can do.
services.AddAuthorization(options =>

    options.AddPolicy("CanAddStuff", policy => policy.RequireRole("Manager"));
    options.AddPolicy("CanGetStuff", policy => policy.RequireRole("Reader"));
);

//somewhere in authentication step of some user you decide it is manager  logged in, and manager in YOUR hierarchy of titles is also a Reader
var claims = new List<Claim>();
claims.Add(new Claim(ClaimTypes.Role, "Manager", ClaimValueTypes.String, Issuer));
claims.Add(new Claim(ClaimTypes.Role, "Reader", ClaimValueTypes.String, Issuer));

对于头衔/角色:

[Authorize(Role="Manager")]
public IActionResult AddSomeStuff()...

[Authorize(Role="Reader")]
public IActionResult GetSomeStuff()...

这将简单地检查您的用户是否有Claim(您可以在身份验证步骤中添加它们)。您可以根据自己的国王/经理/读者/奴隶等层次结构随意添加任意数量的内容。

更多信息在这里-https://github.com/blowdart/AspNetAuthorizationWorkshop#step-6-multiple-handlers-for-a-requirement

【讨论】:

【参考方案2】:

访问受保护的 API 需要将访问令牌传递给 API 请求的标头。

传递客户端 ID 和密码不会提供访问权限。客户端 ID 和密码仅用于非交互式客户端。

获得对安全 Web API 方法的访问权有两个建议:

方法一

    使用客户端 id/secret 调用身份服务器 客户端从 1) 获取访问令牌 从客户端调用您的 API 方法,将访问令牌传递到请求标头中。

方法二

    使用用户凭据(用户名/密码)调用身份服务器 身份服务器验证用户凭据并生成访问令牌。 客户端从 2) 获取访问令牌 从客户端调用您的 API 方法,将访问令牌传递到请求标头中。

方法 1 只需要客户端 ID/密码。对于需要从身份服务器获取令牌的非交互式服务,建议使用此方法。

方法 2 需要对凭据进行自定义验证。第三方解决方案或您自己的后端安全存储。这是交互式客户端应用程序的推荐解决方案。

【讨论】:

以上是关于使用 IdentityServer 和 C# 保护 API的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Identityserver4 添加/检查策略和角色 .net 核心 API 保护

IdentityServer4文档- 使用客户端凭据保护 API

如何使用 IdentityServer4 保护 API(免受意外调用)?

IdentityServer4示例之使用ResourceOwnerPassword流程来保护API

IdentityServer4主题之定义资源

IdentityServer4-快速入门