很多童鞋对单点登出不是很理解,下面我们来看看单点登出到底做了什么东西,
我们来看看怎么从配置到代码的。
1)web.xml
com.bingo.tfp.web.init.SafeDispatcherServlet
<servlet-mapping>
<servlet-name>cas</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
从上面可以知道,所有/logout的请求都交给SafeDispatcherServlet去分发了,查看代码可以知道这个Servlet只是对 org.springframework.web.servlet.DispatcherServlet一次包装,将所有请求都交给 org.springframework.web.servlet.DispatcherServlet去处理了。
2)cas-servlet.xml
handlerMappingC的bean里面有一段配置:
<prop key="/logout">
logoutController
</prop>
也就是说,所有/logout的请求,都交给一个beanid为logoutController的Bean去处理了,那么我们看看com.bingo.tfp.web.LogoutController到底做了什么事情,
<bean id="logoutController" class="com.bingo.tfp.web.LogoutController"
p:centralAuthenticationService-ref="centralAuthenticationService"
p:logoutView="casLogoutView"
p:warnCookieGenerator-ref="warnCookieGenerator"
p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator"
p:followServiceRedirects="true"/>
我们看看源码是怎么操作的:
[java] view plain copy print ?
- protected ModelAndView handleRequestInternal(
- final HttpServletRequest request, final HttpServletResponse response)
- throws Exception {
-
- final String ticketGrantingTicketId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);
-
- final String service = request.getParameter("service");
-
-
- if (ticketGrantingTicketId != null) {
-
- this.centralAuthenticationService
- .destroyTicketGrantingTicket(ticketGrantingTicketId);
-
- this.ticketGrantingTicketCookieGenerator.removeCookie(response);
-
- this.warnCookieGenerator.removeCookie(response);
- }
-
- if (this.followServiceRedirects && service != null) {
- return new ModelAndView(new RedirectView(service));
- }
-
- return new ModelAndView(this.logoutView);
- }
protected ModelAndView handleRequestInternal(
final HttpServletRequest request, final HttpServletResponse response)
throws Exception {
//取得TGT_ID
final String ticketGrantingTicketId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);
// 取得service参数数据,这个参数是可选参数
final String service = request.getParameter("service");
//如果TGT不为空
if (ticketGrantingTicketId != null) {
//那么在centralAuthenticationService中销毁
this.centralAuthenticationService
.destroyTicketGrantingTicket(ticketGrantingTicketId);
//ticketGrantingTicketCookieGenerator 中销毁cookie
this.ticketGrantingTicketCookieGenerator.removeCookie(response);
//warnCookieGenerator 中销毁
this.warnCookieGenerator.removeCookie(response);
}
// 如果参数:followServiceRedirects为true 同时service不会空的时候,跳转到service指定的URL
if (this.followServiceRedirects && service != null) {
return new ModelAndView(new RedirectView(service));
}
//否则,跳转到logoutView指定的页面
return new ModelAndView(this.logoutView);
}
是不是很简单,那么有童鞋要问了,那么时候访问客户端呢?不要着急,我们在来看看
[java] view plain copy print ?
- this.centralAuthenticationService
- .destroyTicketGrantingTicket(ticketGrantingTicketId);
this.centralAuthenticationService
.destroyTicketGrantingTicket(ticketGrantingTicketId);
做了什么事情:
[java] view plain copy print ?
- public void destroyTicketGrantingTicket(final String ticketGrantingTicketId) {
-
- Assert.notNull(ticketGrantingTicketId);
-
- if (log.isDebugEnabled()) {
- log.debug("Removing ticket [" + ticketGrantingTicketId + "] from registry.");
- }
-
- final TicketGrantingTicket ticket = (TicketGrantingTicket) this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
-
- if (ticket == null) {
- return;
- }
-
- if (log.isDebugEnabled()) {
- log.debug("Ticket found. Expiring and then deleting.");
- }
-
- ticket.expire();
-
- this.ticketRegistry.deleteTicket(ticketGrantingTicketId);
- }
public void destroyTicketGrantingTicket(final String ticketGrantingTicketId) {
//断言参数不能空
Assert.notNull(ticketGrantingTicketId);
if (log.isDebugEnabled()) {
log.debug("Removing ticket [" + ticketGrantingTicketId + "] from registry.");
}
// 从票据仓库中取得TGT票据
final TicketGrantingTicket ticket = (TicketGrantingTicket) this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
//如果票据为空,则直接返回
if (ticket == null) {
return;
}
if (log.isDebugEnabled()) {
log.debug("Ticket found. Expiring and then deleting.");
}
//叫票据注销,也就是设置为期满(或者叫做过期)
ticket.expire();
//在票据仓库中删除该票据
this.ticketRegistry.deleteTicket(ticketGrantingTicketId);
}
很奇怪是不是,居然还没有请求客户端的东西,别急嘛。我们再来看看
[java] view plain copy print ?
- ticket.expire();
ticket.expire();
做了什么,这个是TicketGrantingTicketImpl来实现的。
public synchronized void expire() {
this.expired = true;
logOutOfServices();
}
是不是发现新大陆了?
下面是logOutOfServices()方法的源代码:
[java] view plain copy print ?
- private void logOutOfServices() {
- for (final Entry<String, Service> entry : this.services.entrySet()) {
-
- if (!entry.getValue().logOutOfService(entry.getKey())) {
- LOG.warn("Logout message not sent to [" + entry.getValue().getId() + "]; Continuing processing...");
- }
- }
- }
private void logOutOfServices() {
for (final Entry<String, Service> entry : this.services.entrySet()) {
if (!entry.getValue().logOutOfService(entry.getKey())) {
LOG.warn("Logout message not sent to [" + entry.getValue().getId() + "]; Continuing processing...");
}
}
}
哇,原来在TGT票据里面有个Entry来保存用户访问过的service对象,key是对应service的seesionID,那么是不是使用https请求客户无端呢?看看AbstractWebApplicationService类就知道咯,
[java] view plain copy print ?
- 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, true);
- }
-
- return false;
- }
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, true);
}
return false;
}
怎么样,这样是不是对单点登出细节比较清楚呢。