# 概述

  • CAS是Central Authentication Service的缩写,中央认证服务,一种独立开放指令协议。
  • CAS是耶鲁大学发起的一个开源项目,旨在为Web应用系统提供一种可靠的单点登录的方法.名称为 Yale CAS
  • 2004年12月,CAS正式成为JS-SIG的一个项目,项目更名为JASIG CAS
  • 2012年,JASIG与Sakai基金会合并,更名为Apereo基金会,全部的CAS也跟着更名为Apereo CAS

# CAS服务端源码概要

# 源码槽点

  • 项目基于传统的tomcat容器展开,灵活性差
  • 版本管理混乱,最新版本6.x源码不全
  • 源码兼容性差,新版本跟旧版本API不一致
  • 庞大,臃肿

# SpringWebFlow与Cas Server

  • Cas Server依赖于SpringWebFlow展开相应的流程
  • SpringWebFlow将在以后的时间里面专门解析,本次不涉及SpringWebFlow的内容

# CAS服务端登录设计

# 认证过程

# TGT的结构

public interface Ticket extends Serializable, Comparable<Ticket> {
    String getId();
    boolean isExpired();
    String getPrefix();

    /*xxx: CAS中,所有的Ticket,都与tgt有关系*/
    TicketGrantingTicket getTicketGrantingTicket();
}
public interface TicketGrantingTicket extends Ticket {
    String PREFIX = "TGT";

    Authentication getAuthentication();

    /*xxx: 签发st*/
    ServiceTicket grantServiceTicket(String id, Service service,
                                     ExpirationPolicy expirationPolicy,
                                     boolean credentialProvided,
                                     boolean onlyTrackMostRecentSession);

    /*xxx: 获取所有签发了st的服务*/
    Map<String, Service> getServices();
}
@Entity
@Table(name = "TICKETGRANTINGTICKET")
public class TicketGrantingTicketImpl extends AbstractTicket implements TicketGrantingTicket {
    
}

# TGT的颁发

/*xxx: 生成tgt的action */
public class CreateTicketGrantingTicketAction extends AbstractAction {
    @Override
    public Event doExecute(final RequestContext context) {
        /*xxx: 获取已有tgt*/
        final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);
        /*xxx: 生成tgt的关键方法 */
        /*xxx: tgt与认证结果是有一定的关系的*/
        final TicketGrantingTicket tgt = createOrUpdateTicketGrantingTicket(authenticationResult, authentication, ticketGrantingTicket);
        /*xxx: 将生成的 tgt,放在 flow-scope中,后续的 sendtgt,会将之,取出来,并以 tgc的名称,写入 cookie中 */
        WebUtils.putTicketGrantingTicketInScopes(context, tgt);
        /*xxx: 省略其它抽象...*/
    }

    protected TicketGrantingTicket createOrUpdateTicketGrantingTicket(final AuthenticationResult authenticationResult,
                                                                      final Authentication authentication, final String ticketGrantingTicket) {
        final TicketGrantingTicket tgt;
        /*xxx: 省略其它抽象...*/
        tgt = this.centralAuthenticationService.createTicketGrantingTicket(authenticationResult);
        return tgt;
    }
}
public interface CentralAuthenticationService {
    /*xxx: 生成tgt的关键接口 */
    TicketGrantingTicket createTicketGrantingTicket(AuthenticationResult authenticationResult)
            throws AuthenticationException, AbstractTicketException;
}

public class DefaultCentralAuthenticationService extends AbstractCentralAuthenticationService {
    /*xxx: 生成tgt*/
    public TicketGrantingTicket createTicketGrantingTicket(final AuthenticationResult authenticationResult)
            throws AuthenticationException, AbstractTicketException {
        /*xxx: 获取认证信息*/
        final Authentication authentication = authenticationResult.getAuthentication();
        /*xxx: 获取服务信息*/
        final Service service = authenticationResult.getService();
        /*xxx: 进行服务授权验证,略*/

        final TicketGrantingTicketFactory factory = (TicketGrantingTicketFactory) this.ticketFactory.get(TicketGrantingTicket.class);
        /*xxx: 通过tgt工厂生成的tgt */
        final TicketGrantingTicket ticketGrantingTicket = factory.create(authentication, TicketGrantingTicket.class);

        /*xxx: 生成tgt后,将之保存在 ticket注册表中 */
        this.ticketRegistry.addTicket(ticketGrantingTicket);

        return ticketGrantingTicket;
    }
}
public interface TicketGrantingTicketFactory extends TicketFactory {
    <T extends TicketGrantingTicket> T create(Authentication authentication, Class<T> clazz);
}

public class DefaultTicketGrantingTicketFactory implements TicketGrantingTicketFactory {

    /*xxx: 唯一键生成器*/
    protected UniqueTicketIdGenerator ticketGrantingTicketUniqueTicketIdGenerator;
    
    
    @Override
    public <T extends TicketGrantingTicket> T create(final Authentication authentication, final Class<T> clazz) {
        final String tgtId = produceTicketIdentifier(authentication);
        return produceTicket(authentication, tgtId, clazz);
    }

    protected String produceTicketIdentifier(final Authentication authentication) {
        /*xxx: TicketGrantingTicket.PREFIX="TGT"*/
        String tgtId = this.ticketGrantingTicketUniqueTicketIdGenerator.getNewTicketId(TicketGrantingTicket.PREFIX);
        return tgtId;
    }

    protected <T extends TicketGrantingTicket> T produceTicket(final Authentication authentication,
                                                               final String tgtId, final Class<T> clazz) {
        final TicketGrantingTicket result = new TicketGrantingTicketImpl(
                tgtId, authentication, this.ticketGrantingTicketExpirationPolicy);
        return (T) result;
    }
}

# TGT的存储(默认只提供了jpa的存储实现)

/*xxx: ticket注册表 */
public interface TicketRegistry {
    void addTicket(Ticket ticket);
}

@Component("jpaTicketRegistry")
/*xxx: 涉及到两张表的操作;TICKETGRANTINGTICKET, SERVICETICKET  */
public class JpaTicketRegistry extends AbstractDistributedTicketRegistry {
    @PersistenceContext(unitName = "ticketEntityManagerFactory")
    private EntityManager entityManager;

    @Override
    public void addTicket(final Ticket ticket) {
        entityManager.persist(ticket);
    }

}

# TGT的发送

/*xxx: 发送tgt动作*/
public class SendTicketGrantingTicketAction extends AbstractAction {
    
    @Override
    protected Event doExecute(final RequestContext context) {
        /*xxx: 从请求上下文中,获取tgtId信息 */
        final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);
        /*xxx: 从cookie中,获取tgtId信息 */
        final String ticketGrantingTicketValueFromCookie = WebUtils.getTicketGrantingTicketIdFrom(context.getFlowScope());

        /*xxx: 发送tgt*/
        this.ticketGrantingTicketCookieGenerator.addCookie(context, ticketGrantingTicketId);

        /*xxx: 如果cookie中的tgc,与当前上下文中的 tgtId不匹配,则需要进行销毁 tgt*/
        if (ticketGrantingTicketValueFromCookie != null && !ticketGrantingTicketId.equals(ticketGrantingTicketValueFromCookie)) {
            this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketValueFromCookie);
        }
        
    }
}
public class CookieRetrievingCookieGenerator extends CookieGenerator {

    private final CookieValueManager casCookieValueManager;
    
    /*xxx: 添加tgc*/
    public void addCookie(final RequestContext requestContext, final String cookieValue) {
        /*xxx: 根据cookie中的tgt,生成客户端tgt*/
        final String theCookieValue = this.casCookieValueManager.buildCookieValue(cookieValue, request);
        super.addCookie(response, theCookieValue);
    }

    /*xxx: 实例化时,指定了cookie的名称*/
    public CookieRetrievingCookieGenerator(final String name, final String path, final int maxAge, final boolean secure,
                                           final String domain, final CookieValueManager casCookieValueManager,
                                           final int rememberMeMaxAge, final boolean httpOnly) {
    }
}
@Configuration("casCookieConfiguration")
public class CasCookieConfiguration {
    
    @ConditionalOnMissingBean(name = "ticketGrantingTicketCookieGenerator")
    @Bean
    @RefreshScope
    /*xxx: tgc生成器 */
    public CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator() {
        final TicketGrantingCookieProperties tgc = casProperties.getTgc();
        final int rememberMeMaxAge = (int) Beans.newDuration(tgc.getRememberMeMaxAge()).getSeconds();
        return new TGCCookieRetrievingCookieGenerator(cookieValueManager(),
                tgc.getName(),
                tgc.getPath(),
                tgc.getDomain(),
                rememberMeMaxAge,
                tgc.isSecure(),
                tgc.getMaxAge(),
                tgc.isHttpOnly());
    }
}

public class TicketGrantingCookieProperties extends CookieProperties {
    public TicketGrantingCookieProperties() {
        super.setName("TGC");
    }
    
    /*xxx: 省略其它抽象...*/
}
public interface CookieValueManager {
    String buildCookieValue(String givenCookieValue, HttpServletRequest request);
}

/*xxx: 对cookie中的值进行加密的处理*/
public class EncryptedCookieValueManager implements CookieValueManager {
    
    private final CipherExecutor<Serializable, Serializable> cipherExecutor;
    
    @Override
    /*xxx: 将tgt拼接上客户端以及客户端代理信息,并进行加密*/
    public final String buildCookieValue(final String givenCookieValue, final HttpServletRequest request) {
        final String res = buildCompoundCookieValue(givenCookieValue, request);
        return cipherExecutor.encode(res, new Object[]{}).toString();
    }
}

public class DefaultCasCookieValueManager extends EncryptedCookieValueManager {
    @Override
    protected String buildCompoundCookieValue(final String givenCookieValue, final HttpServletRequest request) {
        final ClientInfo clientInfo = ClientInfoHolder.getClientInfo();
        final StringBuilder builder = new StringBuilder(givenCookieValue);

        builder.append('@').append(clientInfo.getClientIpAddress());
        final String userAgent = HttpRequestUtils.getHttpServletRequestUserAgent(request);

        builder.append('@').append(userAgent);
        
        return builder.toString();
    }
}

# 客户端服务--逻辑结构设计

# 客户端服务

public interface Principal extends Serializable {
    String getId();

    default Map<String, Object> getAttributes() {
        return new LinkedHashMap<>(0);
    }
}

public interface Service extends Principal {
    default boolean matches(Service service) {
        final String thisUrl = URLDecoder.decode(getId(), StandardCharsets.UTF_8.name());
        final String serviceUrl = URLDecoder.decode(service.getId(), StandardCharsets.UTF_8.name());
        return thisUrl.equalsIgnoreCase(serviceUrl);
    }
}

# 客户端服务存储器

public interface ServicesManager {
    RegisteredService save(RegisteredService registeredService);

    Collection<RegisteredService> getAllServices();

    RegisteredService delete(RegisteredService svc);
}

public abstract class AbstractServicesManager implements ServicesManager {
    /*xxx: 托管*/
    private final ServiceRegistry serviceRegistry;

    /*xxx: 本地缓存*/
    private Map<Long, RegisteredService> services = new ConcurrentHashMap<>();
}

public class DomainServicesManager extends AbstractServicesManager {
    private final Map<String, TreeSet<RegisteredService>> domains = new ConcurrentHashMap<>();

    @Override
    protected void deleteInternal(final RegisteredService service) {
        final String domain = extractDomain(service.getServiceId());
        this.domains.get(domain).remove(service);
    }
    
    /*xxx: 省略其它抽象...*/
}
public interface ServiceRegistry {
}

public class InMemoryServiceRegistry extends AbstractServiceRegistry {
    private List<RegisteredService> registeredServices = new ArrayList<>();
}

# 客户端服务授权检查

public class SetServiceUnauthorizedRedirectUrlAction extends AbstractAction {
    @Override
    protected Event doExecute(final RequestContext requestContext) throws Exception {
        final RegisteredService registeredService = WebUtils.getRegisteredService(requestContext);
        if (registeredService != null && registeredService.getAccessStrategy() != null) {
            /*xxx: 本质上:  requestContext.getFlowScope().put("unauthorizedRedirectUrl", url);*/
            WebUtils.putUnauthorizedRedirectUrlIntoFlowScope(requestContext, registeredService.getAccessStrategy().getUnauthorizedRedirectUrl());
        }
        return null;
    }
}

# service参数检查

public class DefaultLoginWebflowConfigurer extends AbstractCasWebflowConfigurer {
    /*xxx: 执行 flowScope.service != null,并获取结果*/
    protected void createHasServiceCheckDecisionState(final Flow flow) {
        createDecisionState(flow, CasWebflowConstants.STATE_ID_HAS_SERVICE_CHECK,
                "flowScope.service != null",
                CasWebflowConstants.STATE_ID_RENEW_REQUEST_CHECK,
                CasWebflowConstants.STATE_ID_VIEW_GENERIC_LOGIN_SUCCESS);
    }
}

# ST的结构

public interface ServiceTicket extends Ticket {
    String PREFIX = "ST";

    Service getService();
}

@Entity
@Table(name = "SERVICETICKET")
public class ServiceTicketImpl extends AbstractTicket implements ServiceTicket {
    @ManyToOne(targetEntity = TicketGrantingTicketImpl.class)
    private TicketGrantingTicket ticketGrantingTicket;

    @Lob
    @Column(name = "SERVICE", nullable = false, length = Integer.MAX_VALUE)
    private Service service;

    @Override
    public Authentication getAuthentication() {
        return getTicketGrantingTicket().getAuthentication();
    }
}

# ST的颁发

/*xxx: 生成st的action */
public class GenerateServiceTicketAction extends AbstractAction {
    
    @Override
    protected Event doExecute(final RequestContext context) {
        /*xxx: 获取到客户端服务信息*/
        final Service service = WebUtils.getService(context);

        /*xxx :获取上下文中的 tgt*/
        final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);
        
        /*xxx: 省略其它抽象...*/

        /*xxx: 通过tgt,为客户端服务颁发 st*/
        final ServiceTicket serviceTicketId = this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicket, service, authenticationResult);
        /*xxx: 将st保存绑定到 request中*/
        /*xxx: 本质上: map.put("serviceTicketId", serviceTicketId.getId());*/
        WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);
    }
}
public interface CentralAuthenticationService {
    /*xxx:  签发ServiceTicket的关键接口*/
    ServiceTicket grantServiceTicket(String ticketGrantingTicketId, Service service, AuthenticationResult authenticationResult)
            throws AuthenticationException, AbstractTicketException;
}

public class DefaultCentralAuthenticationService extends AbstractCentralAuthenticationService {
    
    public ServiceTicket grantServiceTicket(final String ticketGrantingTicketId, final Service service, final AuthenticationResult authenticationResult)
            throws AuthenticationException, AbstractTicketException {
        /*xxx: 获取tgt实体 */
        final TicketGrantingTicket ticketGrantingTicket = getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);

        /*xxx: 解析客户端服务*/
        final Service selectedService = resolveServiceFromAuthenticationRequest(service);
        final RegisteredService registeredService = this.servicesManager.findServiceBy(selectedService);

        /*xxx: 通过 serviceTicket工厂创建的 serviceTicket */
        final ServiceTicket serviceTicket = factory.create(ticketGrantingTicket, selectedService, credentialProvided, ServiceTicket.class);
        /*xxx: 更新tgt,内部存储了签发的service信息*/
        this.ticketRegistry.updateTicket(ticketGrantingTicket);
        /*xxx: 将st进行存储*/
        this.ticketRegistry.addTicket(serviceTicket);
    }
}
public interface ServiceTicketFactory extends TicketFactory {
    <T extends Ticket> T create(TicketGrantingTicket ticketGrantingTicket,
                                Service service,
                                boolean credentialProvided, Class<T> clazz);
}

public class DefaultServiceTicketFactory implements ServiceTicketFactory {

    private final Map<String, UniqueTicketIdGenerator> uniqueTicketIdGeneratorsForService;
    
    @Override
    public <T extends Ticket> T create(final TicketGrantingTicket ticketGrantingTicket, final Service service,
                                       final boolean credentialProvided, final Class<T> clazz) {
        String ticketId = produceTicketIdentifier(service, ticketGrantingTicket, credentialProvided);
        return produceTicket(ticketGrantingTicket, service, credentialProvided, ticketId, clazz);
    }

    protected String produceTicketIdentifier(final Service service, final TicketGrantingTicket ticketGrantingTicket,
                                             final boolean credentialProvided) {
        UniqueTicketIdGenerator serviceTicketUniqueTicketIdGenerator = this.uniqueTicketIdGeneratorsForService.get(service.getClass().getName());
        return serviceTicketUniqueTicketIdGenerator.getNewTicketId("ST");
    }

    protected <T extends Ticket> T produceTicket(final TicketGrantingTicket ticketGrantingTicket, final Service service,
                                                 final boolean credentialProvided, final String ticketId, final Class<T> clazz) {
        final ServiceTicket result = ticketGrantingTicket.grantServiceTicket(
                ticketId,
                service,
                this.serviceTicketExpirationPolicy,
                credentialProvided,
                trackMostRecentSession);
        return (T) result;
    }
    
}

# ST的存储

同tgt,略;

# ST的发送

/*xxx: 生成st的action */
public class GenerateServiceTicketAction extends AbstractAction {
    @Override
    protected Event doExecute(final RequestContext context) {
        /*xxx: 将st保存绑定到 request中*/
        WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);
    }
}

/*xxx: 登录成功后,重定向到客户端的action */
public class RedirectToServiceAction extends AbstractAction {
    @Override
    protected Event doExecute(final RequestContext requestContext) {
        final String serviceTicketId = WebUtils.getServiceTicketFromRequestScope(requestContext);
        final ResponseBuilder builder = responseBuilderLocator.locate(service);

        final Response response = builder.build(service, serviceTicketId, auth);
    }
}
public class WebApplicationServiceResponseBuilder extends AbstractWebApplicationServiceResponseBuilder {
    @Override
    public Response build(final WebApplicationService service, final String serviceTicketId, final Authentication authentication) {

        final Map<String, String> parameters = new HashMap<>();

        parameters.put("ticket", serviceTicketId);
        
        if (responseType == ResponseType.POST) {
            return buildPost(finalService, parameters);
        }
        if (responseType == ResponseType.REDIRECT) {
            return buildRedirect(finalService, parameters);
        }
        if (responseType == ResponseType.HEADER) {
            return buildHeader(finalService, parameters);
        }
        /*xxx: 省略其它抽象...*/
    }

    protected Response buildPost(final WebApplicationService service, final Map<String, String> parameters) {
        return DefaultResponse.getPostResponse(service.getOriginalUrl(), parameters);
    }

    protected Response buildRedirect(final WebApplicationService service, final Map<String, String> parameters) {
        return DefaultResponse.getRedirectResponse(service.getOriginalUrl(), parameters);
    }

    protected Response buildHeader(final WebApplicationService service, final Map<String, String> parameters) {
        return DefaultResponse.getHeaderResponse(service.getOriginalUrl(), parameters);
    }
}
public class DefaultResponse implements Response {
    public static Response getPostResponse(final String url, final Map<String, String> attributes) {
        return new DefaultResponse(ResponseType.POST, url, attributes);
    }

    public static Response getHeaderResponse(final String url, final Map<String, String> attributes) {
        return new DefaultResponse(ResponseType.HEADER, url, attributes);
    }

    public static Response getRedirectResponse(final String url, final Map<String, String> parameters) {
        final StringBuilder builder = new StringBuilder(parameters.size() * 40 + 100);
        /*xxx: 将parameters的参数,以 query的方式,拼接在 请求路径上,略*/
        final String urlRedirect = builder.toString();
        return new DefaultResponse(ResponseType.REDIRECT, urlRedirect, parameters);
    }
}

# 服务票据的核验

# 票据凭证验证接口设计

@Configuration("casValidationConfiguration")
public class CasValidationConfiguration {
    @Bean
    @ConditionalOnMissingBean(name = "v3ServiceValidateController")
    public V3ServiceValidateController v3ServiceValidateController() {
        return new V3ServiceValidateController(
                cas20WithoutProxyProtocolValidationSpecification,
                authenticationSystemSupport.getIfAvailable(),
                servicesManager,
                centralAuthenticationService,
                proxy20Handler.getIfAvailable(),
                argumentExtractor.getIfAvailable(),
                multifactorTriggerSelectionStrategy,
                authenticationContextValidator,
                cas3ServiceJsonView(),
                cas3ServiceSuccessView(),
                cas3ServiceFailureView,
                casProperties.getAuthn().getMfa().getAuthenticationContextAttribute(),
                serviceValidationAuthorizers,
                casProperties.getSso().isRenewAuthnEnabled()
        );
    } 
}
public class V3ServiceValidateController extends AbstractServiceValidateController {
    @GetMapping(path = "/p3/serviceValidate")
    protected ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
        return super.handleRequestInternal(request, response);
    }
}

# 验证流程设计

public abstract class AbstractServiceValidateController extends AbstractDelegateController {
    @Override
    public ModelAndView handleRequestInternal(final HttpServletRequest request, final HttpServletResponse response) throws Exception {
        /*xxx: 提取服务信息 */
        final WebApplicationService service = this.argumentExtractor.extractService(request);

        /*xxx: 获取 stId */
        final String serviceTicketId = service != null ? service.getArtifactId() : null;

        prepareForTicketValidation(request, service, serviceTicketId);
        return handleTicketValidation(request, service, serviceTicketId);
    }

    protected void prepareForTicketValidation(final HttpServletRequest request, final WebApplicationService service, final String serviceTicketId) {
    }

    protected ModelAndView handleTicketValidation(final HttpServletRequest request, final WebApplicationService service, final String serviceTicketId) {
        /*xxx: 验证st合法性 */
        final Assertion assertion = validateServiceTicket(service, serviceTicketId);
        /*xxx: 省略其它抽象...*/
    }

    /*xxx: 验证st的合法性*/
    protected Assertion validateServiceTicket(final WebApplicationService service, final String serviceTicketId) {
        return this.centralAuthenticationService.validateServiceTicket(serviceTicketId, service);
    }
}
public interface CentralAuthenticationService {
    /*xxx: 验证st合法性*/
    Assertion validateServiceTicket(String serviceTicketId, Service service) throws AbstractTicketException;
}

public class DefaultCentralAuthenticationService extends AbstractCentralAuthenticationService {
    
    public Assertion validateServiceTicket(final String serviceTicketId, final Service service) throws AbstractTicketException {
        /*xxx: 通过stId获取 service实例*/
        final ServiceTicket serviceTicket = this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class);

        /*xxx: 获取认证信息*/
        final TicketGrantingTicket root = serviceTicket.getTicketGrantingTicket().getRoot();
        final Authentication authentication = getAuthenticationSatisfiedByPolicy(root.getAuthentication(),
                new ServiceContext(selectedService, registeredService));

        /*xxx: 组装认证构造器 */
        final AuthenticationBuilder builder = DefaultAuthenticationBuilder.newInstance(authentication);

        /*xxx: 获取认证信息 */
        final Authentication finalAuthentication = builder.build();

        /*xxx: 组装 Assertion实体 */
        final Assertion assertion = new DefaultAssertionBuilder(finalAuthentication)
                .with(selectedService)
                .with(serviceTicket.getTicketGrantingTicket().getChainedAuthentications())
                .with(serviceTicket.isFromNewLogin())
                .build();
        
        /*xxx: 省略其它抽象...*/
        
        return assertion;
    }
}

# cas客户端服务专题

# 已注册服务--RegisteredService

# 已注册服务-逻辑结构

public interface RegisteredService extends Serializable, Comparable<RegisteredService> {
    /*xxx: 服务唯一标志*/
    String getServiceId();

    /*xxx: 服务名称*/
    String getName();

    /*xxx: 主题名称*/
    String getTheme();

    /*xxx: 服务响应策略,通常是带上 ticketId并重定向*/
    String getResponseType();

    /*xxx: 决定服务是否匹配时,相对其它已注册服务的评估顺序*/
    int getEvaluationOrder();

    /*xxx: 决定请求的服务与已注册服务是否匹配*/
    boolean matches(Service service);

    /*xxx: 决定serviceId 与已注册服务是否匹配*/
    boolean matches(String serviceId);

    /*xxx: 已注册服务的登出类型*/
    LogoutType getLogoutType();
}

/*xxx: 通过正则匹配的已注册服务*/
public class RegexRegisteredService extends AbstractRegisteredService {
    /*xxx: 服务 正则匹配器 */
    private transient Pattern servicePattern;

    @Override
    public boolean matches(final String serviceId) {
        if (this.servicePattern == null) {
            /*xxx: 通过serviceId去创建正则表达式,并决定是否匹配*/
            this.servicePattern = RegexUtils.createPattern(this.serviceId);
        }
        return StringUtils.isBlank(serviceId) ? false : this.servicePattern.matcher(serviceId).matches();
    }
}

/*xxx: 已注册服务,还可以通过jdbc进行配置*/
@Entity
@Table(name = "RegexRegisteredService")
public abstract class AbstractRegisteredService implements RegisteredService {
    @Column(nullable = false)
    protected String serviceId;

    @Column(nullable = false)
    private String name;

    @Column
    private String theme;
    
    /*xxx: 省略其它抽象....*/
}

# 默认的已注册服务配置

{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^https://www.apereo.org",
  "name" : "Apereo",
  "theme" : "apereo",
  "id" : 10000002,
  "description" : "Apereo foundation sample service",
  "evaluationOrder" : 1
}
{
  "@class" : "org.apereo.cas.services.RegexRegisteredService",
  "serviceId" : "^(https|imaps|http)://.*",
  "name" : "HTTPS and IMAPS",
  "id" : 10000001,
  "description" : "This service definition authorizes all application urls that support HTTPS and IMAPS protocols.",
  "evaluationOrder" : 10000
}

# 服务注册表

# 服务注册表--逻辑设计

/*xxx: 服务注册表*/
public interface ServiceRegistry {
    /*xxx: 保存已注册服务*/
    RegisteredService save(RegisteredService registeredService);

    /*xxx: 移除已注册服务*/
    boolean delete(RegisteredService registeredService);

    /*xxx: 加载已注册服务*/
    List<RegisteredService> load();

    /*xxx: 通过serviceId查找已注册服务*/
    RegisteredService findServiceById(String id);

    /*xxx: 获取已注册服务注册表大小*/
    default long size() {
        return load().size();
    }
}

# 基于文件资源的已注册服务注册表

/*xxx: 基于抽象资源的 服务注册表*/
public abstract class AbstractResourceBasedServiceRegistry extends AbstractServiceRegistry implements ResourceBasedServiceRegistry {
    /*xxx: 服务注册表配置文件的路径地址*/
    protected Path serviceRegistryDirectory;

    /*xxx: 已注册服务列表,由 id 号构成的主键 */
    private Map<Long, RegisteredService> serviceMap = new ConcurrentHashMap<>();

    @Override
    public synchronized List<RegisteredService> load() {
        /*xxx: 获取到所有的文件列表 */
        final Collection<File> files = FileUtils.listFiles(this.serviceRegistryDirectory.toFile(), new String[]{getExtension()}, true);

        /*xxx: 从文件中进行加载,并收集配置*/
        this.serviceMap = files
                .stream()
                .map(this::load)
                .filter(Objects::nonNull)
                .flatMap(Collection::stream)
                .sorted()
                .collect(Collectors.toMap(RegisteredService::getId, Function.identity(),
                        LOG_DUPLICATE_AND_RETURN_FIRST_ONE, LinkedHashMap::new));
    }

    @Override
    /*xxx: 从文件中读取配置*/
    public Collection<RegisteredService> load(final File file) {
        try (BufferedInputStream in = new BufferedInputStream(Files.newInputStream(file.toPath()))) {
            /*xxx: 通过已配置服务序列号器,对流进行读取解析 */
            return this.registeredServiceSerializers
                    .stream()
                    .filter(s -> s.supports(file))
                    .map(s -> s.load(in))
                    .filter(Objects::nonNull)
                    .flatMap(Collection::stream)
                    .collect(Collectors.toList());
        } catch (final Exception e) {
            /*xxx: 打印错误日志...*/
        }
    }

    /*xxx: 资源文件的后缀*/
    protected abstract String getExtension();
}
/*xxx: 内部资源服务注册表*/
public static class EmbeddedResourceBasedServiceRegistry extends AbstractResourceBasedServiceRegistry {
    
    private static List getRegisteredServiceSerializers() {
        /*xxx: 提供了两类实现,默认json序列化器,cas插件json序列化器*/
        return CollectionUtils.wrapList(
                new CasAddonsRegisteredServicesJsonSerializer(),
                new DefaultRegisteredServiceJsonSerializer());
    }
    
    @Override
    protected String getExtension() {
        return "json";
    }
}
/*xxx: cas已注册服务插件json序列化器*/
public class CasAddonsRegisteredServicesJsonSerializer extends DefaultRegisteredServiceJsonSerializer {
    
    @Override
    public Collection<RegisteredService> load(final InputStream stream) {
        final List<RegisteredService> results = new ArrayList<>();

        final Map<String, List> servicesMap = this.objectMapper.readValue(stream, Map.class);

        final Iterator<Map> it = servicesMap.get("services").iterator();

        /*xxx: 将文件内容的json内容,转换为实体*/
        final RegisteredService svc = convertServiceProperties(record);
        results.add(svc);
        
        return svc;
    }
}

# 基于内存的已注册服务注册表

public class InMemoryServiceRegistry extends AbstractServiceRegistry {
    @Override
    public List<RegisteredService> load() {
        final List<RegisteredService> services = new ArrayList<>();
        return services;
    }
}
public class ImmutableInMemoryServiceRegistry extends InMemoryServiceRegistry implements ImmutableServiceRegistry {
    @Override
    public RegisteredService save(final RegisteredService registeredService) {
        return registeredService;
    }
}

# 服务注册表的配置

@ConditionalOnProperty(prefix = "cas.serviceRegistry", name = "initFromJson", havingValue = "true")
public class CasServiceRegistryInitializationConfiguration {
    @RefreshScope
    @Bean
    public ServiceRegistry embeddedJsonServiceRegistry() {
        final Resource location = getServiceRegistryInitializerServicesDirectoryResource();
        return new EmbeddedResourceBasedServiceRegistry(eventPublisher, location);
    }

    /*xxx: 如果没有配置,则用默认的 services作为配置项*/
    private Resource getServiceRegistryInitializerServicesDirectoryResource() {
        final JsonServiceRegistryProperties registry = casProperties.getServiceRegistry().getJson();
        return ObjectUtils.defaultIfNull(registry.getLocation(), new ClassPathResource("services"));
    }
}

# 普通服务

# 普通服务与已注册服务的区别

  • 普通服务,是认证服务器,对外来请求信息的简单封装
  • 已注册服务,代表了外来的服务应该怎么处理,是否需要认证,是否需要认证,颁发授权的方式,采用的登录主题等等

# 普通服务的抽象设计

/*xxx: 认证凭证*/
public interface Principal extends Serializable {
    /*xxx: 凭证id*/
    String getId();

    /*xxx: 凭证属性集合*/
    default Map<String, Object> getAttributes() {
        return new LinkedHashMap<>(0);
    }
}

/*xxx: 普通服务 */
public interface Service extends Principal {
    /*xxx: 普通服务 */
    public interface Service extends Principal {
        /*xxx: 设置凭据*/
        default void setPrincipal(String principal) {}
    }
}

/*xxx: web应用程序服务 */
public interface WebApplicationService extends Service {
    /*xxx: 获取 web服务制品 id*/
    String getArtifactId();

    /*xxx: 获取原始请求的url */
    String getOriginalUrl();
    
    /*xxx: 省略其它抽象...*/
}

public abstract class AbstractWebApplicationService implements WebApplicationService {
    private String id;
    private String artifactId;
    private String originalUrl;

    protected AbstractWebApplicationService(final String id, final String originalUrl, final String artifactId) {
        this.id = id;
        this.originalUrl = originalUrl;
        this.artifactId = artifactId;
    }
}

# 普通服务的生效机制

public class WebApplicationServiceFactory extends AbstractServiceFactory<WebApplicationService> {

    public class WebApplicationServiceFactory extends AbstractServiceFactory<WebApplicationService> {
        @Override
        /*xxx: id代表 重定向地址,重定向地址通过重定向参数获取而来  */
        public WebApplicationService createService(final String id) {
            return newWebApplicationService(HttpRequestUtils.getHttpServletRequestFromRequestAttributes(), id);
        }
    }
    
    
    /*xxx: 通过 http请求,创建服务 */
    protected static AbstractWebApplicationService newWebApplicationService(final HttpServletRequest request,
                                                                            final String serviceToUse) {
        final String artifactId = request != null ? request.getParameter("ticket") : null;
        /*xxx: serviceToUse 代表的是 客户端后端服务器的地址 */
        /*xxx: 将请求中的 查询参数,以及 参数 移除*/
        final String id = cleanupUrl(serviceToUse);

        /*xxx: 没有参数的 url ->id*/
        /*xxx: 完整参数的 url -> originalUrl*/
        /*xxx: ticket -> artifactId*/
        final AbstractWebApplicationService newService = new SimpleWebApplicationServiceImpl(id, serviceToUse, artifactId);
    }
}

# cas服务主题配置

# 主题解析器运行原理

  • Spring MVC中通过ThemeSource接口来提供对动态更换样式的支持,并提供了ResourceBundleThemeSource这个具体实现类来提供通 过properties配置文件对theme中的样式的配置
  • 例如配置文件中的内容为 helloworld=theme/default/css/helloworld.css 而jsp文件中使用 来引用对helloworld这个样式文件的引入

# 已注册服务主题解析器逻辑设计

/*xxx: 已注册服务主题解析器 */
public class RegisteredServiceThemeResolver extends AbstractThemeResolver {
    /*xxx: 服务管理器*/
    private final ServicesManager servicesManager;

    @Override
    public String resolveThemeName(final HttpServletRequest request) {
        final Service service = this.authenticationRequestServiceSelectionStrategies.resolveService(serviceContext);

        final RegisteredService rService = this.servicesManager.findServiceBy(service);
        
        /*xxx: 简化之后的方法...,省略其它抽象... */
        return rService.getTheme();
    }
}

# CAS服务端登出设计

# 登出流程集成设计

<end-state id="redirectView" view="externalRedirect:#{flowScope.logoutRedirectUrl}"/>
//package org.springframework.web.servlet.view;
public class RedirectView extends AbstractUrlBasedView implements SmartView {
}

# 登出说明

  • externalRedirect,重定向流程的外部页面
  • flowRedirect,重定向到另一个流程中

# CAS客户端

# 集成设计

@Component
public class CasClientConfiguration {
    @Bean
    /*xxx: 认证过滤器*/
    public FilterRegistrationBean<AuthenticationFilter> casAuthenticationFilterRegistrationBean(){
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        AuthenticationFilter authenticationFilter = new AuthenticationFilter();
        registrationBean.setName("casAuthenticationFilter");
        registrationBean.setFilter(authenticationFilter);
        registrationBean.addInitParameter("casServerLoginUrl",clientProperties.getCasServerLoginUrl());
        registrationBean.addInitParameter("serverName",clientProperties.getServerName());

        return registrationBean;
    }

    @Bean
    /*xxx: ticket校验过滤器*/
    public FilterRegistrationBean<Cas30ProxyReceivingTicketValidationFilter> ticketValidationFilterFilterRegistrationBean(){
        FilterRegistrationBean<Cas30ProxyReceivingTicketValidationFilter> registrationBean = new FilterRegistrationBean<Cas30ProxyReceivingTicketValidationFilter>();
        Cas30ProxyReceivingTicketValidationFilter filter = new Cas30ProxyReceivingTicketValidationFilter();
        registrationBean.setName("ticketValidationFilter");

        registrationBean.addInitParameter("casServerUrlPrefix",clientProperties.getCasServerUrlPrefix());
        registrationBean.addInitParameter("serverName",clientProperties.getServerName());
        registrationBean.addInitParameter("redirectAfterValidation","true");
        registrationBean.addInitParameter("useSession","true");
        registrationBean.setFilter(filter);
        return registrationBean;
    }

    @Bean
    /*xxx: 请求包装过滤器*/
    public FilterRegistrationBean<HttpServletRequestWrapperFilter> httpServletRequestWrapperFilterFilterRegistrationBean(){
        FilterRegistrationBean registrationBean  = new FilterRegistrationBean();
        HttpServletRequestWrapperFilter filter = new HttpServletRequestWrapperFilter();
        registrationBean.setFilter(filter);
        return registrationBean;
    }
    
}

# 请求包装过滤器

public final class HttpServletRequestWrapperFilter extends AbstractConfigurationFilter {
    
    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
                         final FilterChain filterChain) throws IOException, ServletException {
        final AttributePrincipal principal = retrievePrincipalFromSessionOrRequest(servletRequest);

        filterChain.doFilter(new CasHttpServletRequestWrapper((HttpServletRequest) servletRequest, principal),
                servletResponse);
    }

    protected AttributePrincipal retrievePrincipalFromSessionOrRequest(final ServletRequest servletRequest) {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpSession session = request.getSession(false);
        /*xxx: 票据是否验证通过的标志*/
        final Assertion assertion = (Assertion) (session == null ? request
                .getAttribute("_const_cas_assertion_") : session
                .getAttribute("_const_cas_assertion_"));

        return assertion == null ? null : assertion.getPrincipal();
    }
}
final class CasHttpServletRequestWrapper extends HttpServletRequestWrapper {
    private final AttributePrincipal principal;

    /*xxx: 获取认证信息*/
    public Principal getUserPrincipal() {
        return this.principal;
    }
    /*xxx: 获取认证的用户名*/
    public String getRemoteUser() {
        return principal != null ? this.principal.getName() : null;
    }
}

# 认证过滤器

public abstract class AbstractCasFilter extends AbstractConfigurationFilter {
    private String serverName;

    private String service;

    private Protocol protocol;
}
public class AuthenticationFilter extends AbstractCasFilter {
    /*xxx: 认证重定向策略*/
    private AuthenticationRedirectStrategy authenticationRedirectStrategy = new DefaultAuthenticationRedirectStrategy();

    /*xxx: url忽略匹配策略*/
    private UrlPatternMatcherStrategy ignoreUrlPatternMatcherStrategyClass = null;

    public AuthenticationFilter() {
        this(Protocol.CAS2);
    }

    public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
                               final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        /*xxx: 跳过认证*/
        if (isRequestUrlExcluded(request)) {
            filterChain.doFilter(request, response);
            return;
        }

        final HttpSession session = request.getSession(false);
        final Assertion assertion = session != null ? (Assertion) session.getAttribute("_const_cas_assertion_") : null;

        /*xxx: 已经认证过,则跳过*/
        if (assertion != null) {
            filterChain.doFilter(request, response);
            return;
        }

        final String serviceUrl = constructServiceUrl(request, response);
        /*xxx: 从请求提取 ticket信息*/
        final String ticket = retrieveTicketFromRequest(request);
        
        /*xxx: 请求是否已经被网关记录过*/
        final boolean wasGatewayed = this.gateway && this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);

        /*xxx: 请求已经被网关记录过,或者存在 ticket,则放行*/
        if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
            filterChain.doFilter(request, response);
            return;
        }

        final String modifiedServiceUrl;
        if (this.gateway) {
            /*xxx: 通过网关记录*/
            modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
        } else {
            modifiedServiceUrl = serviceUrl;
        }

        final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl,
                getProtocol().getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);

        /*xxx: 重定向到 认证服务器进行  认证 */
        this.authenticationRedirectStrategy.redirect(request, response, urlToRedirectTo);
    }
    /*xxx: 是否跳过认证*/
    private boolean isRequestUrlExcluded(final HttpServletRequest request) {
        if (this.ignoreUrlPatternMatcherStrategyClass == null) {
            return false;
        }

        final StringBuffer urlBuffer = request.getRequestURL();
        if (request.getQueryString() != null) {
            urlBuffer.append("?").append(request.getQueryString());
        }
        final String requestUri = urlBuffer.toString();
        return this.ignoreUrlPatternMatcherStrategyClass.matches(requestUri);
    }
}

# ticket校验过滤器

public abstract class AbstractTicketValidationFilter extends AbstractCasFilter {

    public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
                               final FilterChain filterChain) throws IOException, ServletException {

        /*xxx: 前置过滤*/
        if (!preFilter(servletRequest, servletResponse, filterChain)) {
            return;
        }

        /*xxx: 从请求中,提取ticket*/
        final String ticket = retrieveTicketFromRequest(request);

        /*xxx: 通过票据验证器进行验证*/
        final Assertion assertion = this.ticketValidator.validate(ticket,
                constructServiceUrl(request, response));

        /*xxx: 验证成功后,将之存储在 request域,以及session域*/
        request.setAttribute("_const_cas_assertion_", assertion);

        if (this.useSession) {
            request.getSession().setAttribute("_const_cas_assertion_", assertion);
        }

        if (this.redirectAfterValidation) {
            /*xxx: 验证成功后进行重定向*/
            response.sendRedirect(constructServiceUrl(request, response));
            return;
        }

        /*xxx: 否则继续往下执行过滤链*/
        filterChain.doFilter(request, response);
    }

    protected abstract TicketValidator getTicketValidator(final FilterConfig filterConfig);
    
}
public class Cas20ProxyReceivingTicketValidationFilter extends AbstractTicketValidationFilter {
    /*xxx: 票据验证器默认实现类*/
    protected Class<? extends Cas20ServiceTicketValidator> defaultServiceTicketValidatorClass;

    protected final boolean preFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
                                      final FilterChain filterChain) throws IOException, ServletException {
        /*xxx: 跟代理有关的啥东东...  懒得看了*/
        final String requestUri = request.getRequestURI();

        if (CommonUtils.isEmpty(this.proxyReceptorUrl) || !requestUri.endsWith(this.proxyReceptorUrl)) {
            return true;
        }

        try {
            CommonUtils.readAndRespondToProxyReceptorRequest(request, response, this.proxyGrantingTicketStorage);
        } catch (final RuntimeException e) {
            throw e;
        }

        return false;
    }

    protected final TicketValidator getTicketValidator(final FilterConfig filterConfig) {
        final Cas20ServiceTicketValidator  validator = createNewTicketValidator(ticketValidatorClass, casServerUrlPrefix,
                this.defaultServiceTicketValidatorClass);
        /*xxx: 省略其它抽象...*/
        return validator;
    }
}
public class Cas30ProxyReceivingTicketValidationFilter extends Cas20ProxyReceivingTicketValidationFilter {
    public Cas30ProxyReceivingTicketValidationFilter() {
        super(Protocol.CAS3);
        this.defaultServiceTicketValidatorClass = Cas30ServiceTicketValidator.class;
    }
}

# 票据验证器

public interface TicketValidator {
    Assertion validate(String ticket, String service) throws TicketValidationException;
}

public abstract class AbstractUrlBasedTicketValidator implements TicketValidator {
    private HttpURLConnectionFactory urlConnectionFactory = new HttpsURLConnectionFactory();

    protected abstract String retrieveResponseFromServer(URL validationUrl, String ticket);

    public final Assertion validate(final String ticket, final String service) throws TicketValidationException {
        final String validationUrl = constructValidationUrl(ticket, service);

        final String serverResponse = retrieveResponseFromServer(new URL(validationUrl), ticket);

        return parseResponseFromServer(serverResponse);
    }
}

public abstract class AbstractCasProtocolUrlBasedTicketValidator extends AbstractUrlBasedTicketValidator {
    protected final String retrieveResponseFromServer(final URL validationUrl, final String ticket) {
        return CommonUtils.getResponseFromServer(validationUrl, getURLConnectionFactory(), getEncoding());
    }
}

public class Cas20ServiceTicketValidator extends AbstractCasProtocolUrlBasedTicketValidator {
    protected String getUrlSuffix() {
        return "serviceValidate";
    }

    /*xxx: 从服务端响应中,获取认证信息*/
    protected final Assertion parseResponseFromServer(final String response) throws TicketValidationException {
        final String principal = XmlUtils.getTextForElement(response, "user");

        final Map<String, Object> attributes = extractCustomAttributes(response);

        /*xxx: 省略其它抽象...*/
        
        final Assertion assertion = new AssertionImpl(new AttributePrincipalImpl(principal, attributes));
        
        return assertion;
    }
}
public class Cas30ServiceTicketValidator extends Cas20ServiceTicketValidator {
    @Override
    protected String getUrlSuffix() {
        return "p3/serviceValidate";
    }
}

# 集群登出过滤器

# 单点登出关键要素

  • 拦截登出请求的过滤器
  • 登出请求后,销毁对应的session的监听器

# 单点登出过滤器逻辑设计

public final class SingleSignOutFilter extends AbstractConfigurationFilter {
    /*xxx: 登出处理器*/
    private static final SingleSignOutHandler HANDLER = new SingleSignOutHandler();

    public void init(final FilterConfig filterConfig) throws ServletException {
        /*xxx: 省略其它抽象...*/
        HANDLER.init();
    }

    public void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse,
                         final FilterChain filterChain) throws IOException, ServletException {
        if (!this.handlerInitialized.getAndSet(true)) {
            HANDLER.init();
        }

        if (HANDLER.process(request, response)) {
            filterChain.doFilter(servletRequest, servletResponse);
        }
    }
}

# 单点登出处理器

public final class SingleSignOutHandler {
    
    public boolean process(final HttpServletRequest request, final HttpServletResponse response) {
        /*xxx: 如果是认证请求*/
        if (isTokenRequest(request)) {
            /*xxx: 将 令牌与 session 绑定起来 */
            recordSession(request);
            return true;

            /*xxx: 判断是否是后端登出请求(从认证服务器发起的)*/
        } else if (isBackChannelLogoutRequest(request)) {
            /*xxx: 本质上是调用 session.invalidate()*/
            destroySession(request);
            return false;

            /*xxx: 判断是否是 前端登出请求 */
        } else if (isFrontChannelLogoutRequest(request)) {
            /*xxx: 本质上是调用 session.invalidate()*/
            destroySession(request);
            /*xxx: 重定向到cas服务器的地址*/
            final String redirectionUrl = computeRedirectionToServer(request);
            if (redirectionUrl != null) {
                CommonUtils.sendRedirect(response, redirectionUrl);
            }
            return false;
        } else {
            return true;
        }
    }
}

# 单点登出处理器的拦截条件

  • cas客户端认证(验证token)时
  • 后端登出请求
  • 前端登出请求

# 单点登出session监听器

public final class SingleSignOutHttpSessionListener implements HttpSessionListener {
    public void sessionDestroyed(final HttpSessionEvent event) {
        if (sessionMappingStorage == null) {
            sessionMappingStorage = getSessionMappingStorage();
        }
        final HttpSession session = event.getSession();
        sessionMappingStorage.removeBySessionById(session.getId());
    }
}

public interface SessionMappingStorage {

    /*xxx: 通过token移除数据*/
    HttpSession removeSessionByMappingId(String mappingId);

    /*xxx: 通过sessionId移除数据*/
    void removeBySessionById(String sessionId);

    /*xxx: 添加token与session的绑定*/
    void addSessionById(String mappingId, HttpSession session);
}