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;
}