這段時間學習了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;
}