SSO认证中心是CAS整个应用架构的一个极其重要的关键点,必须满足如下两点要求:
1.高可用,不允许程序发生故障。如果认证中心发生故障,整个应用群将无法登录,导致所有服务瘫痪。
2.高并发,因为所有用户的登录请求都需要经过它处理,其承担的处理量往往是相当巨大的。
其中memcached的CAS源码 MemCacheTicketRegistry.java 类如下:
/* * Licensed to Jasig under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Jasig licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a * copy of the License at the following location: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jasig.cas.ticket.registry; import java.io.IOException; import java.net.InetSocketAddress; import java.util.Arrays; import java.util.Collection; import java.util.List; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import net.spy.memcached.AddrUtil; import net.spy.memcached.MemcachedClient; import net.spy.memcached.MemcachedClientIF; import org.jasig.cas.ticket.ServiceTicket; import org.jasig.cas.ticket.Ticket; import org.jasig.cas.ticket.TicketGrantingTicket; import org.springframework.beans.factory.DisposableBean; /** * Key-value ticket registry implementation that stores tickets in memcached keyed on the ticket ID. * * @author Scott Battaglia * @author Marvin S. Addison * @since 3.3 */ public final class MemCacheTicketRegistry extends AbstractDistributedTicketRegistry implements DisposableBean { /** Memcached client. */ @NotNull private final MemcachedClientIF client; /** * TGT cache entry timeout in seconds. */ @Min(0) private final int tgtTimeout; /** * ST cache entry timeout in seconds. */ @Min(0) private final int stTimeout; /** * Creates a new instance that stores tickets in the given memcached hosts. * * @param hostnames Array of memcached hosts where each element is of the form host:port. * @param ticketGrantingTicketTimeOut TGT timeout in seconds. * @param serviceTicketTimeOutST timeout in seconds. */ public MemCacheTicketRegistry(final String[] hostnames, final int ticketGrantingTicketTimeOut, final int serviceTicketTimeOut) { try { this.client = new MemcachedClient(AddrUtil.getAddresses(Arrays.asList(hostnames))); } catch (final IOException e) { throw new IllegalArgumentException("Invalid memcached host specification.", e); } this.tgtTimeout = ticketGrantingTicketTimeOut; this.stTimeout = serviceTicketTimeOut; } /** * This alternative constructor takes time in milliseconds. * It has the timeout parameters in order to create a unique method signature. * * @param ticketGrantingTicketTimeOut TGT timeout in milliseconds. * @param serviceTicketTimeOut ST timeout in milliseconds. * @param hostnames Array of memcached hosts where each element is of the form host:port. * @see MemCacheTicketRegistry#MemCacheTicketRegistry(String[], int, int) * @deprecated This has been deprecated */ @Deprecated public MemCacheTicketRegistry(final long ticketGrantingTicketTimeOut, final long serviceTicketTimeOut, final String[] hostnames) { this(hostnames, (int) (ticketGrantingTicketTimeOut / 1000), (int) (serviceTicketTimeOut / 1000)); } /** * Creates a new instance using the given memcached client instance, which is presumably configured via * <code>net.spy.memcached.spring.MemcachedClientFactoryBean</code>. * * @param client Memcached client. * @param ticketGrantingTicketTimeOut TGT timeout in seconds. * @param serviceTicketTimeOutST timeout in seconds. */ public MemCacheTicketRegistry(final MemcachedClientIF client, final int ticketGrantingTicketTimeOut, final int serviceTicketTimeOut) { this.tgtTimeout = ticketGrantingTicketTimeOut; this.stTimeout = serviceTicketTimeOut; this.client = client; } public String getHostnames() { return hostnames; } public void setHostnames(String hostnames) { this.hostnames = hostnames; } public int getTgtTimeout() { return tgtTimeout; } public int getStTimeout() { return stTimeout; } protected void updateTicket(final Ticket ticket) { logger.debug("Updating ticket {}", ticket); try { if (!this.client.replace(ticket.getId(), getTimeout(ticket), ticket).get()) { logger.error("Failed updating {}", ticket); } } catch (final InterruptedException e) { logger.warn("Interrupted while waiting for response to async replace operation for ticket {}. " + "Cannot determine whether update was successful.", ticket); } catch (final Exception e) { logger.error("Failed updating {}", ticket, e); } } public void addTicket(final Ticket ticket) { logger.debug("Adding ticket {}", ticket); try { if (!this.client.add(ticket.getId(), getTimeout(ticket), ticket).get()) { logger.error("Failed adding {}", ticket); } } catch (final InterruptedException e) { logger.warn("Interrupted while waiting for response to async add operation for ticket {}." + "Cannot determine whether add was successful.", ticket); } catch (final Exception e) { logger.error("Failed adding {}", ticket, e); } } public boolean deleteTicket(final String ticketId) { logger.debug("Deleting ticket {}", ticketId); try { return this.client.delete(ticketId).get(); } catch (final Exception e) { logger.error("Failed deleting {}", ticketId, e); } return false; } public Ticket getTicket(final String ticketId) { try { final Ticket t = (Ticket) this.client.get(ticketId); if (t != null) { return getProxiedTicketInstance(t); } } catch (final Exception e) { logger.error("Failed fetching {} ", ticketId, e); } return null; } /** * {@inheritDoc} * This operation is not supported. * * @throws UnsupportedOperationException if you try and call this operation. */ @Override public Collection<Ticket> getTickets() { throw new UnsupportedOperationException("GetTickets not supported."); } public void destroy() throws Exception { this.client.shutdown(); } /** * @param sync set to true, if updates to registry are to be synchronized * @deprecated As of version 3.5, this operation has no effect since async writes can cause registry consistency issues. */ @Deprecated public void setSynchronizeUpdatesToRegistry(final boolean sync) {} @Override protected boolean needsCallback() { return true; } private int getTimeout(final Ticket t) { if (t instanceof TicketGrantingTicket) { return this.tgtTimeout; } else if (t instanceof ServiceTicket) { return this.stTimeout; } throw new IllegalArgumentException("Invalid ticket type"); } }
将其 MemCacheTicketRegistry.java 类改为如下代码:
/* * Licensed to Jasig under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Jasig licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a * copy of the License at the following location: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jasig.cas.ticket.registry; import java.io.IOException; import java.net.InetSocketAddress; import java.util.Arrays; import java.util.Collection; import java.util.List; import javax.validation.constraints.Min; import javax.validation.constraints.NotNull; import net.spy.memcached.AddrUtil; import net.spy.memcached.MemcachedClient; import net.spy.memcached.MemcachedClientIF; import org.jasig.cas.ticket.ServiceTicket; import org.jasig.cas.ticket.Ticket; import org.jasig.cas.ticket.TicketGrantingTicket; import org.springframework.beans.factory.DisposableBean; /** * Key-value ticket registry implementation that stores tickets in memcached keyed on the ticket ID. * * @author Scott Battaglia * @author Marvin S. Addison * @since 3.3 */ public final class MemCacheTicketRegistry extends AbstractDistributedTicketRegistry implements DisposableBean { /** Memcached client. */ @NotNull private final MemcachedClientIF client = getClient(); /** * TGT cache entry timeout in seconds. */ @Min(0) private int tgtTimeout; /** * ST cache entry timeout in seconds. */ @Min(0) private int stTimeout; private String hostname; public MemcachedClient getClient(){ try { return new MemcachedClient(AddrUtil.getAddresses(Arrays.asList(hostname))); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } protected void updateTicket(final Ticket ticket) { logger.debug("Updating ticket {}", ticket); try { if (!this.client.replace(ticket.getId(), getTimeout(ticket), ticket).get()) { logger.error("Failed updating {}", ticket); } } catch (final InterruptedException e) { logger.warn("Interrupted while waiting for response to async replace operation for ticket {}. " + "Cannot determine whether update was successful.", ticket); } catch (final Exception e) { logger.error("Failed updating {}", ticket, e); } } public void addTicket(final Ticket ticket) { logger.debug("Adding ticket {}", ticket); try { if (!this.client.add(ticket.getId(), getTimeout(ticket), ticket).get()) { logger.error("Failed adding {}", ticket); } } catch (final InterruptedException e) { logger.warn("Interrupted while waiting for response to async add operation for ticket {}." + "Cannot determine whether add was successful.", ticket); } catch (final Exception e) { logger.error("Failed adding {}", ticket, e); } } public boolean deleteTicket(final String ticketId) { logger.debug("Deleting ticket {}", ticketId); try { return this.client.delete(ticketId).get(); } catch (final Exception e) { logger.error("Failed deleting {}", ticketId, e); } return false; } public Ticket getTicket(final String ticketId) { try { final Ticket t = (Ticket) this.client.get(ticketId); if (t != null) { return getProxiedTicketInstance(t); } } catch (final Exception e) { logger.error("Failed fetching {} ", ticketId, e); } return null; } /** * {@inheritDoc} * This operation is not supported. * * @throws UnsupportedOperationException if you try and call this operation. */ @Override public Collection<Ticket> getTickets() { throw new UnsupportedOperationException("GetTickets not supported."); } public void destroy() throws Exception { this.client.shutdown(); } /** * @param sync set to true, if updates to registry are to be synchronized * @deprecated As of version 3.5, this operation has no effect since async writes can cause registry consistency issues. */ @Deprecated public void setSynchronizeUpdatesToRegistry(final boolean sync) {} @Override protected boolean needsCallback() { return true; } private int getTimeout(final Ticket t) { if (t instanceof TicketGrantingTicket) { return this.tgtTimeout; } else if (t instanceof ServiceTicket) { return this.stTimeout; } throw new IllegalArgumentException("Invalid ticket type"); } public int getTgtTimeout() { return tgtTimeout; } public void setTgtTimeout(int tgtTimeout) { this.tgtTimeout = tgtTimeout; } public int getStTimeout() { return stTimeout; } public void setStTimeout(int stTimeout) { this.stTimeout = stTimeout; } public String getHostname() { return hostname; } public void setHostname(String hostname) { this.hostname = hostname; } }
cas单点登录架构 ticket 票据存储方式为 memcached(单节点配置memcached满足cas存储票据),具体ticketRegistry.xml配置如下:
修改cas-server-webapp工程中ticketRegistry.xml文件
1)替换掉DefaultTicketRegistry,改为自己需要的MemCacheTicketRegistry类名
<!-- Ticket Registry --> <bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.DefaultTicketRegistry" />
修改为
<bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.RedisRegistry" p:stTimeout="5" p:tgtTimeout="60" p:hostname="127.0.0.1:11211"/>
2)注释掉ticketRegistryCleaner、jobDetailTicketRegistryCleaner以及triggerJobDetailTicketRegistryCleaner的bean相关所有定义
<!--Quartz --> <!-- TICKET REGISTRY CLEANER --> <bean id="ticketRegistryCleaner" class="org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner" p:ticketRegistry-ref="ticketRegistry" p:logoutManager-ref="logoutManager" /> <bean id="jobDetailTicketRegistryCleaner" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean" p:targetObject-ref="ticketRegistryCleaner" p:targetMethod="clean" /> <bean id="triggerJobDetailTicketRegistryCleaner" class="org.springframework.scheduling.quartz.SimpleTriggerBean" p:jobDetail-ref="jobDetailTicketRegistryCleaner" p:startDelay="20000" p:repeatInterval="5000000" />
ticket票据的存储由TicketRegistry定义,缺省是DefaultTicketRegistry类,即JVM内存方式实现,我们可以定义外置存储方式,让多个实例共用这个存储,以达到共享目的。有什么不懂的可以在素文宅 www.yoodb.com 留言,和本人进行沟通大家相互学习。