- 浏览: 102365 次
- 性别:
- 来自: 北京
文章分类
最新评论
-
vampirehgg:
请教一下,登录成功跳转回客户端时,地址栏中会自动加上ticke ...
【原创】CAS总结之协议分析篇 -
Readiay:
【原创】CAS总结之单点退出篇(CAS到底有没有实现单点退出?) -
gyxkg:
很好,感谢楼主,做的漂亮
【原创】CAS总结之协议分析篇 -
dadafei007:
...
【原创】CAS总结之Ticket篇 -
zxmsdyz:
非常清楚 感谢楼主付出
【原创】CAS总结之协议分析篇
CAS 总结之单点退出篇
CAS 到底有没有实现单点退出?本人阅读了 JA-SIG CAS v3.3 ,以及 JA-SIG CAS-CLIENT 3.1.9 的源代码,发现表面上好像实现了单点退出,但实际上却没有真正实现。
现将 CAS 的 logout 接口的实现整理如下。
首先看一下 CAS logout 功能的序列图。
CAS logout
功能的序列图
从图中可以看出, CAS logout 功能有两步,一是调用 TGT 对象中各个 Service 的 logoutOfService 方法,二是在缓存中清除 TGT 对象。
我们看一下 CAS 的 AbstractWebApplicationService 中 logoutOfService 方法的实现。
public synchronized boolean logOutOfService(final String sessionIdentifier) { if (this.loggedOutAlready) { return true; } LOG.debug("Sending logout request for: " + getId()); final String logoutRequest = "<samlp:LogoutRequest xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\" ID=\"" + GENERATOR.getNewTicketId("LR") + "\" Version=\"2.0\" IssueInstant=\"" + SamlUtils.getCurrentDateAndTime() + "\"><saml:NameID xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">@NOT_USED@</saml:NameID><samlp:SessionIndex>" + sessionIdentifier + "</samlp:SessionIndex></samlp:LogoutRequest>"; this.loggedOutAlready = true; if (this.httpClient != null) { return this.httpClient.sendMessageToEndPoint(getOriginalUrl(), logoutRequest); } return false; }
另外 HttpClient 类中 sendMessageToEndPoint 方法的实现如下:
public boolean sendMessageToEndPoint(final String url, final String message) { HttpURLConnection connection = null; BufferedReader in = null; try { if (log.isDebugEnabled()) { log.debug("Attempting to access " + url); } final URL logoutUrl = new URL(url); final String output = "logoutRequest=" + URLEncoder.encode(message, "UTF-8"); connection = (HttpURLConnection) logoutUrl.openConnection(); connection.setDoInput(true); connection.setDoOutput(true); connection.setReadTimeout(this.readTimeout); connection.setConnectTimeout(this.connectionTimeout); connection.setRequestProperty("Content-Length", "" + Integer.toString(output.getBytes().length)); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); final DataOutputStream printout = new DataOutputStream(connection .getOutputStream()); printout.writeBytes(output); printout.flush(); printout.close(); in = new BufferedReader(new InputStreamReader(connection .getInputStream())); while (in.readLine() != null) { // nothing to do } if (log.isDebugEnabled()) { log.debug("Finished sending message to" + url); } return true; } catch (final Exception e) { log.error(e,e); return false; } finally { if (in != null) { try { in.close(); } catch (final IOException e) { // can't do anything } } if (connection != null) { connection.disconnect(); } } }
通过阅读代码可以发现, logOutOfService 方法是调用 serivce 的 originUrl 接口,利用 HttpURLConnection 的方式把退出请求发送给 service ,注意没有给 HttpURLConnection 设置 requestMethod ,因此用的是默认的 GET 方法。 sessionIdentifier 的值是 ST 的值。 service 在 response 中会解析 logoutRequest 参数中的 sessionIdentifier 的值,然后把 sessionIdentifier 标识的 session kill 掉就可以了。这时我们发现,原理上是可以单点退出的。
再来看客户端的实现,客户端和单点退出有关的类包括:
- SingleSignOutFilter :用来解析 logoutRequest 参数。
- SessionMappingStorage :一个接口,定义了 Session 存储器的方法。
- HashMapBackedSessionMappingStorage : Session 存储器的实现类,定义了 2 个 Map 来存储 Session 。
MANAGED_SESSIONS:key 为 ST 的值, value 为 session ;
ID_TO_SESSION_KEY_MAPPING : key 为 sessionId,value 为 ST 的值。
-
SingleSignOutHttpSessionListener
:此
Listener
监听到
session
destroy
的事件后,用
sessionId
从上述
ID_TO_SESSION_KEY_MAPPING
中取出
ST
的值,然后依据
ST
的值从
MANAGED_SESSIONS
中取出
session,
然后就可以执行其
invalidate
方法了。
我们看一下 SingleSignOutFilter 中的 doFilter 方法。
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) servletRequest; if ("POST".equals(request.getMethod())) { final String logoutRequest = CommonUtils.safeGetParameter(request, "logoutRequest"); if (CommonUtils.isNotBlank(logoutRequest)) { if (log.isTraceEnabled()) { log.trace ("Logout request=[" + logoutRequest + "]"); } final String sessionIdentifier = XmlUtils.getTextForElement(logoutRequest, "SessionIndex"); if (CommonUtils.isNotBlank(sessionIdentifier)) { final HttpSession session = SESSION_MAPPING_STORAGE.removeSessionByMappingId(sessionIdentifier); if (session != null) { String sessionID = session.getId(); if (log.isDebugEnabled()) { log.debug ("Invalidating session [" + sessionID + "] for ST [" + sessionIdentifier + "]"); } try { session.invalidate(); } catch (final IllegalStateException e) { log.debug(e,e); } } return; } } } else { final String artifact = CommonUtils.safeGetParameter(request, this.artifactParameterName); final HttpSession session = request.getSession(false); if (session != null) { if (log.isDebugEnabled()) { log.debug("Storing session identifier for " + session.getId()); } if (CommonUtils.isNotBlank(artifact)) { try { SESSION_MAPPING_STORAGE.removeBySessionById(session.getId()); } catch (final Exception e) { // ignore if the session is already marked as invalid. Nothing we can do! } SESSION_MAPPING_STORAGE.addSessionById(artifact, session); } } else { log.debug("No Session Found, so ignoring."); } } filterChain.doFilter(servletRequest, servletResponse); }
非常奇怪,这个方法首先判断了 request 的方法,如果是 POST, 则会执行 session.invalidate 方法,从而实现单点退出,如果是 GET ,则只会在存在 ticket 参数的情况下,把 session 存进 SessionMappingStorage ,永远也不执行 session.invalidate 方法,不能单点退出。因为 CAS 的 logoutRequest 请求是用 GET 方法发过来的,所以,单点登录功能没有实现。
本人对 SingleSignOutFilter 中的 doFilter 方法重写了一下,代码如下。经验证,确实实现了单点退出。
public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException { final HttpServletRequest request = (HttpServletRequest) servletRequest; final String logoutRequest = CommonUtils.safeGetParameter(request, "logoutRequest"); Enumeration ff = request.getParameterNames(); String a = request.getQueryString(); if (CommonUtils.isNotBlank(logoutRequest)) { final String sessionIdentifier = XmlUtils.getTextForElement(logoutRequest, "SessionIndex"); if (CommonUtils.isNotBlank(sessionIdentifier)) { final HttpSession session = SESSION_MAPPING_STORAGE.removeSessionByMappingId(sessionIdentifier); if (session != null) { String sessionID = session.getId(); try { session.invalidate(); } catch (final IllegalStateException e) { } } } } else{ final String artifact = CommonUtils.safeGetParameter(request, this.artifactParameterName); final HttpSession session = request.getSession(false); if (CommonUtils.isNotBlank(artifact) && session!=null) { try { SESSION_MAPPING_STORAGE.removeBySessionById(session.getId()); } catch (final Exception e) { } SESSION_MAPPING_STORAGE.addSessionById(artifact, session); } } filterChain.doFilter(servletRequest, servletResponse); }
另外还需要注意的是,因为客户端部署了三个 Filter:AuthenticationFilter 、 ServiceValidationFilter 、 SingleSignOutFilter ,所以三个 Filter 的顺序需要注意,我的顺序为 AuthenticationFilter 、 ServiceValidationFilter 、 SingleSignOutFilter ,一开始不行,因为执行退出功能时, CAS 服务端用 HttpURLConnection 访问客户端,没有把 sessionId 代过来,所以在 AuthenticationFilter 中就被 redirect 回 CAS 了,到不了 SingleSignOutFilter ,我做了一个改动,就是在 AuthenticationFilter 中的 redirectUrl ,后面加上了 session ID 的值,格式如:“ ;jsessionid= ” , 这样 CAS 端解析 service 参数生成 WebApplicationService 时, orginUrl 里就有 sessionId 了。
虽然这样就实现了单点退出,但我感觉 CAS 的这种采用 Filter 的方式太麻烦了,不如让客户应用提供一个 callback url ,CAS 直接调用这个 callback url 来退出更好一些,但这样的话,对 CAS 的改动非常大。
本人博客 :http://zhenkm0507.iteye.com
评论
一个简单的方法: 各应用提供一个url ,访问这个url就会把当前域下 cookie 都设置过期,并把当前session 失效掉。
这样cas只需要生成一个页面,页面中分别get请求各个系统的这个url就可以了,可以生成多个iframe并指定其src 来实现。
这方法和cas的机制没多大关系,比较简单,而且在业务系统有集群的情况下也可以工作良好。
可以实现单点登出
是在DoFilter方法中加入这句话么? 能具体点么?
可以实现单点登出
默认配置是sign out的放在最前面的,因为这样就可以避免被Authentication Filter拦截了。
不过我用了一个笨方法。。。
自己写了个filter,当访问项目的/logout地址的时候直接清除Cookie和Session。
不过这样项目多了就郁闷了。。。
还是期待CAS4吧,我在他们的SVN上看到应该差不多完成了。
URL logoutUrl = new URL(url); String output = (new StringBuilder()).append("logoutRequest=").append(URLEncoder.encode(message, "UTF-8")).toString(); connection = (HttpURLConnection)logoutUrl.openConnection(); connection.setDoInput(true); connection.setDoOutput(true); connection.setRequestMethod("POST"); connection.setReadTimeout(readTimeout); connection.setConnectTimeout(connectionTimeout); connection.setRequestProperty("Content-Length", Integer.toString(output.getBytes().length)); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); DataOutputStream printout = new DataOutputStream(connection.getOutputStream()); printout.writeBytes(output); printout.flush(); printout.close();
<!-- CAS 登出-->
<filter-name>CAS Single Sign Out Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Single Sign Out Filter</filter-name>
<url-pattern>/logout</url-pattern>
</filter-mapping>
这样配置拦截器 无效 是什么原因?
顺序都搞错了。。SingleSignOutFilter 要放在最前面
CAS的论坛上倒是在说CAS4.0出来以后要实现真正的单点退出,解决这种集群里面的问题,不光是考虑CAS认证中心的集群,还要考虑到各个CLIENT也可能是个集群。不过这个可能就不知道啥时候去了,我一直关心CAS的站点,都没有看到要出4.0.。。。。
发表评论
-
【原创】CAS总结之集群环境篇
2010-01-08 21:54 8507CAS的集群环 ... -
【原创】CAS总结之协议分析篇
2009-12-11 23:34 9259请见附件。附件中的动画演示了CAS1.0协议及2.0协议中Cl ... -
【原创】CAS总结之Ticket篇
2009-12-11 23:20 34566CAS的核心就是其Ticket,及其在Ticket之上的一系列 ... -
CAS总结之单点退出篇
2009-12-11 22:18 3059请见本人另一篇文章 CAS总结之单点退出篇(CAS到底有没有 ... -
【原创】CAS调研总结
2009-12-09 16:55 11040本篇文章是对JA-SIG CAS( ...
相关推荐
基于springmvc+maven+webservce+memecached 单点登陆实现,实现完全跨域、单点退出
CAS单点登录,退出后ticket失效报出异常解决办法——换jar包 把客户端的 casclient.jar 包换成我的这个。
CAS单点登出问题及解决 二 (文章摘自笔者发在自己公司内部论坛的文章:谢绝转载)
CAS单点登录,退出后ticket失效报出异常解决办法——换jar包 把客户端的 casclient.jar 包换成我的这个。
此文档自己亲手从0开始一步一步配置的详尽过程,其中包括keytool创建 、ticket、tomcat配置cas、自定义登录页面,处理服务器返回的乱码,服务退出、cas服务器返回多数据等等文档 包括SSO原理图,以及认证流程图等
手把手教你如何配置如下几项: 单点登录认证配置 配置为自定义数据库认证方式 获取登录用户名 单点退出 网站间建立信任关系 修改默认页面 超时设置 另附CAS实用网址
包含cas源码、cas使用说明文档(包含配置信息)、连接数据库所需jar包、cas服务端自定义返回值等
NULL 博文链接:https://zxs19861202.iteye.com/blog/855856
一个简单的集成了shiro+cas+pac4j的springboot项目,实现单点登录及单点退出。 包括一个cas-server服务器和一个demo客户端
JEECG BPM 单点登录说明文档(kisso集成),已经在项目中使用。
单点退出所需jar包.找了半天才找到,储存一下,以备不时之需
CAS验证这是一个实用工具,可帮助您通过CAS服务器( )验证基于Connect或Express(可能还有其他框架,甚至根本没有)的Web服务。它允许单点登录和。换句话说,如果客户端已到CAS服务器以前记录的,该库将允许您服务...
此文档自己亲手从0开始一步一步配置的详尽过程,其中包括keytool创建 、ticket、tomcat配置cas、自定义登录页面,处理服务器返回的乱码,服务退出、cas服务器返回多数据等等文档
-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置 --> org.jasig.cas.client.session.SingleSignOutHttpSessionListener <!-- 该过滤器用于实现单点登出功能,可选配置。 --> ...
单点登陆系统架构的实现方案 包括服务器程序及子系统例子程序 服务器段配置数据库链接池后运行run.bat启动 子系统例子,放入tomcat后配置cas_domain_configure.properties 和cas_client_tcp_pool.properties并修改/...
CAS(中央身份验证服务)是Web的单点登录/单点退出协议。 我们假设您已经熟悉CAS协议,如果不熟悉,请在使用前阅读本。 安装 npm install connect-cas2 特征 非代理模式CAS登录/注销 代理模式CAS登录/注销,获取...
本人为初学者,此文档为实践过程的记录,超细致
CAS(中央身份验证服务)是Web的单点登录/单点退出协议。 我们假设您已经熟悉CAS协议,如果不熟悉,请在使用前阅读本。安装$ npm install http-cas-client特征唱歌登录(SSO) 带有axios api的CAS代理,用于POST,...
CAS单点登录整合,实现远程登录,及远程退出,与验证码,数据库读取等
退出系统 Session.Abandon(); FormsAuthentication.SignOut(); Request.Cookies.Clear(); Response.Redirect("http://localhost:8442/cas/logout?service="+ Server.UrlEncode("http://"+Request.Url.Authority))...