Cas服务端源码解析

编程

此版本为cas5.3,源码链接:https://github.com/apereo/cas/tree/5.3.x

1. 系统启动

启动的配置类:

  • CasSupportActionsConfiguration

 启动的时候会初始化系列action,比较典型的action如下:action名称-实际类型

authenticationViaFormAction-InitialAuthenticationAction

serviceAuthorizationCheck-ServiceAuthorizationCheck

sendTicketGrantingTicketAction-SendTicketGrantingTicketAction

createTicketGrantingTicketAction-CreateTicketGrantingTicketAction

 

  • DefaultLoginWebflowConfigurer

 此类做了一些初始化操作:初始化流,异常,视图状态等

protected void doInitialize() {

final Flow flow = getLoginFlow();

if (flow != null) {

createInitialFlowActions(flow);

createDefaultGlobalExceptionHandlers(flow);

createDefaultEndStates(flow);

createDefaultDecisionStates(flow);

createDefaultActionStates(flow);

createDefaultViewStates(flow);

createRememberMeAuthnWebflowConfig(flow);

setStartState(flow, CasWebflowConstants.STATE_ID_INITIAL_AUTHN_REQUEST_VALIDATION_CHECK);

}

 

2. 访问应用

 

  1. 初始化访问

 

  • InitialFlowSetupAction

InitialFlowSetupAction的doExecute要做的就是把ticketGrantingTicketId,warnCookieValue和service放到FlowScope的作用域中,以便在登录流程中的state中进行判断。

public Event doExecute(final RequestContext context) {

final HttpServletRequest request = WebUtils.getHttpServletRequestFromExternalWebflowContext(context);

if (request.getMethod().equalsIgnoreCase(HttpMethod.POST.name())) {

WebUtils.putInitialHttpRequestPostParameters(context);

}

//设置 TGC cookie路径 /cas 或者/

configureCookieGenerators(context);

//cookie,静态认证,密码策略等放入flowScop,TGT放入FlowScope/RequestScope

configureWebflowContext(context);

//将service进行注册并且放入RequestScope

configureWebflowContextForService(context);

return success();

}

 

 

  • InitialAuthenticationRequestValidationAction

初始化认证请求校验

啥没干,就返回一个success的事件

protected Event doExecute(final RequestContext requestContext) {

return this.rankedAuthenticationProviderWebflowEventResolver.resolveSingle(requestContext);

}

 

  • TicketGrantingTicketCheckAction

校验request 上下文的TGT是否合法。

当我们第一次访问集成了CAS单点登录的应用系统,此时应用系统会跳转到CAS单点登录的服务器端。此时,request的cookies中不存在CASTGC(TGT),因此FlowScope作用域中的ticketGrantingTicketId为null

 

public Event doExecute(final RequestContext requestContext) {

// tgt为null 返回tgt不存在的事件

final String tgtId = WebUtils.getTicketGrantingTicketId(requestContext);

if (StringUtils.isBlank(tgtId)) {

return new Event(this, CasWebflowConstants.TRANSITION_ID_TGT_NOT_EXISTS);

}

final Ticket ticket = this.centralAuthenticationService.getTicket(tgtId, Ticket.class);

if (ticket != null && !ticket.isExpired()) {

return new Event(this, CasWebflowConstants.TRANSITION_ID_TGT_VALID);

}

return new Event(this, CasWebflowConstants.TRANSITION_ID_TGT_INVALID);

}

 

 

  • ServiceAuthorizationCheck

判断FlowScope作用域中是否存在service,不存在返回成功事件,如果service存在,查找service的注册信息,判断service是否符合注册服务访问要求,不合法则将未授权的serice放入FlowScope

protected Event doExecute(final RequestContext context) {

final Service service = authenticationRequestServiceSelectionStrategies.resolveService(serviceInContext);

if (service == null) {

return success();

}

final RegisteredService registeredService = this.servicesManager.findServiceBy(service);

if (!registeredService.getAccessStrategy().isServiceAccessAllowed()) {

WebUtils.putUnauthorizedRedirectUrlIntoFlowScope(context, registeredService.getAccessStrategy().getUnauthorizedRedirectUrl());

throw new UnauthorizedServiceException(UnauthorizedServiceException.CODE_UNAUTHZ_SERVICE, msg);

}

return success();

}

 

  • InitializeLoginAction

初始化登录行为,直接返回成功事件,进入登录页面

protected Event doExecute(final RequestContext requestContext) throws Exception {

LOGGER.debug("Initialized login sequence");

return success();

}

 

2. 表单提交

  • InitialAuthenticationAction-AbstractAuthenticationAction

[Transition@1950dc92 on = submit, to = realSubmit]

protected Event doExecute(final RequestContext requestContext) {

// ticket请求事件处理, 为null

final Event serviceTicketEvent = this.serviceTicketRequestWebflowEventResolver.resolveSingle(requestContext);

if (serviceTicketEvent != null) {

fireEventHooks(serviceTicketEvent, requestContext);

return serviceTicketEvent;

}

// 此处是调用认证,得到认证的最后结果时间success

final Event finalEvent = this.initialAuthenticationAttemptWebflowEventResolver.resolveSingle(requestContext);

// 触发认证完成的事件

fireEventHooks(finalEvent, requestContext);

return finalEvent;

}

-------初始认证事件处理器 的resolveSingle干了啥---------

认证调用开始:

调用链为:

  1. InitialAuthenticationAttemptWebflowEventResolver

DefaultAuthenticationSystemSupport:handleInitialAuthenticationTransaction()

DefaultAuthenticationTransactionManager:handle()

PolicyBasedAuthenticationManager:authenticate()

AbstractUsernamePasswordAuthenticationHandler

ChainingPrincipalResolver

 

  • InitialAuthenticationAttemptWebflowEventResolver

这个方法到底干了些啥?

  • 由PolicyBasedAuthenticationManager处理具体的认证逻辑,返回AuthenticationResultBuilder
  • 认证后的事件处理
  • 认证结果放入conversationScope

 

  1. 获取AuthenticationResultBuilder

 

由 InitialAuthenticationAttemptWebflowEventResolver resolveInternal方法处理,最后得到AuthenticationResultBuilder

 

public Set<Event> resolveInternal(final RequestContext context) {

try {

// 从上下文获取凭证及service

final Credential credential = getCredentialFromContext(context);

final Service service = WebUtils.getService(context);

if (credential != null) {

final AuthenticationResultBuilder builder = this.authenticationSystemSupport.handleInitialAuthenticationTransaction(service, credential);

 

先看下提交的数据有些啥?

凭证和service

 

最后由PolicyBasedAuthenticationManager 实现类的authenticate方法进行处理:

  • PolicyBasedAuthenticationManager

public Authentication authenticate(final AuthenticationTransaction transaction) throws AuthenticationException {

// 调用认证预处理器

final boolean result = invokeAuthenticationPreProcessors(transaction);

// 将凭证放入当前线程

AuthenticationCredentialsThreadLocalBinder.bindCurrent(transaction.getCredentials());

// 执行内部认证

final AuthenticationBuilder builder = authenticateInternal(transaction);

}

-------内部认证干了啥???-----

 

  • 内部认证逻辑:

找到认证处理器和用户解析器,进行认证和解析

// 获取凭证

final Collection<Credential> credentials = transaction.getCredentials();

//将凭证放入AuthenticationBuilder

final AuthenticationBuilder builder = new DefaultAuthenticationBuilder(NullPrincipal.getInstance());

credentials.forEach(cred -> builder.addCredential(new BasicCredentialMetaData(cred)));

获取当前事务的认证处理器

final Set<AuthenticationHandler> handlerSet = getAuthenticationHandlersForThisTransaction(transaction);

 

认证处理器有2个,org.apereo.cas.authentication.AcceptUsersAuthenticationHandler做真正的处理。

下一步,迭代所有的凭证,从handler中找到能够处理的认证处理器,如果能够处理,则进行认证及解析用户信息(Principal)

// 根据handler找到用户解析器

final PrincipalResolver resolver = getPrincipalResolverLinkedToHandlerIfAny(handler, transaction);

// 认证并解析用户

authenticateAndResolvePrincipal(builder, credential, resolver, handler);

 

认证内部实现参见:AbstractUsernamePasswordAuthenticationHandler:doAuthentication()

用户解析实现参见:ChainingPrincipalResolver:resolve()

 

认证结果和用户信息都会放在AuthenticationBuilder认证建造器中,返回builder,内部认证完成

 

  • 信息封装

 

填充认证方法属性,认证元数据属性,调用认证后处理,认证结果放入线程中

 

final Authentication authentication = builder.build();

addAuthenticationMethodAttribute(builder, authentication);

populateAuthenticationMetadataAttributes(builder, transaction);

invokeAuthenticationPostProcessors(builder, transaction);

final Authentication auth = builder.build();

final Principal principal = auth.getPrincipal();

principal.getId(), principal.getAttributes(), transaction.getCredentials());

//最后将认证结果放入线程中

AuthenticationCredentialsThreadLocalBinder.bindCurrent(auth);

return auth

 

认证的结果的builder有些啥?

 

用户信息,凭证,认证原数据,认证成功的处理器

 

2. 认证调用结束,事件处理

认证完成返回AuthenticationResultBuilder,进行认证后的事件处理(无事件)

final Set<Event> resolvedEvents = resolveCandidateAuthenticationEvents(context, service,

registeredService);

 

 

3. 放入缓存

授予TGT给认证结果,其实是将认证结果放入conversationScope,并没有生成TGT,名字取得有迷惑性

grantTicketGrantingTicketToAuthenticationResult(context, builder, service);

 

  • CreateTicketGrantingTicketAction

创建或者更新TGT更新到域中

public Event doExecute(final RequestContext context) {

final Service service = WebUtils.getService(context);

final RegisteredService registeredService = WebUtils.getRegisteredService(context);

// 拿到认证结果建造器

final AuthenticationResultBuilder authenticationResultBuilder = WebUtils.getAuthenticationResultBuilder(context);

//先从请求域或者flow中获取tgt->null

final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);

// 基于ticketGrantingTicket生成新的TGT

final TicketGrantingTicket tgt = createOrUpdateTicketGrantingTicket(authenticationResult, authentication, ticketGrantingTicket);

// 缓存TGT

WebUtils.putTicketGrantingTicketInScopes(context, tgt);

WebUtils.putAuthenticationResult(authenticationResult, context);

WebUtils.putAuthentication(tgt.getAuthentication(), context);

return success();

}

 

 

  • SendTicketGrantingTicketAction

将TGT放入cookie,以及处理TGT的销毁,返回成功

final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);

final String ticketGrantingTicketValueFromCookie = WebUtils.getTicketGrantingTicketIdFrom(context.getFlowScope());

if (this.renewalStrategy.isParticipating(context)) {

//将TGT放入cookie

this.ticketGrantingTicketCookieGenerator.addCookie(context, ticketGrantingTicketId);

}

if (ticketGrantingTicketValueFromCookie != null && !ticketGrantingTicketId.equals(ticketGrantingTicketValueFromCookie)) {

// 如果二者不匹配,将cookie中的tgt删除

this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketValueFromCookie);

 

 

  • GenerateServiceTicketAction

完成认证,对指定的TGT和service生成唯一的st,st只是作为客户端的认证标识,而且只有在访问/login的时候会生成

根据TGT获取到ticket ,里面包含了认证结果,获取到认证信息

 

//获取service及TGT

final Service service = WebUtils.getService(context);

final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);

try {

// 根据TGT获取到认证信息

final Authentication authentication = this.ticketRegistrySupport.getAuthenticationFrom(ticketGrantingTicket);

 

 

final Service selectedService = authenticationRequestServiceSelectionStrategies.resolveService(service);

final RegisteredService registeredService = servicesManager.findServiceBy(selectedService);

WebUtils.putRegisteredService(context, registeredService);

WebUtils.putService(context, service);

if (registeredService != null) {

final URI url = registeredService.getAccessStrategy().getUnauthorizedRedirectUrl();

// 将service中的未授权的url缓存起来

WebUtils.putUnauthorizedRedirectUrlIntoFlowScope(context, url);

}

final Credential credential = WebUtils.getCredential(context);

// 根据凭证和认证信息构建一个认证结果建造器builder

final AuthenticationResultBuilder builder = this.authenticationSystemSupport.establishAuthenticationContextFromInitial(authentication, credential);

final AuthenticationResult authenticationResult = builder.build(principalElectionStrategy, service);

// 生成st

final ServiceTicket serviceTicketId = this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicket, service, authenticationResult);

//放入RequestScope

WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);

return success()

 

生成st的逻辑详见:DefaultCentralAuthenticationService:grantServiceTicket()

 

TGT和ST的关系:一对多

tgtId:TGT-4-ZI-xw7c3rqX-zF-vzYj1Hegm4gg3LQRip8BvKYcIoe0jT3rd-pDfTKnBKnPWFLsqcBAA013935-PC

stId:ST-4-8E5BJFvGOc57nvvTh8jebaYzOBYA013935-PC

AuthenticationResult和 Authentication的关系,前者多了credentialProvided和service属性

 

 

  • RedirectToServiceAction

将生成的st和service拼接,重定向到客户端

protected Event doExecute(final RequestContext requestContext) {

//http://portal.demo.qds.sd:18835/

final WebApplicationService service = WebUtils.getService(requestContext);

final Authentication auth = WebUtils.getAuthentication(requestContext);

// ST-4-8E5BJFvGOc57nvvTh8jebaYzOBYA013935-PC

final String serviceTicketId = WebUtils.getServiceTicketFromRequestScope(requestContext);

final ResponseBuilder builder = responseBuilderLocator.locate(service);

// http://portal.demo.qds.sd:18835/?ticket=ST-4-8E5BJFvGOc57nvvTh8jebaYzOBYA013935-PC

final Response response = builder.build(service, serviceTicketId, auth);

return finalizeResponseEvent(requestContext, service, response);

}

 

从response中可以看到,下一步将重定向到service中,并在url后跟上st

 

3. 验证ST

注意st有过期时间限制,断点的时候就会过期

/cas/p3/validateService

进入controller

org.apereo.cas.web.v3.V3ServiceValidateController:handle(),返回一个视图

@GetMapping(path = CasProtocolConstants.ENDPOINT_SERVICE_VALIDATE_V3)

protected ModelAndView handle(final HttpServletRequest request, final HttpServletResponse response) throws Exception {

return super.handleRequestInternal(request, response);

}

调用链:

下面看下主要validateServiceTicket方法的主要逻辑:

  1. 获取service

从st获取service和当前request请求中的service

 

//根据st获取到ServiceTicket对象

final ServiceTicket serviceTicket = this.ticketRegistry.getTicket(serviceTicketId, ServiceTicket.class);

// 从st获取service和当前请求中的service

final Service selectedService = resolveServiceFromAuthenticationRequest(serviceTicket.getService());

final Service resolvedService = resolveServiceFromAuthenticationRequest(service);

 

1. 校验

判断service是否过期,二者是否相等,校验不过抛出异常

if (serviceTicket.isExpired())

if (!serviceTicket.isValidFor(resolvedService))

 

2. 获取信息

根据st获取到TGT,里面包含认证信息authentication和service,认证信息包含用户信息Principal

final TicketGrantingTicket root = serviceTicket.getTicketGrantingTicket().getRoot();

final Authentication authentication = getAuthenticationSatisfiedByPolicy(root.getAuthentication(),

new ServiceContext(selectedService, registeredService));

final Principal principal = authentication.getPrincipal();

 

3. 构建Assertion

根据认证信息构建Assertion对象,最后更新st

final Assertion assertion = new DefaultAssertionBuilder(finalAuthentication)

.with(selectedService)

.with(serviceTicket.getTicketGrantingTicket().getChainedAuthentications())

.with(serviceTicket.isFromNewLogin())

.build();

//最后更新st

this.ticketRegistry.updateTicket(serviceTicket);

 

Assertion对象都有什么:

 

看下最后客户端收到的信息是什么:

  

4. 登出

  • TerminateSessionAction

 

@Override

public Event doExecute(final RequestContext requestContext) {

boolean terminateSession = true;

// isConfirmLogout = false

if (logoutProperties.isConfirmLogout()) {

terminateSession = isLogoutRequestConfirmed(requestContext);

}

if (terminateSession) {

// 下一步去销毁session

return terminate(requestContext);

}

return this.eventFactorySupport.event(this, CasWebflowConstants.STATE_ID_WARN);

}

context中到底有些啥信息

 

此action的主要逻辑为:

  • 获取request和response;
  • 从域中获取tgtId,结果为null;
  • 从cookie中获取tgtId,实际通过从request,header中获取
  • 通知tgt所关联的应用下线
  • 销毁cookie,销毁session

 

public Event terminate(final RequestContext context) {

final HttpServletRequest request = WebUtils.getHttpServletRequestFromExternalWebflowContext(context);

final HttpServletResponse response = WebUtils.getHttpServletResponseFromExternalWebflowContext(context);

//null

String tgtId = WebUtils.getTicketGrantingTicketId(context);

if (StringUtils.isBlank(tgtId)) {

//从cookie中获取

tgtId = this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request);

}

if (StringUtils.isNotBlank(tgtId)) {

LOGGER.debug("Destroying SSO session linked to ticket-granting ticket [{}]", tgtId);

// 通知tgt所关联的应用下线

final List<LogoutRequest> logoutRequests = this.centralAuthenticationService.destroyTicketGrantingTicket(tgtId);

WebUtils.putLogoutRequests(context, logoutRequests);

}

// 销毁cookie

LOGGER.debug("Removing CAS cookies");

this.ticketGrantingTicketCookieGenerator.removeCookie(response);

this.warnCookieGenerator.removeCookie(response);

// 销毁session

destroyApplicationSession(request, response);

LOGGER.debug("Terminated all CAS sessions successfully.");

if (StringUtils.isNotBlank(logoutProperties.getRedirectUrl())) {

WebUtils.putLogoutRedirectUrl(context, logoutProperties.getRedirectUrl());

return this.eventFactorySupport.event(this, CasWebflowConstants.STATE_ID_REDIRECT);

}

return this.eventFactorySupport.success(this);

 

通知应用下线:

  • 获取TicketGrantingTicket对象
  • 由登出管理者logoutManager处理登出
  • 返回登出结果

public List<LogoutRequest> destroyTicketGrantingTicket(final String ticketGrantingTicketId) {

try {

// 根据ticketGrantingTicketId 获取 TicketGrantingTicket对象

final TicketGrantingTicket ticket = getTicket(ticketGrantingTicketId, TicketGrantingTicket.class);

// 将ticket关联的认证信息放入线程 AuthenticationCredentialsThreadLocalBinder.bindCurrent(ticket.getAuthentication());

final List<LogoutRequest> logoutRequests = this.logoutManager.performLogout(ticket);

// 删除ticket

deleteTicket(ticketGrantingTicketId);

//事件发布

doPublishEvent(new CasTicketGrantingTicketDestroyedEvent(this, ticket));

return logoutRequests;

} catch (final InvalidTicketException e) {

LOGGER.debug("TicketGrantingTicket [{}] cannot be found in the ticket registry.", ticketGrantingTicketId);

}

return new ArrayList<>(0);

此处处理链较长:从TerminateSessionAction: doExecute()方法开始一直到DefaultSingleLogoutServiceMessageHandler:performBackChannelLogout()方法

直接看最后的处理逻辑:

通过httpClient发送请求

public boolean performBackChannelLogout(final LogoutRequest request) {

try {

LOGGER.debug("Creating back-channel logout request based on [{}]", request);

final String logoutRequest = this.logoutMessageBuilder.create(request);

final WebApplicationService logoutService = request.getService();

logoutService.setLoggedOutAlready(true);

LOGGER.debug("Preparing logout request for [{}] to [{}]", logoutService.getId(), request.getLogoutUrl());

final LogoutHttpMessage msg = new LogoutHttpMessage(request.getLogoutUrl(), logoutRequest, this.asynchronous);

LOGGER.debug("Prepared logout message to send is [{}]. Sending...", msg);

return this.httpClient.sendMessageToEndPoint(msg);

} catch (final Exception e) {

LOGGER.error(e.getMessage(), e);

}

return false;

}

 

简单看下发送的消息内容:post 表单请求,携带logoutRequest请求参数,其值为xml格式的字符串,包含了 st

 

 

登出结果LogoutRequest

 

参考文档:

https://my.oschina.net/indestiny/blog/202454

https://blog.csdn.net/dovejing/article/details/44523545

https://www.cnblogs.com/xuxiaojian/p/9973866.html

https://segmentfault.com/a/1190000014001205

以上是 Cas服务端源码解析 的全部内容, 来源链接: utcz.com/z/518823.html

回到顶部