我目前正在开发一个单点登录系统,并使用这个单点登录系统。我开发了一个用于用户管理的管理客户端应用程序。管理客户端在操作用户数据时将调用身份提供程序 (IDP) 中的 API 调用。原因是用户信息存储在身份提供者的数据库中,因此,要从管理客户端对其进行操作,通过 API 调用来执行此操作是有意义的,而不是在多个不同的位置存储或访问信息。
系统使用 OpenIddict 服务器作为 IDP,使用 OpenIddict 客户端作为管理客户端应用程序。 我正在尝试利用多个身份验证流程来完成这项工作。当用户登录时使用授权代码流作为检索包含其声明的访问令牌的方式。客户端凭据流用于在管理客户端向 IDP API 端点发出请求时检索用户的访问令牌。
每当用户尝试向控制器发出 Http 请求(控制器将向 IDP 中的 API 端点发出请求)时,管理客户端都会检查用户的角色。由于用户的角色是在管理客户端中检查的,因此我选择使用客户端凭据流程来授权从客户端向 IDP 发出的请求。
请求此访问令牌的方法如下:
private TokenResponse RequestAccessToken()
{
lock (accessTokenLock)
{
if (DateTime.Now.AddMinutes(1) > accessTokenExpiry)
{
HttpRequestMessage request = new()
{
Method = HttpMethod.Post,
RequestUri = new Uri($"{client.BaseAddress}connect/token"),
Content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("client_secret", clientSecret)
})
};
HttpResponseMessage? response = client.Send(request);
response.EnsureSuccessStatusCode();
var stringResponse = response.Content.ReadAsStringAsync().Result;
var tokenResponse = JsonConvert.DeserializeObject<TokenResponse>(stringResponse);
accessTokenExpiry = DateTime.Now.AddSeconds(tokenResponse.ExpiresIn);
return tokenResponse;
}
return new TokenResponse();
}
}
private class TokenResponse
{
[JsonProperty("token_type")]
public string TokenType { get; set; }
[JsonProperty("access_token")]
public string AccessToken { get; set; }
[JsonProperty("expires_in")]
public int ExpiresIn { get; set; }
}
我在对 IDP 的 API 端点的以下请求中使用从上面检索到的访问令牌:
public async Task<List<UserDTO>> GetAllUsersAsync()
{
try
{
TokenResponse accessToken = RequestAccessToken();
HttpRequestMessage request = new()
{
Method = HttpMethod.Get,
RequestUri = new Uri($"useradministration/getallusersdetailsandclaims", UriKind.Relative),
Headers =
{
Authorization = new AuthenticationHeaderValue("Bearer", accessToken.AccessToken)
}
};
var response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var userData = System.Text.Json.JsonSerializer.Deserialize<List<UserDTO>>(content,
new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
return userData;
}
catch (Exception ex)
{
throw new InvalidOperationException(ex.Message);
}
}
IDP 中的
useradministration/getallusersdetailsandclaims
端点受 [Authorize]
标签保护。
但是,当我发送 JWT Bearer 访问令牌时,我从请求的 Authorization 标头中的
RequestAccessToken()
方法检索到,我从 IDP 收到未经授权的响应。
我相信 IDP 在通过请求发送令牌时并未验证令牌。从客户端凭据流程检索到的令牌与通过授权代码流程检索到的令牌略有不同。
是否可以在 IDP 中实现两种不同的身份验证方案,以便能够处理从授权代码流和客户端凭据流检索的令牌?这样它就可以验证管理员发送的令牌客户端在请求期间,以及通过其他方式发出的授权/拒绝请求。
我已尽力使这篇文章保持简洁,但如果需要更多信息,请随时在评论中询问,如果需要,我可以在帖子中添加更多信息。
提前致谢。
我认为这是可能的,但可能会带来繁重的工作。正如我们所知,它本质上是基于令牌的授权,因此我们当然可以创建一个自定义中间件来检查服务器的传入请求,然后获取访问令牌并验证它是否有效。该中间件应设置为可用于某些特定端点,然后我们可能不会在这些端点上添加
[Authorize]
属性。但看起来很丑。我相信我们想要实现的是某种内置功能,因此我们只需添加一些配置即可满足要求。
我在 github 上进行了搜索,但还没有成功。所以我只能尝试回到项目本身。在 ASP.NET Core 中,我们有“基于策略”的授权。这允许我们在授权属性中添加策略,我认为比创建中间件更好。那么我们定义的策略将类似于下面。
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AuthCodePolicy", policy =>{
policy.RequireClaim("scope", "value1");
});
options.AddPolicy("ClientCredntialPolicy", policy =>{
policy.RequireClaim("role", "value2");
});
});
但是在一个端点中添加 2 个策略意味着请求需要同时满足 2 个策略。因此,如果不同的流程有不同的端点,上面的代码可能没问题,但如果两个流程只有一个端点,那么我们可以按照我上面分享的部分创建 1 个需求和 2 个处理程序,那么它应该是类似的到下面的代码。
public class AuthHandler : AuthorizationHandler<AuthAndClientRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, AuthAndClientRequirement requirement)
{
//validation logic for auth code flow here
return Task.CompletedTask;
}
}
public class ClientHandler : AuthorizationHandler<AuthAndClientRequirement>
{
protected override Task HandleRequirementAsync(
AuthorizationHandlerContext context, AuthAndClientRequirement requirement)
{
//validation logic for client credential flow here
return Task.CompletedTask;
}
}
// Add all of your handlers to DI.
builder.Services.AddSingleton<IAuthorizationHandler, AuthHandler>();
builder.Services.AddSingleton<IAuthorizationHandler, ClientHandler>();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AuthAndClientPolicy", policy =>
policy.Requirements.Add(new AuthAndClientRequirement()));
});