cas4.2.7 集群服務搭建
cas服務端集群,網上資料很多,無非就是session共享,ticket共享。 但是session共享是必須的嗎?或者能實現集群嗎?
實踐:
1. ticket共享,直接上代碼
package org.jasig.cas.ticket; import java.util.Collection; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; import org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry;import org.springframework.beans.factory.DisposableBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; /** * @authorzlx * @Description: redis管理ticket * @date 2018年4月9日 下午3:28:12 */ @Component("redisTicketRegistry") public class RedisTicketRegistry extends AbstractDistributedTicketRegistry implements DisposableBean { /**管理ticket key,避免使用redis keys命令*/ private final String TICKET_KEY_MANAGER = "ticket_key_manager"; @Autowiredprivate RedisTemplate<String, Ticket> redisTemplate; @Autowired private StringRedisTemplate stringRedisTemplate; //默認失效時間 /小時 private int timeout = 8; @Override public void updateTicket(final Ticket ticket) { logger.debug("Updating ticket {}", ticket); try { this.redisTemplate.boundValueOps(ticket.getId()).set(ticket); this.redisTemplate.expire(ticket.getId(), timeout, TimeUnit.HOURS); } catch (final Exception e) { logger.error("Failed updating {}", ticket, e); } } @Override public void addTicket(final Ticket ticket) { logger.debug("Adding ticket {}", ticket); try { this.redisTemplate.boundValueOps(ticket.getId()).set(ticket); this.redisTemplate.expire(ticket.getId(), timeout, TimeUnit.HOURS); this.stringRedisTemplate.boundSetOps(TICKET_KEY_MANAGER).add(ticket.getId()); } catch (final Exception e) { logger.error("Failed adding {}", ticket, e); } } @Override public int deleteTicket(final String ticketId) { int count = 0; logger.debug("Deleting ticket {}", ticketId); try { this.redisTemplate.delete(ticketId); this.stringRedisTemplate.boundSetOps(TICKET_KEY_MANAGER).remove(ticketId); count++; } catch (final Exception e) { logger.error("Failed deleting {}", ticketId, e); } return count; } @Override public Ticket getTicket(final String ticketId) { try { final Ticket t = this.redisTemplate.boundValueOps(ticketId).get(); if (t != null) { return getProxiedTicketInstance(t); } } catch (final Exception e) { logger.error("Failed fetching {} ", ticketId, e); } return null; } @Override public boolean deleteSingleTicket(String ticketId) { logger.debug("Deleting Single Ticket {}", ticketId); try { this.redisTemplate.delete(ticketId); this.stringRedisTemplate.boundSetOps(TICKET_KEY_MANAGER).remove(ticketId); return true; } catch (final Exception e) { logger.error("Failed deleting {}", ticketId, e); } return false; } @Override public Collection<Ticket> getTickets() { Set<Ticket> tickets = new HashSet<Ticket>(); Set<String> keys = this.stringRedisTemplate.boundSetOps(TICKET_KEY_MANAGER).members(); for (String key : keys) { Ticket ticket = this.redisTemplate.boundValueOps(key).get(); if (ticket == null) { this.stringRedisTemplate.boundSetOps(TICKET_KEY_MANAGER).remove(key); } else { tickets.add(ticket); } } return tickets; } @Override public void destroy() throws Exception { // TODO Auto-generated method stub } @Override protected boolean needsCallback() { // TODO Auto-generated method stub return false; } }
deployerConfigContext.xml中變更:
<!-- <alias name="defaultTicketRegistry" alias="ticketRegistry" /> -->
<alias name="redisTicketRegistry" alias="ticketRegistry" />
ticket共享配好後,發布代碼。 測試發現有時候cas集群有效。有時候報錯。錯誤如下:
2018-05-14 16:48:11,841 ERROR [org.jasig.cas.util.WebflowCipherExecutor] - <Unable to correctly extract the Initialization Vector or ciphertext.> org.apache.shiro.crypto.CryptoException: Unable to correctly extract the Initialization Vector or ciphertext. at org.apache.shiro.crypto.JcaCipherService.decrypt(JcaCipherService.java:378) ~[shiro-core-1.2.6.jar:1.2.6] at org.jasig.cas.util.BinaryCipherExecutor.decode(BinaryCipherExecutor.java:102) ~[cas-server-core-util-4.2.7.jar:4.2.7] at org.jasig.cas.util.BinaryCipherExecutor.decode(BinaryCipherExecutor.java:1) ~[cas-server-core-util-4.2.7.jar:4.2.7] at org.jasig.cas.web.flow.CasWebflowCipherBean.decrypt(CasWebflowCipherBean.java:44) ~[cas-server-webapp-support-4.2.7.jar:4.2.7] at org.jasig.spring.webflow.plugin.EncryptedTranscoder.decode(EncryptedTranscoder.java:105) ~[spring-webflow-client-repo-1.0.0.jar:1.0.0] at org.jasig.spring.webflow.plugin.ClientFlowExecutionRepository.getFlowExecution(ClientFlowExecutionRepository.java:90) ~[spring-webflow-client-repo-1.0.0.jar:1.0.0] at org.springframework.webflow.executor.FlowExecutorImpl.resumeExecution(FlowExecutorImpl.java:168) ~[spring-webflow-2.4.2.RELEASE.jar:2.4.2.RELEASE] at org.springframework.webflow.mvc.servlet.FlowHandlerAdapter.handle(FlowHandlerAdapter.java:228) ~[spring-webflow-2.4.2.RELEASE.jar:2.4.2.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:961) ~[spring-webmvc-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:895) ~[spring-webmvc-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:967) ~[spring-webmvc-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:869) ~[spring-webmvc-4.2.8.RELEASE.jar:4.2.8.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:648) ~[servlet-api.jar:?] at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:843) ~[spring-webmvc-4.2.8.RELEASE.jar:4.2.8.RELEASE] at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) ~[servlet-api.jar:?] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:230) ~[catalina.jar:8.5.0] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[catalina.jar:8.5.0] at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-websocket.jar:8.5.0] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[catalina.jar:8.5.0] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[catalina.jar:8.5.0] at org.apereo.cas.security.ResponseHeadersEnforcementFilter.doFilter(ResponseHeadersEnforcementFilter.java:238) ~[cas-server-security-filter-2.0.6.jar:2.0.6] at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[catalina.jar:8.5.0] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[catalina.jar:8.5.0] at org.apereo.cas.security.RequestParameterPolicyEnforcementFilter.doFilter(RequestParameterPolicyEnforcementFilter.java:261) ~[cas-server-security-filter-2.0.6.jar:2.0.6] at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[catalina.jar:8.5.0] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[catalina.jar:8.5.0] at org.jasig.inspektr.common.web.ClientInfoThreadLocalFilter.doFilter(ClientInfoThreadLocalFilter.java:62) ~[inspektr-common-1.3.GA.jar:1.3.GA] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[catalina.jar:8.5.0] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[catalina.jar:8.5.0] at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[catalina.jar:8.5.0] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[catalina.jar:8.5.0] at org.springframework.session.web.http.SessionRepositoryFilter.doFilterInternal(SessionRepositoryFilter.java:164) ~[spring-session-1.2.2.RELEASE.jar:?] at org.springframework.session.web.http.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:80) ~[spring-session-1.2.2.RELEASE.jar:?] at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:346) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:262) ~[spring-web-4.2.8.RELEASE.jar:4.2.8.RELEASE] at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:192) ~[catalina.jar:8.5.0] at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:165) ~[catalina.jar:8.5.0] at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:198) ~[catalina.jar:8.5.0] at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:108) ~[catalina.jar:8.5.0] at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:522) ~[catalina.jar:8.5.0] at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) ~[catalina.jar:8.5.0] at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) ~[catalina.jar:8.5.0] at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:620) ~[catalina.jar:8.5.0] at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) ~[catalina.jar:8.5.0] at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) ~[catalina.jar:8.5.0] at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:1096) ~[tomcat-coyote.jar:8.5.0] at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) ~[tomcat-coyote.jar:8.5.0] at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:760) ~[tomcat-coyote.jar:8.5.0] at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1480) ~[tomcat-coyote.jar:8.5.0] at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) [?:1.8.0_77] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) [?:1.8.0_77] at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-util.jar:8.5.0] at java.lang.Thread.run(Thread.java:745) [?:1.8.0_77] Caused by: java.lang.NullPointerException at java.lang.System.arraycopy(Native Method) ~[?:1.8.0_77] at org.apache.shiro.crypto.JcaCipherService.decrypt(JcaCipherService.java:370) ~[shiro-core-1.2.6.jar:1.2.6] ... 60 more
查資料都說要用tomcat redis session manager實現集群tomcat的session共享
那就根據資料配置好tomcat redis session manager,使用壓力測試(集群切換分發更頻繁)還是一樣一部分成功一部分失敗。也使用spring redis session試過了還是一樣。
後來發現了一篇文章:https://blog.csdn.net/eguid_1/article/details/51444009
仔細分析了下spring web flow的源碼,確定了問題就是出在webflow流程控制上,使用組播方式實現session復制也無濟於事(也配過了,確實沒有效果),修改spring webflow的源碼難度太大,繞過webflow登錄流程代碼改動太大。
為了尋找到更好的解決方案,繼續分析、跟蹤cas源碼,從最底層錯誤開始分析
1. JcaCipherService.java:370
2. BinaryCipherExecutor.java:102
3. CasWebflowCipherBean.java:44
這一步發現調用的CipherExecutor是可以選擇的,默認使用的為BinaryCipherExecutor。既然BinaryCipherExecutor裏報錯那就換NoOpCipherExecutor試試看。因為NoOpCipherExecutor的encode和decode方法的參數和返回值是String類型
而CasWebflowCipherBean調用時傳的是byte[]:
@Override public byte[] decrypt(final byte[] bytes) { return webflowCipherExecutor.decode(bytes); }
那麽就自定義一個CipherExecutor,代碼如下:
package org.jasig.cas.util; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; /** * No-Op cipher executor that does nothing for encryption/decryption. * @author Misagh Moayyed * @since 4.1 */ @Component("noOpByteCipherExecutor") public final class NoOpByteCipherExecutor extends AbstractCipherExecutor<byte[], byte[]> { private static final Logger LOGGER = LoggerFactory.getLogger(NoOpByteCipherExecutor.class); /** * Instantiates a new No-Op cipher executor. * Issues a warning on safety. */ public NoOpByteCipherExecutor() { super(NoOpByteCipherExecutor.class.getName()); LOGGER.warn("[{}] does no encryption and may NOT be safe in a production environment. " + "Consider using other choices, such as [{}] that handle encryption, signing and verification of " + "all appropriate values.", this.getClass().getName(), BaseStringCipherExecutor.class.getName()); } @Override public byte[] encode(final byte[] value) { return value; } @Override public byte[] decode(final byte[] value) { return value; } }
然後在cas-servlet.xml中配置:
<!-- 自定義CipherExecutor --> <bean id="loginFlowCipherBean" class="org.jasig.cas.web.flow.CasWebflowCipherBean" > <constructor-arg ref="noOpByteCipherExecutor" />
</bean>
要實現集群單點登錄還需要在deployerConfigContext.xml增加如下配置:
<!-- 註入noOpCookieValueManager -->
<bean id="ticketGrantingTicketCookieGenerator" class="org.jasig.cas.web.support.TGCCookieRetrievingCookieGenerator">
<constructor-arg ref="noOpCookieValueManager" />
</bean>
至此實現了集群下的單點登錄功能。
版權聲明:本文為博主原創文章,轉載需註明出處。http://www.cnblogs.com/bryanx/p/9044473.html
cas4.2.7 集群服務搭建