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. 访问应用
初始化访问
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干了啥---------
认证调用开始:
调用链为:
- InitialAuthenticationAttemptWebflowEventResolver
DefaultAuthenticationSystemSupport:handleInitialAuthenticationTransaction()
DefaultAuthenticationTransactionManager:handle()
PolicyBasedAuthenticationManager:authenticate()
AbstractUsernamePasswordAuthenticationHandler
ChainingPrincipalResolver
InitialAuthenticationAttemptWebflowEventResolver
这个方法到底干了些啥?
- 由PolicyBasedAuthenticationManager处理具体的认证逻辑,返回AuthenticationResultBuilder
- 认证后的事件处理
- 认证结果放入conversationScope
获取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方法的主要逻辑:
获取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