cas 5.3.X 源代碼學習


這段時間學習了cas5.3源碼,記錄了個人學習認識與心得,就當是學習筆記。由於水平有限而且也只是要了解大概,因此很多地方可能認識的不全面或有偏差。


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<Event> 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<Authentication> 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<String, Object> getAttributes();

    void addAttribute(String name, Object value);
	//這個也沒搞懂,為什么會有多個憑據
    List<CredentialMetaData> getCredentials();
	//因為認證具體過程是委托給多個 AuthenticationHandler 處理類,所以這塊登記的是成功進行認證處理的類的執行結果
    Map<String, AuthenticationHandlerExecutionResult> getSuccesses();
	//相反的,失敗的處理類所拋出的異常
    Map<String, Throwable> 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<AuthenticationHandler> handlerSet = getAuthenticationHandlersForThisTransaction(transaction);
    log.debug("Candidate resolved authentication handlers for this transaction are [{}]", handlerSet);
...

    //循環每一個憑證(為什么是多個憑證?),每個憑證遍歷調用每一個登記的認證處理類
    try {
        final Iterator<Credential> it = credentials.iterator();
        AuthenticationCredentialsThreadLocalBinder.clearInProgressAuthentication();
        while (it.hasNext()) {
            final Credential credential = it.next();
            log.debug("Attempting to authenticate credential [{}]", credential);

            final Iterator<AuthenticationHandler> 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<Boolean, Set<Throwable>> 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<Boolean, Set<Throwable>> 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 extends Ticket> T getTicket(String ticketId, Class<T> clazz) throws InvalidTicketException;

    default void deleteTicket(String ticketId) {
    }

    Collection<Ticket> getTickets(Predicate<Ticket> 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<LogoutRequest> destroyTicketGrantingTicket(String ticketGrantingTicketId);

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM