使用 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_id
和client_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_id
和client_secret
调用API 的正确方法是什么? scope
是必需的吗?
更新
为了澄清,我认为是机器对机器的场景。因此,没有用户参与。当HttpClient
调用API时,它必须通过认证。在机器对机器的场景中,我想检查请求是否具有特定的范围,例如 api_read、api_write、api_full 和基于调用是否可以访问某个函数。
例如,在 Blazor 中,我创建了一个函数来传递 scope
,但不传递 clinet_id
和 client_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(免受意外调用)?