• -------------------------------------------------------------
  • ====================================

cas4.2.7 集群服务搭建

技能 dewbay 6年前 (2019-04-12) 2210次浏览 已收录 0个评论 扫描二维码

cas服务端集群,网上资料很多,无非就是 session 共享,ticket 共享。 但是 session 共享是必须的吗?或者能实现集群吗?

实践:

1. ticket 共享,直接上代码

cas4.2.7 集群服务搭建
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;

/**
 * @author zlx
 * @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";
    @Autowired
    private 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;
    }
    
}
cas4.2.7 集群服务搭建

deployerConfigContext.xml 中变更:

    <!-- <alias name="defaultTicketRegistry" alias="ticketRegistry" /> -->
    <alias name="redisTicketRegistry" alias="ticketRegistry" />

ticket 共享配好后,发布代码。 测试发现有时候cas集群有效。有时候报错。错误如下:

cas4.2.7 集群服务搭建
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
cas4.2.7 集群服务搭建

查资料都说要用 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,代码如下:

cas4.2.7 集群服务搭建
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;
    }
}
cas4.2.7 集群服务搭建

然后在 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


露水湾 , 版权所有丨如未注明 , 均为原创丨本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:cas4.2.7 集群服务搭建
喜欢 (0)
[]
分享 (0)
关于作者:
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址