tomcat版本: tomcat-8.0.29html
jdk版本: jdk1.8.0_65java
cas版本:node
cas4.1.3 (4.x還在開發過程當中不是很穩定,迭代比較快,也會有些bug) mysql
cas-client-3.4.1nginx
參考來源:git
Jasig: 4.1.x JPA Ticket Registrygithub
Jasig: 4.1.3 JPA Ticket Registryweb
百度文庫: CAS JPA-Ticket存儲解決負載均衡配置spring
CSDN: cas 入門之十五:ticket 存儲方案-jpa ticket存儲 (3.x版本有點老)sql
Stackoverflow: Jasig CAS. How to enable transactions with JpaTicketRegistry?
參照如下文章首先準備好以下圖的環境
接文章(CAS (5) —— Nginx代理模式下瀏覽器訪問CAS服務器配置詳解)以後,咱們嘗試爲CAS
服務配置多個節點。
如若要想實現CAS集羣,關於RegistryTicket能夠有多種處理方式:
*注意:本篇文章主要關注的是JpaTicketRegistry這種方式再集羣環境下的解決方案
當咱們實現了用Nginx代理轉發到node-c,併成功實現app1.hoau.com與app2.hoau.com的SSO後,以node-c的配置爲例,咱們配置好node-d,而且打開nginx的Load Balancer和Sticky Session
upstream
upstream cas_server_ssl { server sso.hoau.com:8433 weight=1 srun_id=cas-tomcat-c; server sso.hoau.com:8443 weight=1 srun_id=cas-tomcat-d; jvm_route $cookie_JSESSIONID|sessionid reverse; }
*注意:以上配置中的jvm_route和srun_id,其中srun_id須要和node-c和node-d中的Catalina server.xml配置一致
server
server { listen 443; server_name proxy.sso.hoau.com; ssl on; ssl_certificate /Users/Richard/Documents/Dev/servers/cluster/nginx/keys/server.crt; ssl_certificate_key /Users/Richard/Documents/Dev/servers/cluster/nginx/keys/server.key; ssl_session_timeout 5m; ssl_protocols SSLv3 TLSv1; ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP; ssl_prefer_server_ciphers on; location / { #root html; #index index.html index.htm index.jsp; proxy_redirect off; proxy_set_header Host $host; proxy_set_header Referer $http_referer; proxy_set_header Cookie $http_cookie; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-FORWARDED-HOST $server_addr; proxy_set_header X-FORWARDED-PORT $server_port; proxy_connect_timeout 3; proxy_send_timeout 30; proxy_read_timeout 30; proxy_pass https://cas_server_ssl; } }
若是此時嘗試測試系統,可能會出現如下錯誤,緣由是由於兩個CAS Server的Session不共享,隨機生成的Ticket並不能在兩個不一樣的CAS Server環境下同時使用。
javax.servlet.ServletException: org.jasig.cas.client.validation.TicketValidationException: Ticket 'ST-1-9yxMX3RGh9helSCSwNTb-cas01.sso.hoau.com' not recognized org.jasig.cas.client.validation.AbstractTicketValidatorFilter.doFilter(AbstractTicketValidationFilter.java:227)
*注意: 這是咱們嘗試配置JpaTicketRegistry的初衷(固然也能夠經過上述的多種方式都能解決此問題)
如下是4.1.x官方文檔給出來的示例配置,在4.1.x後CAS將之前@Transactional的註解方式更改爲了下面推薦的這種aop方式,可是下面的配置對於4.1.2/4.1.3這兩個版本缺乏了一點東西,運行時可能會遇到錯誤。
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" p:driverClass="${database.driverClass:org.hsqldb.jdbcDriver}" p:jdbcUrl="${database.url:jdbc:hsqldb:mem:cas-ticket-registry}" p:user="${database.user:sa}" p:password="${database.password:}" p:initialPoolSize="${database.pool.minSize:6}" p:minPoolSize="${database.pool.minSize:6}" p:maxPoolSize="${database.pool.maxSize:18}" p:maxIdleTimeExcessConnections="${database.pool.maxIdleTime:1000}" p:checkoutTimeout="${database.pool.maxWait:2000}" p:acquireIncrement="${database.pool.acquireIncrement:16}" p:acquireRetryAttempts="${database.pool.acquireRetryAttempts:5}" p:acquireRetryDelay="${database.pool.acquireRetryDelay:2000}" p:idleConnectionTestPeriod="${database.pool.idleConnectionTestPeriod:30}" p:preferredTestQuery="${database.pool.connectionHealthQuery:select 1}" /> <bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.JpaTicketRegistry" /> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/> <util:list id="packagesToScan"> <value>org.jasig.cas.ticket</value> <value>org.jasig.cas.adaptors.jdbc</value> </util:list> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" id="jpaVendorAdapter" p:generateDdl="true" p:showSql="true" /> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" p:dataSource-ref="dataSource" p:jpaVendorAdapter-ref="jpaVendorAdapter" p:packagesToScan-ref="packagesToScan"> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">${database.dialect:org.hibernate.dialect.HSQLDialect}</prop> <prop key="hibernate.hbm2ddl.auto">create-drop</prop> <prop key="hibernate.jdbc.batch_size">${database.batchSize:1}</prop> </props> </property> </bean> <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager" p:entityManagerFactory-ref="entityManagerFactory" /> <tx:advice id="txRegistryAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="deleteTicket" read-only="false" /> <tx:method name="addTicket" read-only="false" /> <tx:method name="updateTicket" read-only="false" /> <tx:method name="getTicket" read-only="true" /> <tx:method name="getTickets" read-only="true" /> <tx:method name="sessionCount" read-only="true" /> <tx:method name="serviceTicketCount" read-only="true" /> </tx:attributes> </tx:advice> <tx:advice id="txRegistryLockingAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="getOwner" read-only="true" /> <tx:method name="acquire" read-only="false" /> <tx:method name="release" read-only="false" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="ticketRegistryOperations" expression="execution(* org.jasig.cas.ticket.registry.JpaTicketRegistry.*(..))"/> <aop:pointcut id="ticketRegistryLockingOperations" expression="execution(* org.jasig.cas.ticket.registry.support.JpaLockingStrategy.*(..))"/> <aop:advisor advice-ref="txRegistryAdvice" pointcut-ref="ticketRegistryOperations"/> <aop:advisor advice-ref="txRegistryLockingAdvice" pointcut-ref="ticketRegistryLockingOperations"/> </aop:config> <bean id="ticketRegistryCleaner" class="org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner" c:centralAuthenticationService-ref="centralAuthenticationService" c:ticketRegistry-ref="ticketRegistry" p:lock-ref="cleanerLock"/> <bean id="cleanerLock" class="org.jasig.cas.ticket.registry.support.JpaLockingStrategy" p:uniqueId="${host.name}" p:applicationId="cas-ticket-registry-cleaner" /> <bean id="jobDetailTicketRegistryCleaner" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean" p:targetObject-ref="ticketRegistryCleaner" p:targetMethod="clean" /> <bean id="triggerJobDetailTicketRegistryCleaner" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean" p:jobDetail-ref="jobDetailTicketRegistryCleaner" p:startDelay="20000" p:repeatInterval="5000000" />
*注意: 若是使用MySQL5.x以上的entityManagerFactory須要修改,這在多數論壇文章中都能找到
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" p:dataSource-ref="ticketDataSource" p:jpaVendorAdapter-ref="jpaVendorAdapter" p:packagesToScan-ref="packagesToScan"> <property name="persistenceUnitName" value="CasPU"/> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <prop key="hibernate.hbm2ddl.auto">update</prop> </props> </property> </bean>
這裏比較重要的是hibernate.dialect須要根據目標數據庫配置,另外hibernate.hbm2ddl.auto是給JPATicketRegistry賦予相應權限的,由於在服務啓動時,系統會自動檢測目標數據庫中,是否含有相應表的信息,若是沒有,則會執行ddl腳本建立表:
4.1.x版本提供了JpaLockingStrategy,來支持exclusive non-reentrant lock
This will configure the cleaner with the following defaults:
tableName = 「LOCKS」
uniqueIdColumnName = 「UNIQUE_ID」
applicationIdColumnName = 「APPLICATION_ID」
expirationDataColumnName = 「EXPIRATION_DATE」
platform = SQL92
lockTimeout = 3600 (1 hour)
MySQL
CREATE INDEX ST_TGT_FK_I ON SERVICETICKET (ticketGrantingTicket_ID); CREATE INDEX ST_TGT_FK_I ON TICKETGRANTINGTICKET (ticketGrantingTicket_ID);
以上這些內容,都能在官方文檔中找到,還有其餘相關的重要說明,在此不贅述。
*注意: 若是這樣配置完畢,而後充其服務器進行測試,會遇到如下錯誤
org.jasig.cas.web.flow.GenerateServiceTicketAction@7c10e841 in state 'generateServiceTicket' of flow 'login' -- action execution attributes were 'map[[empty]]'] with root cause javax.persistence.TransactionRequiredException: No transactional EntityManager available at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:275) at com.sun.proxy.$Proxy51.merge(Unknown Source) at org.jasig.cas.ticket.registry.JpaTicketRegistry.updateTicket(JpaTicketRegistry.java:57) at org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry$TicketDelegator.updateTicket(AbstractDistributedTicketRegistry.java:101) at org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry$TicketGrantingTicketDelegator.grantServiceTicket(AbstractDistributedTicketRegistry.java:234) at org.jasig.cas.CentralAuthenticationServiceImpl.grantServiceTicket(CentralAuthenticationServiceImpl.java:313) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:
以上的錯誤主要是由於CentralAuthenticationService.grantServiceTicket沒有transaction,咱們須要修改ticketRegistry.xml的配置文件
<tx:advice id="txRegistryAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="deleteTicket" read-only="false" /> <tx:method name="addTicket" read-only="false" /> <tx:method name="updateTicket" read-only="false" /> <tx:method name="getTicket" read-only="true" /> <tx:method name="getTickets" read-only="true" /> <tx:method name="add*" read-only="false"/> <tx:method name="delete*" read-only="false"/> <tx:method name="save*" read-only="false"/> <tx:method name="update*" read-only="false"/> <tx:method name="get*" read-only="true"/> <tx:method name="grant*" read-only="false"/> <tx:method name="validate*" read-only="true"/> <tx:method name="sessionCount" read-only="true" /> <tx:method name="serviceTicketCount" read-only="true" /> </tx:attributes> </tx:advice> <tx:advice id="txRegistryLockingAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="getOwner" read-only="true" /> <tx:method name="acquire" read-only="false" /> <tx:method name="release" read-only="false" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="ticketRegistryOperations" expression="execution(* org.jasig.cas.ticket.registry.JpaTicketRegistry.*(..))"/> <aop:pointcut id="centralAuthenticationServiceOperations" expression="execution(* org.jasig.cas.CentralAuthenticationService.*(..))"/> <aop:pointcut id="ticketRegistryLockingOperations" expression="execution(* org.jasig.cas.ticket.registry.support.JpaLockingStrategy.*(..))"/> <!-- --> <aop:advisor advice-ref="txRegistryAdvice" pointcut-ref="ticketRegistryOperations"/> <aop:advisor advice-ref="txRegistryAdvice" pointcut-ref="centralAuthenticationServiceOperations"/> <aop:advisor advice-ref="txRegistryLockingAdvice" pointcut-ref="ticketRegistryLockingOperations"/> </aop:config>
persistence manager沒有配置好或缺乏依賴的jar
18-Dec-2015 08:47:50.459 SEVERE [localhost-startStop-1] org.apache.catalina.core.StandardContext.listenerStart Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in ServletContext resource [/WEB-INF/spring-configuration/ticketRegistry.xml]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: No persistence units parsed from {classpath*:META-INF/persistence.xml} at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1572) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476) at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303) at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299) at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194) at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:960) at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:749)
*注意: 此處請不要按照錯誤提示再META-INF下增長persistence.xml文件,不然可能會出現衝突的狀況。
請用「SimpleTriggerFactoryBean」而非某些論壇裏面使用的「org.springframework.scheduling.quartz.SimpleTriggerBean」(這多是老版本配置),正確配置以下:
<bean id="periodicTicketRegistryCleanerTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean" p:jobDetail-ref="ticketRegistryCleanerJobDetail" p:startDelay="20000" p:repeatInterval="5000000" />
爲DefaultTicketRegistryCleaner加上centralAuthenticationService
老版本的DefaultTicketRegistryCleaner無需centralAuthenticationService,若是4.1.x沒有加上這個會出現錯誤:
18-Dec-2015 08:55:43.357 SEVERE [localhost-startStop-1] org.apache.catalina.core.StandardContext.listenerStart Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'ticketRegistryCleaner' defined in ServletContext resource [/WEB-INF/spring-configuration/ticketRegistry.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner]: No default constructor found; nested exception is java.lang.NoSuchMethodException: org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner.<init>() at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateBean(AbstractAutowireCapableBeanFactory.java:1099) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1044) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:504) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
正確配置以下:
<bean id="ticketRegistryCleaner" class="org.jasig.cas.ticket.registry.support.DefaultTicketRegistryCleaner" c:centralAuthenticationService-ref="centralAuthenticationService" c:ticketRegistry-ref="ticketRegistry" p:lock-ref="cleanerLock" />
請使用較高版本的com.mchange.v2.c3p0
不然會出現如下錯誤:
18-Dec-2015 09:17:49.725 SEVERE [localhost-startStop-1] org.apache.catalina.core.StandardContext.listenerStart Exception sending context initialized event to listener instance of class org.springframework.web.context.ContextLoaderListener org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in ServletContext resource [/WEB-INF/spring-configuration/ticketRegistry.xml]: Cannot resolve reference to bean 'ticketDataSource' while setting bean property 'dataSource'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'ticketDataSource' defined in ServletContext resource [/WEB-INF/spring-configuration/ticketRegistry.xml]: Error setting property values; nested exception is org.springframework.beans.NotWritablePropertyException: Invalid property 'url' of bean class [com.mchange.v2.c3p0.ComboPooledDataSource]: Bean property 'url' is not writable or has an invalid setter method. Does the parameter type of the setter match the return type of the getter? at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveReference(BeanDefinitionValueResolver.java:359) at org.springframework.beans.factory.support.BeanDefinitionValueResolver.resolveValueIfNecessary(BeanDefinitionValueResolver.java:108) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1475) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1220) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537)
正確的依賴:
<dependency> <groupId>com.mchange</groupId> <artifactId>c3p0</artifactId> <version>0.9.5.1</version> </dependency> <dependency> <groupId>com.mchange</groupId> <artifactId>mchange-commons-java</artifactId> <version>0.2.10</version> </dependency>
爲host.name指定合適的參數(如:sso.hoau.com)
2015-12-18 13:46:08,752 DEBUG [org.jasig.cas.web.support.CookieRetrievingCookieGenerator] - org.jose4j.lang.JoseException: A JWS Compact Serialization must have exactly 3 parts separated by period ('.') characters java.lang.RuntimeException: org.jose4j.lang.JoseException: A JWS Compact Serialization must have exactly 3 parts separated by period ('.') characters at org.jasig.cas.util.DefaultCipherExecutor.verifySignature(DefaultCipherExecutor.java:209) at org.jasig.cas.util.DefaultCipherExecutor.decode(DefaultCipherExecutor.java:107) at org.jasig.cas.web.support.DefaultCasCookieValueManager.obtainCookieValue(DefaultCasCookieValueManager.java:89) at org.jasig.cas.web.support.CookieRetrievingCookieGenerator.retrieveCookieValue(CookieRetrievingCookieGenerator.java:116)
Cannot subclass final class class org.jasig.cas.ticket.registry.JpaTicketRegistry
若是參照網上某些解決方案,試圖添加tx:annotation-driven
<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager" />
會出現以下錯誤
Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class [class org.jasig.cas.ticket.registry.JpaTicketRegistry]: Common causes of this problem include using a final class or a non-visible class; nested exception is java.lang.IllegalArgumentException: Cannot subclass final class class org.jasig.cas.ticket.registry.JpaTicketRegistry at org.springframework.aop.framework.CglibAopProxy.getProxy(CglibAopProxy.java:212) at org.springframework.aop.framework.ProxyFactory.getProxy(ProxyFactory.java:109) at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.createProxy(AbstractAutoProxyCreator.java:447) at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:333) at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:293) at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422)
4.1.2的bug
因爲如下或可能遇到的更多錯誤,mmoayyed建議將overlay升到4.1.3
在登錄時碰到後臺錯誤「java.lang.NullPointerException
at org.jasig.cas.ticket.support.TicketGrantingTicketExpirationPolicy.isExpired」
18-Dec-2015 17:35:00.194 SEVERE [http-nio-8443-exec-8] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [cas] in context with path [/cas] threw exception [Request processing failed; nested exception is org.springframework.webflow.execution.ActionExecutionException: Exception thrown executing org.jasig.cas.web.flow.TicketGrantingTicketCheckAction@313fe175 in state 'ticketGrantingTicketCheck' of flow 'login' -- action execution attributes were 'map[[empty]]'] with root cause java.lang.NullPointerException at org.jasig.cas.ticket.support.TicketGrantingTicketExpirationPolicy.isExpired(TicketGrantingTicketExpirationPolicy.java:131) at org.jasig.cas.ticket.AbstractTicket.isExpired(AbstractTicket.java:158) at org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry$TicketDelegator.isExpired(AbstractDistributedTicketRegistry.java:115) at org.jasig.cas.CentralAuthenticationServiceImpl.getTicket(CentralAuthenticationServiceImpl.java:525) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317) at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:190)
https://github.com/Jasig/cas/issues/1323
https://github.com/Jasig/cas/issues/1367
此處略去,請自行驗證。