cas 5.3.X 源代码学习


这段时间学习了cas5.3源码,记录了个人学习认识与心得,就当是学习笔记。由于水平有限而且也只是要了解大概,因此很多地方可能认识的不全面或有偏差。

目录
  • 1.代码结构
  • 2.配置文件读取类
  • 3.注入类配置
  • 4.认证过程
    • 发起认证
    • 委托 AuthenticationSystemSupport 处理
      • 返回结果分析
    • 委托 AuthenticationTransactionManager 处理
      • Authentication分析
    • 委托 AuthenticationManager 处理


1.代码结构

cas5.3代码主要分为3个目录:api,core,support。

api目录为系统的顶层设计,里面主要是接口类和少部分虚拟类,可以说这是整个系统的灵魂。

core类是api中各接口的实现,可以通过该目录下各类具体实现代码来了解api目录下各接口的设计思路。

support类认识很弱,感觉是cas的扩展。由于在core目录中主要提供了一些api默认的实现,support中提供其它方式的实现。

我理解这样组织起来是为了更好了解代码,通过api可以理解系统的领域知识和顶层设计,通过core进一步理解系统的实现手法,通过support理解代码的外部依赖于其他实现手法。

另外这种打包方式一方面实现向上依赖的设计原则,另一方面避免包的相互依赖。


2.配置文件读取类

..\api\cas-server-core-api-configuration-model\src\main\java\org\apereo\cas\configuration\CasConfigurationProperties.java是核心的配置类,主要读取application.properties里面cas开始的所有配置,非常庞大,与它同目录下有大量的配置类。


3.注入类配置

好像..\core\ 目录下凡不以-api结束的目录均是各对应以-api结束目录下实现类的注入配置文件。比如 cas-server-core-authentication 就是 cas-server-core-authentication-api目录下实现类的注入配置。


4.认证过程

cas好像自4.0前端就采用了webflow实现前端认证流程组织。由于对webflow不熟悉,就跳过相关部分。直接从用户点击登录页面提交按钮---也即认证请求开始。

发起认证

认证各过程由各Action委托 CasWebflowEventResolver 类的各实现类发起。初次认证由其子类InitialAuthenticationAttemptWebflowEventResolver负责。

@Override
public Set resolveInternal(final RequestContext context) {
    try {
        //从请求中取得认证凭证,里面存储了用户登录页面输入的内容
        final Credential credential = getCredentialFromContext(context);
        //从请求中取请求的服务
        final Service service = WebUtils.getService(context);
        if (credential != null) {
            //将认证过程委托给authenticationSystemSupport类,系统中注入的是DefaultAuthenticationSystemSupport 实现类。 
            final AuthenticationResultBuilder builder = this.authenticationSystemSupport.handleInitialAuthenticationTransaction(service, credential);
            //到本步骤,认证过程已完成,将认证建造者写入到请求上下文中,继续后续的票据处理
            if (builder.getInitialAuthentication().isPresent()) {
                WebUtils.putAuthenticationResultBuilder(builder, context);
                WebUtils.putAuthentication(builder.getInitialAuthentication().get(), context);
            }
        }
        ...
}

委托 AuthenticationSystemSupport 处理

继续向前了解认证过程。首先看一下 DefaultAuthenticationSystemSupport.handleInitialAuthenticationTransaction方法(DefaultAuthenticationSystemSupport为AuthenticationSystemSupport实现类):

@Override
public AuthenticationResultBuilder handleInitialAuthenticationTransaction(final Service service,final Credential... credential) throws AuthenticationException {
    //定义一个新的AuthenticationResultBuilder实例,结果类,所以不能注入
    final DefaultAuthenticationResultBuilder builder = new DefaultAuthenticationResultBuilder();
    if (credential != null) {
        Stream.of(credential).filter(Objects::nonNull).forEach(builder::collect);
    }
	//继续处理
    return this.handleAuthenticationTransaction(service, builder, credential);
}
...
...
@Override
public AuthenticationResultBuilder handleAuthenticationTransaction(final Service service,
    final AuthenticationResultBuilder authenticationResultBuilder,final Credential... credential) throws AuthenticationException {
    //将每次的认证抽象成认证事务对象,便于组织认证,不错的思路
	final AuthenticationTransaction transaction = DefaultAuthenticationTransaction.of(service, credential);
    // 委托给 AuthenticationTransactionManager对象进行	
    this.authenticationTransactionManager.handle(transaction, authenticationResultBuilder);
    return authenticationResultBuilder;
}

返回结果分析

该过程返回的是 AuthenticationResultBuilder 类,该类使用了建造者模式。该类可以收集凭证,证明信息,提供了建造AuthenticationResult类的能力。

public interface AuthenticationResultBuilder extends Serializable {
    Optional getInitialAuthentication();

    AuthenticationResultBuilder collect(Authentication authentication);

    AuthenticationResultBuilder collect(Credential credential);

    AuthenticationResult build(PrincipalElectionStrategy principalElectionStrategy);

    AuthenticationResult build(PrincipalElectionStrategy principalElectionStrategy, Service service);

建造的AuthenticationResult类可以提供认证对象,返回认证时要访问的服务,代码如下:

public interface AuthenticationResult extends Serializable {
    Authentication getAuthentication();

    Service getService();

    boolean isCredentialProvided();
}

委托 AuthenticationTransactionManager 处理

进一步了解 AuthenticationTransactionManager的实现类DefaultAuthenticationTransactionManager

@Override
public AuthenticationTransactionManager handle(final AuthenticationTransaction authenticationTransaction,final AuthenticationResultBuilder authenticationResult)
    throws AuthenticationException {
    if (!authenticationTransaction.getCredentials().isEmpty()) {
        //委托给 authenticationManager 类进行认证,获得认证对象
        final Authentication authentication = this.authenticationManager.authenticate(authenticationTransaction);
        log.debug("Successful authentication; Collecting authentication result [{}]", authentication);
        publishEvent(new CasAuthenticationTransactionCompletedEvent(this, authentication));
        //收集获取的认证对象到认证结果建造对象内
        authenticationResult.collect(authentication);
    } else {
        log.debug("Transaction ignored since there are no credentials to authenticate");
    }
    return this;
}

Authentication分析

在进一步向下了解认证过程前,先看一下 Authentication对象:

public interface Authentication extends Serializable {
    //认证后的身份对系那个
    Principal getPrincipal();
	//认证时间
    ZonedDateTime getAuthenticationDate();
	//汇总身份属性和认证过程的属性,认证过程的属性如认证的凭据类型、成功的认证处理类
    Map getAttributes();

    void addAttribute(String name, Object value);
	//这个也没搞懂,为什么会有多个凭据
    List getCredentials();
	//因为认证具体过程是委托给多个 AuthenticationHandler 处理类,所以这块登记的是成功进行认证处理的类的执行结果
    Map getSuccesses();
	//相反的,失败的处理类所抛出的异常
    Map getFailures();
	//更新本类实例身份信息
    void update(Authentication authn);
	//更新本类实例的所有信息
    void updateAll(Authentication authn);
}

委托 AuthenticationManager 处理

PolicyBasedAuthenticationManager类是AuthenticationManager的默认实现类,看一下代码:

public Authentication authenticate(final AuthenticationTransaction transaction) throws AuthenticationException {
    //认证前置处理,暂不理会
    final boolean result = invokeAuthenticationPreProcessors(transaction);
	..省略..
    //???,为何要建立线程变量
    AuthenticationCredentialsThreadLocalBinder.bindCurrent(transaction.getCredentials());
    //认证过程,也是通过建造者模式建造 Authentication
    final AuthenticationBuilder builder = authenticateInternal(transaction);
    //???
    AuthenticationCredentialsThreadLocalBinder.bindCurrent(builder);
	
    
    final Authentication authentication = builder.build();
    addAuthenticationMethodAttribute(builder, authentication);
    
    populateAuthenticationMetadataAttributes(builder, transaction);
    invokeAuthenticationPostProcessors(builder, transaction);
	//建造Authentication类,返回的是实现类 DefaultAuthentication 类
    final Authentication auth = builder.build();
    ...

    return auth;
}
...
...
    
protected AuthenticationBuilder authenticateInternal(final AuthenticationTransaction transaction) throws AuthenticationException {
	....

    final AuthenticationBuilder builder = new DefaultAuthenticationBuilder(NullPrincipal.getInstance());
    credentials.forEach(cred -> builder.addCredential(new BasicCredentialMetaData(cred)));

    //这是登记的认证处理类,我们一般自己定义的处理类也会登记后在此处获得
    @NonNull
    final Set handlerSet = getAuthenticationHandlersForThisTransaction(transaction);
    log.debug("Candidate resolved authentication handlers for this transaction are [{}]", handlerSet);
...

    //循环每一个凭证(为什么是多个凭证?),每个凭证遍历调用每一个登记的认证处理类
    try {
        final Iterator it = credentials.iterator();
        AuthenticationCredentialsThreadLocalBinder.clearInProgressAuthentication();
        while (it.hasNext()) {
            final Credential credential = it.next();
            log.debug("Attempting to authenticate credential [{}]", credential);

            final Iterator itHandlers = handlerSet.iterator();
            boolean proceedWithNextHandler = true;
            while (proceedWithNextHandler && itHandlers.hasNext()) {
                final AuthenticationHandler handler = itHandlers.next();
                //检查认证处理类是否支持某凭证,通过该方法可以让特定的处理类处理特定的凭证
                if (handler.supports(credential)) {
                    try {
                        //根据认证处理类获取对应的凭证提供者类(两者关系在注入处理类时已确定),transaction毫无用处,误导人,改代码需要改正;
                        //凭证提供者提供如用户属性信息等
                        final PrincipalResolver resolver = getPrincipalResolverLinkedToHandlerIfAny(handler, transaction);
                        log.debug("Attempting authentication of [{}] using [{}]", credential.getId(), handler.getName());
                        //重要步骤,认证并提供身份。从代码看如果有多个认证处理类可处理当前凭证,身份信息好像会覆盖,最后身份建造者中记录的是最后一次产生的身份
                        authenticateAndResolvePrincipal(builder, credential, resolver, handler);
                        AuthenticationCredentialsThreadLocalBinder.bindInProgress(builder.build());

                        final Pair> failures = evaluateAuthenticationPolicies(builder.build(), transaction);
                        proceedWithNextHandler = !failures.getKey();
                    } catch (final Exception e) {
                        ...
                    }
                } else {
                   ...
                }
            }
        }
        //评估认证建造者类,如有问题,抛出异常,终止认证
        evaluateFinalAuthentication(builder, transaction);
        //通过评估,返回认证建造者,认证基本成功
        return builder;
    } finally {
        AuthenticationCredentialsThreadLocalBinder.clearInProgressAuthentication();
    }
}
...
...
//通过认证处理和身份提供类生成身份对象,将身份对象登记到认证建造者中
protected void authenticateAndResolvePrincipal(final AuthenticationBuilder builder,
                                                   final Credential credential,
                                                   final PrincipalResolver resolver,
                                                   final AuthenticationHandler handler) throws GeneralSecurityException, PreventedException {

        publishEvent(new CasAuthenticationTransactionStartedEvent(this, credential));
		//认证处理,这里判断用户是否合法,常见的就是检查用户名密码是否和数据中某用户是否匹配,匹配则则认证成功
        final AuthenticationHandlerExecutionResult result = handler.authenticate(credential);
        final String authenticationHandlerName = handler.getName();
        //将认证处理结果添加到认证建造者中
    	builder.addSuccess(authenticationHandlerName, result);
        log.debug("Authentication handler [{}] successfully authenticated [{}]", authenticationHandlerName, credential);

        publishEvent(new CasAuthenticationTransactionSuccessfulEvent(this, credential));
        Principal principal = result.getPrincipal();

        final String resolverName = resolver != null ? resolver.getClass().getSimpleName() : "N/A";
        if (resolver == null) {
            ...
        } else {
            //身份提供者进一步对身份对象进行处理,常见的如添加属性
            principal = resolvePrincipal(handler, resolver, credential, principal);
            if (principal == null) {
                if (this.principalResolutionFailureFatal) {
                   ...
                }
                ...
            }
        }

        if (principal == null) {
            ...
        } else {
            //将身份信息添加到认证建造者中
            builder.setPrincipal(principal);
        }
        log.debug("Final principal resolved for this authentication event is [{}]", principal);
        publishEvent(new CasAuthenticationPrincipalResolvedEvent(this, principal));
    }

...
//评估最终认证对象,如果发现有问题,通过抛出异常停止本次认证
protected void evaluateFinalAuthentication(final AuthenticationBuilder builder,
                                               final AuthenticationTransaction transaction) throws AuthenticationException {
    //如果认证建造中登记的成功认证处理者记录为空,说明认证失败,抛出异常
        if (builder.getSuccesses().isEmpty()) {
            publishEvent(new CasAuthenticationTransactionFailureEvent(this, builder.getFailures(), transaction.getCredentials()));
            throw new AuthenticationException(builder.getFailures(), builder.getSuccesses());
        }

        final Authentication authentication = builder.build();
        final Pair> failures = evaluateAuthenticationPolicies(authentication, transaction);
        if (!failures.getKey()) {
            publishEvent(new CasAuthenticationPolicyFailureEvent(this, builder.getFailures(), transaction, authentication));
            failures.getValue().forEach(e -> handleAuthenticationException(e, e.getClass().getSimpleName(), builder));
            throw new AuthenticationException(builder.getFailures(), builder.getSuccesses());
        }
    }

至此,认证过程结束。接下来就是根据认证结果生成TGT和ST的过程了。

票据的管理过程重点是 CentralAuthenticationService接口,它的最终实现是 DefaultCentralAuthenticationService类,这是整个系统的核心,包含了TGT、ST的生成、存储、检测。

public interface CentralAuthenticationService {
    String NAMESPACE = CentralAuthenticationService.class.getPackage().getName();

    TicketGrantingTicket createTicketGrantingTicket(AuthenticationResult authenticationResult) throws AuthenticationException, AbstractTicketException;

    Ticket updateTicket(Ticket ticket);

    Ticket getTicket(String ticketId) throws InvalidTicketException;

     T getTicket(String ticketId, Class clazz) throws InvalidTicketException;

    default void deleteTicket(String ticketId) {
    }

    Collection getTickets(Predicate predicate);

    ServiceTicket grantServiceTicket(String ticketGrantingTicketId, Service service, AuthenticationResult authenticationResult) throws AuthenticationException, AbstractTicketException;

    ProxyTicket grantProxyTicket(String proxyGrantingTicket, Service service) throws AbstractTicketException;

    Assertion validateServiceTicket(String serviceTicketId, Service service) throws AbstractTicketException;

    List destroyTicketGrantingTicket(String ticketGrantingTicketId);

    ProxyGrantingTicket createProxyGrantingTicket(String serviceTicketId, AuthenticationResult authenticationResult) throws AuthenticationException, AbstractTicketException;
}