请问在 OP(OpenID Provider) 上登出(sign out)时如何让 RP(Relying Party) 应用也自动登出?
OP 基于 OpenIddict 实现,RP 基于 Microsoft.AspNetCore.Authentication.OpenIdConnect 实现
也就是站点A通过站点B进行单点登录(基于 OpenID Connect 实现),在站点B上退出登录时,站点A也同时退出登录,即单点登出 Single Logout (SLO)
终于通过非标准的做法实现了。
(一)
服务端(基于 OpenIddict 实现的 OpenID Provider)在用户登出时 revoke 当前用户的所有 token
public bool RevokeToken(string subject)
{
var result = _dbContext.Set<OpenIddictEntityFrameworkCoreToken>()
.Where(t => t.Subject == subject && (t.Status == Statuses.Redeemed || t.Status == Statuses.Valid))
.Update(t => new OpenIddictEntityFrameworkCoreToken { Status = Statuses.Revoked });
return result > 0;
}
Sign-out endpoint 的实现
[HttpPost("/signout")]
public async Task<IActionResult> SignOut(string returnUrl)
{
var userId = User.UCenter()?.UserId;
if (userId.HasValue)
{
_openIddictService.RevokeToken(userId.ToString());
}
await HttpContext.SignOut();
return Redirect(Url.SecuringUrl(returnUrl));
}
同时,服务端实现一个 web api 用于检查 toke id 对应的 token 是否失效
[HttpHead("validate-token-id")]
public async Task<IActionResult> ValidateTokenId()
{
if (Request.Headers.TryGetValue("x-token-id", out var tokenId))
{
if (await _openIddictService.ValidateTokenId(tokenId))
{
return NoContent();
}
}
return BadRequest();
}
ValidateTokenId 的实现
public async Task<bool> ValidateTokenId(string tokenId)
{
var status = await _dbContext.Set<OpenIddictEntityFrameworkCoreToken>()
.Where(t => t.Id == tokenId)
.Select(t => t.Status)
.FirstOrDefaultAsync();
return status == Statuses.Redeemed || status == Statuses.Valid;
}
(二)
客户端(基于 ASP.NET Core 内置的 OpenIdConnect 实现的 Relying Party)实现一个 CookieAuthenticationEvents,在已登录的用户每次请求时检查 token id,如果 token 已经失效,自动登出当前用户。
public class CnblogsCookieAuthenticationEvents : CookieAuthenticationEvents
{
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
var identity = context.Principal.Identity;
if (!identity.IsAuthenticated)
{
return;
}
var sp = context.HttpContext.RequestServices;
var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
var tokenId = context.Principal.Claims.FirstOrDefault(c => c.Type == "oi_tkn_id")?.Value;
if (!string.IsNullOrEmpty(tokenId))
{
using var client = httpClientFactory.CreateClient();
client.BaseAddress = new Uri("https://account.cnblogs.com/");
using var request = new HttpRequestMessage(HttpMethod.Head, "openiddict/validate-token-id");
request.Headers.Add("x-token-id", tokenId);
using var response = await client.SendAsync(request);
if (response.StatusCode == HttpStatusCode.BadRequest)
{
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return;
}
}
await base.ValidatePrincipal(context);
}
}
在 AddCookie 时注册上面的实现
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.EventsType = typeof(CnblogsCookieAuthenticationEvents);
});
(搞定)
哈哈,这个问题太高深了,这么几天都没有人回答。
前段时间刚好也看了一下 OpenIddict ,看得晕乎乎的的,国内好像没人用过个,资料也少。
按我的理解:OpenID仅仅是在用的那一瞬间起一个凭证作用, 要实现单点登录还得各系统自己完成。
什么敏感词????
@Adming: OpenIddict 实现了 OpenID Connect 规范,单点登录是基于 OpenID Connect 规范,我们已经基于 OpenIddict 实现了。只是 OpenIddict 没有 IdentiyServer 做的那么全面,需要自己参考示例代码实现 connect/authorize
与 connect/token
endpoint。
目前 OpenIddict 不支持 Back-Channel Logout
@dudu: 不冲突,OpenID Connect 规范就相当于我上面的说“关于加强XX管理”的工作指导。
但具体怎么实现还是得B系统根据自己的需求选择实时检查、定期检查或不检查。IdentiyServer 完成度高,同步检查方面的工作都可以直接用现成的,OpenIddict 更简单灵活,可能需要自己写代码实现。
@Adming: 重点工作就是 token 管理
@dudu: 是的,但token这个词太抽象了,抽象到它和“卡”,"证件"是一个概念了。