cas4.0服务端集群


cas服务端集群,相信网上资料也蛮多的,无非就是session共享,ticket共享


关于session共享 上一篇tomcat redis session manager已经给出了具体的解决方案,这里主要说一下ticket共享问题
首先定义自己的ticketRegistory 如下

public class RedisTicketRegistry extends AbstractDistributedTicketRegistry {

	private static final Logger logger = LoggerFactory.getLogger(RedisTicketRegistry.class);
	
	//st ticket 最大空闲时间
	private int stMaxFreeTime;
	//tgt ticket 最大空闲时间
	private int tgtMaxFreeTime;
	
	private RedisManager redisManager;
	
	/**
	 * 添加票据
	 * Description 
	 * @param ticket 
	 * @see org.jasig.cas.ticket.registry.TicketRegistry#addTicket(org.jasig.cas.ticket.Ticket)
	 */
	@Override
	public void addTicket(Ticket ticket) {
		// TODO Auto-generated method stub
		String key = ticket.getId();
		int seconds = 0; 
		if(ticket instanceof TicketGrantingTicket){
			seconds = this.getTgtMaxFreeTime();
		} else {
			seconds = this.getStMaxFreeTime();
		}
		logger.debug("---------------add Tikcet into redis-------------------  start");
		this.getRedisManager().set(SerializeUtils.serialize(key), SerializeUtils.serialize(ticket),seconds);
		logger.debug("---------------add Ticket into redis-------------------  end");
	}

	/**
	 * 删除票据
	 * Description 
	 * @param ticketId
	 * @return 
	 * @see org.jasig.cas.ticket.registry.TicketRegistry#deleteTicket(java.lang.String)
	 */
	@Override
	public boolean deleteTicket(String ticketId) {
		// TODO Auto-generated method stub
		if(ticketId==null){
			return false;
		}
		logger.debug("----------------delete Ticket from redis-------------- start");
		this.getRedisManager().del(SerializeUtils.serialize(ticketId));
		logger.debug("----------------delete Ticket from redis-------------- end");
		return true;
	}

	/**
	 * 获取票据
	 * Description 
	 * @param ticketId
	 * @return 
	 * @see org.jasig.cas.ticket.registry.TicketRegistry#getTicket(java.lang.String)
	 */
	@Override
	public Ticket getTicket(String ticketId) {
		// TODO Auto-generated method stub
		logger.debug("---------------get Ticket byte[] ---------------- start");
		byte []tempArr = this.getRedisManager().get(SerializeUtils.serialize(ticketId));
		Ticket ticket = null;
		if(tempArr!=null){
			logger.debug("--------------------deserialize to Ticket--------------- start");
			ticket = (Ticket) SerializeUtils.deserialize(tempArr);
		}
		logger.debug("-----------------get Tikcet -------------------- end");
		return ticket;
	}

	
	@Override
	public Collection
    
    
     
      getTickets() {
		// TODO Auto-generated method stub
		throw new UnsupportedOperationException("----------GetTickets not supported----------------"); 
	}

	@Override
	protected boolean needsCallback() {
		// TODO Auto-generated method stub
		return false;
	}

	/**
	 * 修改票据
	 * Description 
	 * @param ticket 
	 * @see org.jasig.cas.ticket.registry.AbstractDistributedTicketRegistry#updateTicket(org.jasig.cas.ticket.Ticket)
	 */
	@Override
	protected void updateTicket(Ticket ticket) {
		// TODO Auto-generated method stub
		this.addTicket(ticket);
	}


	public int getStMaxFreeTime() {
		return stMaxFreeTime;
	}

	public void setStMaxFreeTime(int stMaxFreeTime) {
		this.stMaxFreeTime = stMaxFreeTime;
	}

	public int getTgtMaxFreeTime() {
		return tgtMaxFreeTime;
	}

	public void setTgtMaxFreeTime(int tgtMaxFreeTime) {
		this.tgtMaxFreeTime = tgtMaxFreeTime;
	}

	public RedisManager getRedisManager() {
		return redisManager;
	}

	public void setRedisManager(RedisManager redisManager) {
		this.redisManager = redisManager;
	}
	
}
    
    

然后修改ticketRegistory.xml 文件 屏蔽掉

 <!--  <bean id="ticketRegistry" class="org.jasig.cas.ticket.registry.DefaultTicketRegistry" /> -->
	
	<!-- 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" /> -->

添加
  <bean id="ticketRegistry" class="com.cn.instai.cas.registry.RedisTicketRegistry" 
  	p:redisManager-ref="redisManager"
  	p:stMaxFreeTime="${st.timeToKillInSeconds}"
  	p:tgtMaxFreeTime="${tgt.timeToKillInSeconds}"
  />

${st.timeToKillInSeconds} 与  <span style="font-family: Arial, Helvetica, sans-serif;">${tgt.timeToKillInSeconds} 根据具体情况赋值</span>

到此 ticket的共享完成 

但是测试发现 单点登出虽然ticket是会清楚  但没法单点登出成功  调试发现  以这种方式共享ticket时候  logout时候没有向请求系统发送logoutRequest请求

4.0采用的是logout-webflow 流程结构,查看logout-webflow.xml
<action-state id="terminateSession">
    <evaluate expression="terminateSessionAction.terminate(flowRequestContext)" />
    <transition to="doLogout" />
  </action-state>
找到开始节点 注意 主要登出业务处理地方  刚开始被我忽略了,一直跟着logoutAction源码   最后发现原来处理都是在terminateSession

cas-server.xml中找到 id为terminateSessionAction的bean   类为org.jasig.cas.web.flow.TerminateSessionAction
<evaluate expression="terminateSessionAction.terminate(flowRequestContext)" />这一句 其实就是调用 TerminateSessionAction 的terminate方法
代码如下
public Event terminate(final RequestContext context) {
        // in login's webflow : we can get the value from context as it has already been stored
        String tgtId = WebUtils.getTicketGrantingTicketId(context);
        // for logout, we need to get the cookie's value
        if (tgtId == null) {
            final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
            tgtId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);
        }
        if (tgtId != null) {
            WebUtils.putLogoutRequests(context, this.centralAuthenticationService.destroyTicketGrantingTicket(tgtId));
        }
        final HttpServletResponse response = WebUtils.getHttpServletResponse(context);
        this.ticketGrantingTicketCookieGenerator.removeCookie(response);
        this.warnCookieGenerator.removeCookie(response);
        return this.eventFactorySupport.success(this);
}

发现 centralAuthenticationService.destroyTicketGrantingTicket 发现原来是调用了 centralAuthenticationService的销毁 tgt票据的方法
查看该方法
    public List
     
     
      
       destroyTicketGrantingTicket(final String ticketGrantingTicketId) {
        Assert.notNull(ticketGrantingTicketId);

        logger.debug("Removing ticket [{}] from registry.", ticketGrantingTicketId);
        final TicketGrantingTicket ticket = this.ticketRegistry.getTicket(ticketGrantingTicketId,
                TicketGrantingTicket.class);

        if (ticket == null) {
            logger.debug("TicketGrantingTicket [{}] cannot be found in the ticket registry.", ticketGrantingTicketId);
            return Collections.emptyList();
        }

        logger.debug("Ticket found. Processing logout requests and then deleting the ticket...");
        //这里就是调用logoutManager的执行logout方法
        final List
      
      
       
        logoutRequests = logoutManager.performLogout(ticket);
        this.ticketRegistry.deleteTicket(ticketGrantingTicketId);

        return logoutRequests;
    }
      
      
     
     
查看 logoutManager的performLogout方法
 public List
      
      
       
        performLogout(final TicketGrantingTicket ticket) {
        final Map
       
       
        
         services;
        // synchronize the retrieval of the services and their cleaning for the TGT
        // to avoid concurrent logout mess ups
        synchronized (ticket) {
            //获取登录过的系统servie
            services = ticket.getServices();
            ticket.removeAllServices();
        }
        ticket.markTicketExpired();

        final List
        
        
         
          logoutRequests = new ArrayList
         
         
          
          ();
        // if SLO is not disabled
        if (!disableSingleSignOut) {
            // through all services
            for (final String ticketId : services.keySet()) {
                final Service service = services.get(ticketId);
                // it's a SingleLogoutService, else ignore
                if (service instanceof SingleLogoutService) {
                    final SingleLogoutService singleLogoutService = (SingleLogoutService) service;
                    // the logout has not performed already
                    if (!singleLogoutService.isLoggedOutAlready()) {
                        final LogoutRequest logoutRequest = new LogoutRequest(ticketId, singleLogoutService);
                        // always add the logout request
                        logoutRequests.add(logoutRequest);
                        final RegisteredService registeredService = servicesManager.findServiceBy(service);
                        // the service is no more defined, or the logout type is not defined or is back channel
                        if (registeredService == null || registeredService.getLogoutType() == null
                                || registeredService.getLogoutType() == LogoutType.BACK_CHANNEL) {
                            // perform back channel logout
                            if (performBackChannelLogout(logoutRequest)) {
                                logoutRequest.setStatus(LogoutRequestStatus.SUCCESS);
                            } else {
                                logoutRequest.setStatus(LogoutRequestStatus.FAILURE);
                                LOGGER.warn("Logout message not sent to [{}]; Continuing processing...",
                                        singleLogoutService.getId());
                            }
                        }
                    }
                }
            }
        }

        return logoutRequests;
    }
         
         
        
        
       
       
      
      
发现  services = ticket.getServices(); 如果采用默认的ticketRegistry 可以获取到所有service 而如果采用RedisTicketRegistry获取到的services为空的

这里 总算是找到导致没法单点登出的原因了 两种redisTicketRegistry里面保存的TGT票据 不包含 客户端登录过的servie信息  

于是查看 ServiceValidateController 开始我以为是在认证st票据的时候 存储了对应的servie信息 结果好像没发现

后来看到 C entralAuthenticationServiceImpl中的grantServiceTicket方法

//TGT票据对授权ST票据
 final ServiceTicket serviceTicket = ticketGrantingTicket.grantServiceTicket(generatedServiceTicketId, service,
                this.serviceTicketExpirationPolicy, credentials != null);
        this.serviceTicketRegistry.addTicket(serviceTicket);

TGT票据grantServiceTicket方法
 public synchronized ServiceTicket grantServiceTicket(final String id,
        final Service service, final ExpirationPolicy expirationPolicy,
        final boolean credentialsProvided) {
        final ServiceTicket serviceTicket = new ServiceTicketImpl(id, this,
            service, this.getCountOfUses() == 0 || credentialsProvided,
            expirationPolicy);

        updateState();

        final List
     
     
      
       authentications = getChainedAuthentications();
        service.setPrincipal(authentications.get(authentications.size()-1).getPrincipal());
        //把service信息 存储到services中
        this.services.put(id, service);

        return serviceTicket;
    }
     
     

断点比较两种票据存储方式的区别 发现当使用redisTicketRegistry是 这里对TGT票据services的修改 不会更新到redis中
于是自定义了一个 centralAuthenticationService的实现类 在TGT票据授权ST票据后 对TGT票据做了一下刷新
即在ticketGrantingTicket.grantServiceTicket后面增加了一行代码
this.serviceTicketRegistry.addTicket(ticketGrantingTicket);
这里由于redis key相同会覆盖 实际效果和更新一样

到此cas服务端的集群完成
      
希望大神们能给出更好的解决思路

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值