Bootstrap

【CAS-Server】cas协议票据认证逻辑

Login流程初始化

当客户端发起登录操作请求/login流程时,首先会调用Login流程的初始Action。初始化Action的实现类为InitialFlowSetupAction.在doExecute(requestContext context)方法中回去验证票据。

 @Override
    public Event doExecute(final RequestContext context) {
        configureCookieGenerators(context);
        configureWebflowContext(context);

        configureWebflowForPostParameters(context);
        configureWebflowForCustomFields(context);
        configureWebflowForServices(context);

		/*开始验证票据逻辑*/ 
		// 从cookie中获取TGC,
        String ticketGrantingTicketId = configureWebflowForTicketGrantingTicket(context);
        
        configureWebflowForSsoParticipation(context, ticketGrantingTicketId);
        
        return success();
    }

获取认证信息Authentication

TGT全名应该是Ticket Granting Ticket ,TGT是存储在服务端的,在cas-server中提供了多种存储方案,包括默认内存存储、redismongo,jpa,memcached等。
InitialFlowSetupActionconfigureWebflowForSsoParticipation会去根据TGC获取认证信息Authentication,并把认证信息放入到会话中Session.


    /**
     * Configure the SSO participation in webflow.
     *
     * @param context                the webflow context
     * @param ticketGrantingTicketId the TGT identifier
     */
    protected void configureWebflowForSsoParticipation(final RequestContext context, final String ticketGrantingTicketId) {
       // 获取单点登录请求对象
       SingleSignOnParticipationRequest ssoRequest = SingleSignOnParticipationRequest
        .builder()
        .requestContext(context).build();
        
        // 是否开启单点登录? 暂时还不知道其用意。
        boolean ssoParticipation = renewalStrategy.supports(ssoRequest) 
                                   && renewalStrategy.isParticipating(ssoRequest);
         
         // 如果从cookie中获取到了TGC则回去填充认证信息
        if (!ssoParticipation && StringUtils.isNotBlank(ticketGrantingTicketId)) {
            Authentication auth = this.ticketRegistrySupport.getAuthenticationFrom(ticketGrantingTicketId);
            WebUtils.putExistingSingleSignOnSessionAvailable(context, auth != null);
            // 获取认证信息
            auth = Optional.ofNullable(auth).map(Authentication::getPrincipal).orElse(NullPrincipal.getInstance());
            // 把认证信息放入到session中。
            WebUtils.putExistingSingleSignOnSessionPrincipal(context, auth);
        }
    }

由上可以看出认证信息是从ticketRegistrySupport对象中获取到的。 ticketRegistrySupport对象的具体实现默认是DefaultTicketRegistrySupport.DefaultTicketRegistrySupport中具体的TGT获取由TicketRegistry完成。

    @Override
    public Authentication getAuthenticationFrom(final String ticketGrantingTicketId) {
        if (StringUtils.isBlank(ticketGrantingTicketId)) {
            return null;
        }
        val tgt = getTicketGrantingTicket(ticketGrantingTicketId);
        return Optional.ofNullable(tgt).map(TicketGrantingTicket::getAuthentication).orElse(null);
    }

从上面DefaultTicketRegistrySupport获取Authentication的逻辑可以看出从服务端存储的TGT(TicketGrantingTicket)中可以取到认证信息Authentication.

获取票据TGT

DefaultTicketRegistrySupport调用getTicketGrantingTicket()方法可以获取服务端存储的TGT票据。在该方法中实际是使用TicketRegistry来完成获取的。

    @Override
    public TicketGrantingTicket getTicketGrantingTicket(final String ticketGrantingTicketId) {
        if (StringUtils.isBlank(ticketGrantingTicketId)) {
            return null;
        }
        val tgt = this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
        return tgt == null || tgt.isExpired() ? null : tgt;
    }

TicketRegistry是一个接口。主要用来实现TGT的操作。在其下有一个默认的实现抽象类AbstractTicketRegistry。在该抽象类中实现了部分方法的封装。其中getTicket(final String ticketId, final @NonNull Class<T> clazz)方法就在该类中进行实现,(为了后续好描述,该方法别名为:getTicket1)。getTicket1会调用Ticket getTicket(final String ticketId) (别名为:getTicket2)。getTicket2会继续调用Ticket getTicket(String ticketId, Predicate<Ticket> predicate) (别名为:getTicket3).
getTicket1getTicket2默认都由AbstractTicketRegistry实现。而getTicket3由具体的TicketRegistry实现类进行实现。比如将TGT存入redisRedisTicketRegistry实现类。

  • getTicket(final String ticketId, final @NonNull Class<T> clazz)的实现方法体, 该方法主要做了类型校验和转换功能。
    @Override
    public <T extends Ticket> T getTicket(final String ticketId, final @NonNull Class<T> clazz) {
        val ticket = getTicket(ticketId);
        if (ticket == null) {
            return null;
        }
        if (!clazz.isAssignableFrom(ticket.getClass())) {
            throw new ClassCastException("Ticket [" + ticket.getId() + " is of type "
                + ticket.getClass() + " when we were expecting " + clazz);
        }
        return clazz.cast(ticket);
    }
  • Ticket getTicket(final String ticketId)的实现方法体如下,该方法主要做了类型过期校验,删除过期TGT操作。
    @Override
    public Ticket getTicket(final String ticketId) {
        return getTicket(ticketId, ticket -> {
        
            if (ticket != null && ticket.isExpired()) {
            	// 如果过期的话就删除吧。
                deleteSingleTicket(ticketId);
                return false;
            }
            
            return true;
        });
    }
  • getTicket(String ticketId, Predicate<Ticket> predicate)方法由具体的实现类实现,RedisTicketRegistry实现如下:

    @Override
    public Ticket getTicket(final String ticketId, final Predicate<Ticket> predicate) {
        try {
            val redisKey = getTicketRedisKey(encodeTicketId(ticketId));
            val t = this.client.boundValueOps(redisKey).get();
            if (t != null) {
                val result = decodeTicket(t);
                if (predicate.test(result)) {
                    return result;
                }
                return null;
            }
        } catch (final Exception e) {
            LOGGER.error("Failed fetching [{}]", ticketId);
            LoggingUtils.error(LOGGER, e);
        }
        return null;
    }
;