cas协议票据认证逻辑
Login流程初始化
当客户端发起登录操作请求/login
流程时,首先会调用Login
流程的初始Action
。初始化Action
的实现类为InitialFlowSetupAction
.在doExecute(requestContext context)
方法中回去验证票据。
@Override
public Event doExecute(final RequestContext context) {
configureCookieGenerators(context);
configureWebflowContext(context);
configureWebflowForPostParameters(context);
configureWebflowForCustomFields(context);
configureWebflowForServices(context);
/*开始验证票据逻辑*/
// 从cookie中获取TGC,
String ticketGrantingTicketId = configureWebflowForTicketGrantingTicket(context);
configureWebflowForSsoParticipation(context, ticketGrantingTicketId);
return success();
}
获取认证信息Authentication
TGT
全名应该是Ticket Granting Ticket
,TGT
是存储在服务端的,在cas-server
中提供了多种存储方案,包括默认内存存储、redis
、mongo
,jpa
,memcached
等。
在InitialFlowSetupAction
的configureWebflowForSsoParticipation
会去根据TGC
获取认证信息Authentication
,并把认证信息放入到会话中Session
.
/**
* Configure the SSO participation in webflow.
*
* @param context the webflow context
* @param ticketGrantingTicketId the TGT identifier
*/
protected void configureWebflowForSsoParticipation(final RequestContext context, final String ticketGrantingTicketId) {
// 获取单点登录请求对象
SingleSignOnParticipationRequest ssoRequest = SingleSignOnParticipationRequest
.builder()
.requestContext(context).build();
// 是否开启单点登录? 暂时还不知道其用意。
boolean ssoParticipation = renewalStrategy.supports(ssoRequest)
&& renewalStrategy.isParticipating(ssoRequest);
// 如果从cookie中获取到了TGC则回去填充认证信息
if (!ssoParticipation && StringUtils.isNotBlank(ticketGrantingTicketId)) {
Authentication auth = this.ticketRegistrySupport.getAuthenticationFrom(ticketGrantingTicketId);
WebUtils.putExistingSingleSignOnSessionAvailable(context, auth != null);
// 获取认证信息
auth = Optional.ofNullable(auth).map(Authentication::getPrincipal).orElse(NullPrincipal.getInstance());
// 把认证信息放入到session中。
WebUtils.putExistingSingleSignOnSessionPrincipal(context, auth);
}
}
由上可以看出认证信息是从ticketRegistrySupport
对象中获取到的。 ticketRegistrySupport
对象的具体实现默认是DefaultTicketRegistrySupport
.DefaultTicketRegistrySupport
中具体的TGT
获取由TicketRegistry
完成。
@Override
public Authentication getAuthenticationFrom(final String ticketGrantingTicketId) {
if (StringUtils.isBlank(ticketGrantingTicketId)) {
return null;
}
val tgt = getTicketGrantingTicket(ticketGrantingTicketId);
return Optional.ofNullable(tgt).map(TicketGrantingTicket::getAuthentication).orElse(null);
}
从上面DefaultTicketRegistrySupport
获取Authentication
的逻辑可以看出从服务端存储的TGT(TicketGrantingTicket)
中可以取到认证信息Authentication
.
获取票据TGT
在DefaultTicketRegistrySupport
调用getTicketGrantingTicket()
方法可以获取服务端存储的TGT
票据。在该方法中实际是使用TicketRegistry
来完成获取的。
@Override
public TicketGrantingTicket getTicketGrantingTicket(final String ticketGrantingTicketId) {
if (StringUtils.isBlank(ticketGrantingTicketId)) {
return null;
}
val tgt = this.ticketRegistry.getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);
return tgt == null || tgt.isExpired() ? null : tgt;
}
TicketRegistry
是一个接口。主要用来实现TGT
的操作。在其下有一个默认的实现抽象类AbstractTicketRegistry
。在该抽象类中实现了部分方法的封装。其中getTicket(final String ticketId, final @NonNull Class<T> clazz)
方法就在该类中进行实现,(为了后续好描述,该方法别名为:getTicket1
)。getTicket1
会调用Ticket getTicket(final String ticketId)
(别名为:getTicket2
)。getTicket2
会继续调用Ticket getTicket(String ticketId, Predicate<Ticket> predicate)
(别名为:getTicket3
).
getTicket1
和getTicket2
默认都由AbstractTicketRegistry
实现。而getTicket3
由具体的TicketRegistry
实现类进行实现。比如将TGT
存入redis
的RedisTicketRegistry
实现类。
getTicket(final String ticketId, final @NonNull Class<T> clazz)
的实现方法体, 该方法主要做了类型校验和转换功能。
@Override
public <T extends Ticket> T getTicket(final String ticketId, final @NonNull Class<T> clazz) {
val ticket = getTicket(ticketId);
if (ticket == null) {
return null;
}
if (!clazz.isAssignableFrom(ticket.getClass())) {
throw new ClassCastException("Ticket [" + ticket.getId() + " is of type "
+ ticket.getClass() + " when we were expecting " + clazz);
}
return clazz.cast(ticket);
}
Ticket getTicket(final String ticketId)
的实现方法体如下,该方法主要做了类型过期校验,删除过期TGT
操作。
@Override
public Ticket getTicket(final String ticketId) {
return getTicket(ticketId, ticket -> {
if (ticket != null && ticket.isExpired()) {
// 如果过期的话就删除吧。
deleteSingleTicket(ticketId);
return false;
}
return true;
});
}
getTicket(String ticketId, Predicate<Ticket> predicate)
方法由具体的实现类实现,RedisTicketRegistry
实现如下:
@Override
public Ticket getTicket(final String ticketId, final Predicate<Ticket> predicate) {
try {
val redisKey = getTicketRedisKey(encodeTicketId(ticketId));
val t = this.client.boundValueOps(redisKey).get();
if (t != null) {
val result = decodeTicket(t);
if (predicate.test(result)) {
return result;
}
return null;
}
} catch (final Exception e) {
LOGGER.error("Failed fetching [{}]", ticketId);
LoggingUtils.error(LOGGER, e);
}
return null;
}