我提前为这篇很长的文章道歉,只是情况比较特殊,我无法在任何地方找到解决方案。我一直在为公司设计单点登录系统......
我提前为这篇很长的帖子道歉,只是情况比较特殊,我无法在任何地方找到解决方案。
我一直在为我工作的公司设计一个使用 ASP.NET 和 OpenIddict 的单点登录系统。作为这项工作的一部分,我创建了一个新的“管理”区域用于用户管理,它充当与身份提供者分开的客户端应用程序。由于身份提供者可以访问用户数据库,我开发了 API 调用,管理客户端应用程序可以向其发送请求,以便身份提供者代表该客户端检索或更改用户信息,这意味着对用户数据库的访问完全由身份提供者负责,而不是在两个地方存储或访问信息。
目前,发送给身份提供商中用于用户管理的 API 调用的 HttpRequest 在管理客户端是安全的,但在身份提供商端则不是。在管理客户端中,授权策略的使用已到位,并通过标签实现 [Authorize (Policy = "AdminUser")]
。因此,当用户在管理客户端上发出请求时,它会在授权请求之前检查他们是否经过身份验证,以及他们是否具有有效的角色声明。这一切都很好,但是这些 API 调用所在的控制器上的身份提供商端也采用了相同的授权策略,但它会尝试返回登录页面,因为来自管理客户端的请求未经授权。
我认为问题在于我没有正确地将访问令牌与请求一起传回,因此身份提供者 API 调用看不到用户的角色是什么,甚至不知道他们是否经过身份验证。我一直无法弄清楚如何检索它,然后正确地传递它。这是我目前所拥有的:
在 Admin 客户端应用中,HomeController.cs 中的 Index 方法用于在 Admin 登录页面上显示用户。此方法使用该 GetAllUsersAsync()
方法通过对身份提供者 API 调用发出的请求获取所有用户。
[HttpGet("~/")]
public async Task<IActionResult> Index()
{
List<UserInfoModel> userModel = new();
var users = await _adminClient.GetAllUsersAsync();
foreach ( var user in users )
{
UserInfoModel userInfo = new();
userInfo.Id = user.Id;
userInfo.Role = user.Role;
userInfo.UserName = user.UserName;
userInfo.Email = user.Email;
userModel.Add(userInfo);
}
return View(userModel);
}
AdminClient.cs 注册为单例服务,并采用一些参数(主要用于客户端 ID 和机密)。
AdminClient.cs:
private readonly HttpClient client = new();
private readonly string clientId;
private readonly string clientSecret;
private readonly ILogger logger;
private readonly object accessTokenLock = new object();
private DateTime accessTokenExpiry = DateTime.MinValue;
private readonly IHttpContextAccessor _httpContextAccessor;
public AdminClient(string baseAddress, string clientId, string clientSecret, ILogger<AdminClient> logger, IHttpContextAccessor httpContextAccessor)
{
client.BaseAddress = new Uri(baseAddress);
this.clientId = clientId;
this.clientSecret = clientSecret;
this.logger = logger;
_httpContextAccessor = httpContextAccessor;
}
程序.cs:
builder.Services.AddSingleton<IAdminClient>(serviceProvider => new AdminClient(
clientDetails.GetSection("IssuerUrl").Value?.ToString(),
clientDetails.GetSection("ClientId").Value?.ToString(),
clientDetails.GetSection("ClientSecret").Value?.ToString(),
serviceProvider.GetRequiredService<ILogger<AdminClient>>(),
new HttpContextAccessor()));
GetAllUsersAsync()
来自 AdminClient.cs。此方法尝试从 Http 上下文获取用户的授权码,然后使用授权码通过该 GetAccessTokenAsync()
方法获取访问令牌(本文将进一步介绍)。然后,它会向身份提供者中的用户管理控制器创建一个 Http 请求,并将访问令牌包含在授权标头中。它会发送此请求,然后该方法的其余部分会在请求成功后反序列化 JSON。
public async Task<List<UserDTO>> GetAllUsersAsync()
{
try
{
// Get authorisation code
string? authCode = _httpContextAccessor?.HttpContext?.Request.Query["code"];
string? accessToken = await GetAccessTokenAsync(authCode);
HttpRequestMessage request = new()
{
Method = HttpMethod.Get,
RequestUri = new Uri($"useradministration/getallusersdetailsandclaims", UriKind.Relative),
Headers =
{
Authorization = new AuthenticationHeaderValue("Bearer", 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);
}
}
该 GetAccessTokenAsync()
方法也位于 AdminClient.cs 类中。它向身份提供者的令牌端点创建一个请求,并在请求的内容中包含授权类型(使用授权码流程)、通过参数传递的授权码以及客户端的 ID 和密钥。然后,它发送请求,如果成功,则反序列化 JSON 响应以提取访问令牌并将其返回,以便在请求中使用 GetAllUsersAsync()
private async Task<string> GetAccessTokenAsync(string authCode)
{
HttpRequestMessage request = new()
{
Method = HttpMethod.Post,
RequestUri = new Uri($"{client.BaseAddress}connect/token"),
Content = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("code", authCode),
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("client_secret", clientSecret)
})
};
try
{
HttpResponseMessage? response = await client.SendAsync(request);
response.EnsureSuccessStatusCode();
string responseContent = await response.Content.ReadAsStringAsync();
JObject json = JObject.Parse(responseContent);
string accessToken = json["access_token"].ToString();
return accessToken;
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
目前,我 null
从 Http 上下文查询中获取一个值以获取授权码,因此访问令牌未从 返回 GetAccessTokenAsync()
,导致 中的请求错误或未经授权 GetAllUsersAsync()
。最重要的是,即使我确实获得了授权码,我仍然不确定这是否是正确的做法。如果我能得到一些关于我所缺少的东西的建议,或者如果我需要以不同的方式完成这个过程,那就太好了。
如果需要更多信息或背景信息,请告诉我,我会发送您需要的任何信息。
先感谢您。
基本上,JWT 令牌必须是短期令牌(可能已经过期,特别是如果只发送一次),并且您可能有一个或多个刷新令牌作为编码到 JWT 中的字段。因此,当您从 SP 向 IDP 发出请求时,您会发回一个刷新令牌,该令牌已用完,IDP 将通过刷新令牌对您进行身份验证,使其失效并向您发回一个新的刷新令牌,您可以在下次请求时使用。