注入下面事件即可
options.Events.OnRedirectToIdentityProviderForSignOut = context => {
context.ProtocolMessage.PostLogoutRedirectUri ="http://xxxx.com/xxxxx"; //用户退出时候的通道,类似于:http://localhost:5000/signout-callback-oidc
return Task.CompletedTask;
};
通常自定义返回页面的情况下需要考虑到如何注销客户端的用户cookie,可以考虑使用前端来重写cookie,但是这个不一定十分有效,用户在认证中心推出后并不一定会返回到client端
此时需要使用后端注销通道
[HttpPost("forcedSignout")]
[AllowAnonymous]
public async Task<IActionResult> ForcedSignout([FromForm] string logout_token)
{
//try
//{
var user = await ValidateLogoutToken(logout_token);
// these are the sub & sid to signout
var sub = user.FindFirst("sub")?.Value;
var sid = user.FindFirst("sid")?.Value;
LogoutSessions.Add(sub, sid);
return Ok();
//}
//catch (Exception ex)
//{
// logger.LogError(ex, "用户注销时候异常");
//}
//return BadRequest();
}
private async Task<ClaimsPrincipal> ValidateJwt(string jwt)
{
// read discovery document to find issuer and key material
var client = new HttpClient();
logger.LogInformation("开始获取远程认证:" + identityAdminConfiguration.IdentityServerBaseUrl + " jwt:" + jwt);
var disco = await client.GetDiscoveryDocumentAsync(new DiscoveryDocumentRequest
{
Address = identityAdminConfiguration.IdentityServerBaseUrl, //认证中心的基础地址
Policy =
{
ValidateIssuerName = false,
RequireHttps = false //如果认证中心没有使用https,则一定要配置这个,如果不配置会返回奇怪的Exception 记录,localhost 域名下可以不用配置。
}
});
if (disco.IsError) throw new Exception(disco.Error);
logger.LogInformation("获取认证终端"+ disco.EndSessionEndpoint);
var keys = new List<SecurityKey>();
foreach (var webKey in disco.KeySet.Keys)
{
var key = new JsonWebKey()
{
Kty = webKey.Kty,
Alg = webKey.Alg,
Kid = webKey.Kid,
X = webKey.X,
Y = webKey.Y,
Crv = webKey.Crv,
E = webKey.E,
N = webKey.N,
};
keys.Add(key);
}
var parameters = new TokenValidationParameters
{
ValidIssuer = disco.Issuer,
ValidAudience = identityAdminConfiguration.ClientId, //client Id
IssuerSigningKeys = keys,
NameClaimType = JwtClaimTypes.Name,
RoleClaimType = JwtClaimTypes.Role
};
var handler = new JwtSecurityTokenHandler();
handler.InboundClaimTypeMap.Clear();
SecurityToken validatedToken;
return handler.ValidateToken(jwt, parameters, out validatedToken);
}
private async Task<ClaimsPrincipal> ValidateLogoutToken(string logoutToken)
{
var claims = await ValidateJwt(logoutToken);
if (claims.FindFirst("sub") == null && claims.FindFirst("sid") == null) throw new Exception("Invalid logout token");
var nonce = claims.FindFirstValue("nonce");
if (!String.IsNullOrWhiteSpace(nonce)) throw new Exception("Invalid logout token");
var eventsJson = claims.FindFirst("events")?.Value;
if (String.IsNullOrWhiteSpace(eventsJson)) throw new Exception("Invalid logout token");
var events = JObject.Parse(eventsJson);
var logoutEvent = events.TryGetValue("http://schemas.openid.net/event/backchannel-logout");
if (logoutEvent == null) throw new Exception("Invalid logout token");
return claims;
}
其中涉及到两个自定义类
public class CookieEventHandler : CookieAuthenticationEvents
{
public LogoutSessionManager LogoutSessions { get; }
ILogger logger;
public CookieEventHandler(LogoutSessionManager logoutSessions,ILogger<CookieEventHandler> logger)
{
this.LogoutSessions = logoutSessions;
this.logger = logger;
}
public override async Task ValidatePrincipal(CookieValidatePrincipalContext context)
{
if (context.Principal.Identity.IsAuthenticated)
{
var sub = context.Principal.FindFirst("sub")?.Value;
var sid = context.Principal.FindFirst("sid")?.Value;
logger.LogInformation("用户退出sid:" + sid);
if (LogoutSessions.IsLoggedOut(sub, sid))
{
logger.LogInformation("侦测到退出用户:" + sid);
context.RejectPrincipal();
await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
// todo: if we have a refresh token, it should be revoked here.
}
else
{
logger.LogInformation($"未能退出用户,缓存中匹配不到sub:{sub}, sid:{sid}");
}
}
else
{
logger.LogInformation("未能检测到登录用户");
}
}
}
public class LogoutSessionManager
{
ILogger logger;
public LogoutSessionManager(ILogger<LogoutSessionManager> logger)
{
this.logger = logger;
}
List<Session> _sessions = new List<Session>();
public void Add(string sub, string sid)
{
//缓存要退出的用户的id,这里放到内存中是不合理的,当系统重启后,一些用户无法注销,也无法扩展多个实例
_sessions.Add(new Session { Sub = sub, Sid = sid });
logger.LogInformation($"缓存待注销的用户的信息sub:{sub} sid:{sid}");
}
public bool IsLoggedOut(string sub, string sid)
{
try
{
logger.LogInformation($"匹配缓存中的sub:{sub} sid:{sid}");
var matches = _sessions.Any(s => s.IsMatch(sub, sid));
logger.LogInformation($"匹配缓存中的sub,sid匹配结果:" + matches.ToString());
return matches;
}
catch(Exception ex)
{
logger.LogError(ex,$"匹配缓存中的sub,sid发生了严重错误");
}
return false;
}
private class Session
{
public string Sub { get; set; }
public string Sid { get; set; }
public bool IsMatch(string sub, string sid)
{
return (Sid == sid && Sub == sub) ||
(Sid == sid && Sub == null) ||
(Sid == null && Sub == sub);
}
}
}
在starup.cs中加入
services.AddTransient<CookieEventHandler>();
services.AddSingleton<LogoutSessionManager>();
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultForbidScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignOutScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddOpenIdConnect("oidc", options =>
{
xxxxx
}
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
//options.Cookie.Name = identityAdminConfiguration.IdentityAdminCookieName;
//options.Cookie.HttpOnly = true; //只读
options.EventsType = typeof(CookieEventHandler); //这一句
});
大概作用就是,用户在注销时认证中心会通过后端通道通知client,client使用认证中心的终结点,获取用户的sid,存储到缓存中,如果用户再次来请求,会触发CookieEventHandler 里的程序,此时在缓存中检测是否有sid(从请求中获取)的退出记录,如果有则执行退出程序,删除cookie,响应401等操作
本文链接:https://blog.nnwk.net/article/53
有问题请留言。版权所有,转载请在显眼位置处保留文章出处,并留下原文连接
Leave your question and I'll get back to you as soon as I see it. All rights reserved. Please keep the source and links
友情链接:
子卿全栈
全部评论