登出介绍
前端登出(FRONT_CHANNEL): 用户点击logout链接(链接地址为cas登出地址)时,浏览器跳转到登出地址,登出页面给对接应用发送jsonp的登出请求,达到登出效果。 注意: 前端登出不支持cas会话超时自动登出。 注意: 前端登出不支持https->http的登出,即sso使用https协议,应用使用http协议。
后端登出(BACK_CHANNEL): 是当TGT超时或者用户通过链接访问登出地址时,服务器后台直接给应用服务器发送登出请求,以达到登出效果。
1.9.0版本之前
cas登出方式有两种方式:
前端登出(FRONT_CHANNEL) 条件:应用协议要和sso服务一致,如sso使用https应用也要https 后端登出(BACK_CHANNEL) 条件:要求应用配置sso单点登出拦截器,并且放在第一个
oauth 登出方式有两种方式:
前端登出(FRONT_CHANNEL) 条件:应用协议要和sso服务一致,如sso使用https应用也要https 后端登出(BACK_CHANNEL) 不支持
1.9.0及以后版本登出类型
之前登出不变
- 新增接口登出(只支持后端登出)
1.9.0新增登出接口
请求方式: GET(HTTPS或HTTP)
请求地址: https://sso.xxx.edu.cn/api/logout/{tgt}
请求参数说明:
参数 | 必须 | 说明 |
---|---|---|
tgt | 是 | 应用登录成功,sso返回参数包括tgt |
返回结果:
{
"code" : 200,
"message" : "OK",
"data" : true
}
cas应用对接介绍
配置应用对接
应用配置登出拦截器
/**
* SingleSignOutFilter
* 必须放在最前面
*/
@Bean
public FilterRegistrationBean<SingleSignOutFilter> filterSingleRegistration() {
FilterRegistrationBean<SingleSignOutFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new SingleSignOutFilter());
Map<String, String> initParameters = new HashMap<String, String>();
initParameters.put("casServerUrlPrefix", CAS_SERVER_URL_PREFIX);
registration.setInitParameters(initParameters);
//set mapping url
registration.addUrlPatterns("/*");
//set loading sequence
registration.setOrder(1);
return registration;
}
- 记录当前登录p3接口返回的tgt参数
<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
<cas:authenticationSuccess>
<cas:user>admin</cas:user>
<cas:attributes>
<cas:tgt>TGT-10-O6Wgj6jXho4QpW2XrMoBnEGtzsuEdvesCk6A-h17MVQsLu2MJFHq-4XAvxhw36DXW6crg-sso-6b565d7bd-k2m64objectId=5c7776dfedd9a9952b3b44c2</cas:tgt>
<cas:samlAuthenticationStatementAuthMethod>urn:oasis:names:tc:SAML:1.0:am:password</cas:samlAuthenticationStatementAuthMethod>
<cas:credentialType>UsernamePasswordCredential</cas:credentialType>
<cas:completedLoginMethod>UsernamePassword</cas:completedLoginMethod>
<cas:isFromNewLogin>false</cas:isFromNewLogin>
<cas:authenticationDate>2021-09-15T09:34:59.024</cas:authenticationDate>
<cas:authenticationMethod>MultiIdPasswordAuthenticationHandler</cas:authenticationMethod>
<cas:successfulAuthenticationHandlers>MultiIdPasswordAuthenticationHandler</cas:successfulAuthenticationHandlers>
<cas:longTermAuthenticationRequestTokenUsed>false</cas:longTermAuthenticationRequestTokenUsed>
</cas:attributes>
</cas:authenticationSuccess>
</cas:serviceResponse>
- 调用sso登出接口进行登出
登出逻辑 1.应用调用sso接口,sso根据tgt将sso登出
2.sso登出之后,根据tgt查询关联已登录的应用
3.构造应用请求地址,通过http发送到应用
4.应用配置了cas拦截器
4.1拦截器作用:登录记录st和session的关系、登出解绑st和session关系,并将session失效
参考源码
public boolean process(final HttpServletRequest request, final HttpServletResponse response) {
if (isTokenRequest(request)) {
logger.trace("Received a token request");
recordSession(request);
return true;
}
if (isLogoutRequest(request)) {
logger.trace("Received a logout request");
destroySession(request);
return false;
}
logger.trace("Ignoring URI for logout: {}", request.getRequestURI());
return true;
}
登录记录st和session关系
private void recordSession(final HttpServletRequest request) {
final HttpSession session = request.getSession(this.eagerlyCreateSessions);
if (session == null) {
logger.debug("No session currently exists (and none created). Cannot record session information for single sign out.");
return;
}
final String token = CommonUtils.safeGetParameter(request, this.artifactParameterName, this.safeParameters);
logger.debug("Recording session for token {}", token);
try {
this.sessionMappingStorage.removeBySessionById(session.getId());
} catch (final Exception e) {
// ignore if the session is already marked as invalid. Nothing we can do!
}
sessionMappingStorage.addSessionById(token, session);
}
登出删除session和st的关系,并且session失效
private void destroySession(final HttpServletRequest request) {
String logoutMessage = CommonUtils.safeGetParameter(request, this.logoutParameterName, this.safeParameters);
if (CommonUtils.isBlank(logoutMessage)) {
logger.error("Could not locate logout message of the request from {}", this.logoutParameterName);
return;
}
if (!logoutMessage.contains("SessionIndex")) {
logoutMessage = uncompressLogoutMessage(logoutMessage);
}
logger.trace("Logout request:\n{}", logoutMessage);
final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
if (CommonUtils.isNotBlank(token)) {
final HttpSession session = this.sessionMappingStorage.removeSessionByMappingId(token);
if (session != null) {
final String sessionID = session.getId();
logger.debug("Invalidating session [{}] for token [{}]", sessionID, token);
try {
session.invalidate();
} catch (final IllegalStateException e) {
logger.debug("Error invalidating session.", e);
}
this.logoutStrategy.logout(request);
}
}
}
oauth应用对接介绍
- 配置应用对接
定义全局静态变量
public class SessionMapUtils { public static Map<String, HttpSession> sessionMap = new ConcurrentHashMap<>(); }
- 认证成功返回tgt,记录session和tgt的关系
@RequestMapping("/")
public String indexController(HttpServletRequest request, Model model) {
OAuth2Authentication userPrincipal = (OAuth2Authentication) request.getUserPrincipal();
OAuth2Request oAuth2Request = userPrincipal.getOAuth2Request();
Map<String, String> requestParameters = oAuth2Request.getRequestParameters();
System.out.println(requestParameters.get("tgt"));
SessionMapUtils.sessionMap.put(requestParameters.get("tgt"), request.getSession(true));
String username = request.getRemoteUser();
user.setUsername(username);
model.addAttribute(user);
return "index";
}
- 定义logout拦截器(下面仅供参考)
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
if (requiresLogout(request, response)) {
String logoutMessage = CommonUtils.safeGetParameter(request, "logoutRequest", Arrays.asList("logoutRequest"));
if (CommonUtils.isBlank(logoutMessage)) {
return;
}
if (!logoutMessage.contains("SessionIndex")) {
logoutMessage = uncompressLogoutMessage(logoutMessage);
}
final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");
if (!StringUtils.isEmpty(token)) {
HttpSession httpSession = SessionMapUtils.sessionMap.get(token);
request.setAttribute("session",httpSession);
SessionMapUtils.sessionMap.remove(token);
}
// 两步 token失效
// session失效
if (invalidateHttpSession) {
HttpSession session = request.getSession(false);
if(session == null){
session = (HttpSession) request.getAttribute("session");
}
if (session != null) {
logger.debug("Invalidating session: " + session.getId());
session.invalidate();
}
}
return;
}
chain.doFilter(request, response);
}
sso后端登出请求介绍
请求方式: POST(HTTPS或HTTP)
请求地址: 对接应用配置的地址
请求参数说明:
参数 | 必须 | 说明 |
---|---|---|
logoutRequest | 是 | 请求参数 |
http://cas.client.com.cn:9879/logout?logoutRequest=<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="LR-6-0H7GAmbdU-3YrLPK18l9QIWX" Version="2.0" IssueInstant="2021-09-13T10:56:57Z"><saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">admin</saml:NameID><samlp:SessionIndex>ST-9-IGwVvNAxgRjiYk6meKpSCGZVbV4rg-sso-85768c5f9-4x8cc</samlp:SessionIndex></samlp:LogoutRequest>
应用解析 logoutRequest参数得到
- cas对接logoutRequest参数
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="LR-6-0H7GAmbdU-3YrLPK18l9QIWX" Version="2.0" IssueInstant="2021-09-13T10:56:57Z">
<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">admin</saml:NameID>
<samlp:SessionIndex>ST-9-IGwVvNAxgRjiYk6meKpSCGZVbV4rg-sso-85768c5f9-4x8cc</samlp:SessionIndex>
</samlp:LogoutRequest>
- oauth对接logoutRequest参数
<samlp:LogoutRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" ID="LR-6-0H7GAmbdU-3YrLPK18l9QIWX" Version="2.0" IssueInstant="2021-09-13T10:56:57Z">
<saml:NameID xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion">admin</saml:NameID>
<samlp:SessionIndex>TGT-10-O6Wgj6jXho4QpW2XrMoBnEGtzsuEdvesCk6A-h17MVQsLu2MJFHq-4XAvxhw36DXW6crg-sso-6b565d7bd-k2m64objectId=5c7776dfedd9a9952b3b44c2</samlp:SessionIndex>
</samlp:LogoutRequest>
解析 SessionIndex参数
SessionIndex在cas应用中对应st,在oauth应用中对应的tgt
解析方法
String logoutMessage = CommonUtils.safeGetParameter(request, "logoutRequest", "logoutRequest");
if (CommonUtils.isBlank(logoutMessage)) {
return;
}
if (!logoutMessage.contains("SessionIndex")) {
logoutMessage = uncompressLogoutMessage(logoutMessage);
}
logger.trace("Logout request:\n{}", logoutMessage);
final String token = XmlUtils.getTextForElement(logoutMessage, "SessionIndex");