CAS源码追踪系列三:cas-server端对请求的处理

目录

  • InitialFlowSetupAction
  • ServiceAuthorizationCheck
  • AuthenticationViaFormAction
  • SendTicketGrantingTicketAction
  • GenerateServiceTicketAction
  • 第一次访问接入cas的另一个应用系统
  • 总结

系列:

上一篇,我们了解了AuthenticationFilter对请求的过滤,如果发现session中没有名为_const_cas_assertion_的assertion对象,而且request中也没有对应的ticket,那么就会跳转到统一登录页面。

那这次我们就来看看cas-server如何处理统一登录(版本:cas-server-3.5.2)。

InitialFlowSetupAction

首先会进入InitialFlowSetupAction,他所做的操作如下:

protected Event doExecute(final RequestContext context)throws Exception {
final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
if (!this.pathPopulated) {
final String contextPath = context.getExternalContext().getContextPath();
final String cookiePath = StringUtils.hasText(contextPath) ? contextPath + "/" : "/";
        logger.info("Setting path for cookies to: " + cookiePath);
this.warnCookieGenerator.setCookiePath(cookiePath);
this.ticketGrantingTicketCookieGenerator.setCookiePath(cookiePath);
this.pathPopulated = true;
    }
//将TGT放在FlowScope作用域中
    context.getFlowScope().put(
"ticketGrantingTicketId", this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request));
//将warnCookieValue放在FlowScope作用域中
    context.getFlowScope().put(
"warnCookieValue", Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request)));
//获取service参数
final Service service = WebUtils.getService(this.argumentExtractors, context);
if (service != null && logger.isDebugEnabled()) {
        logger.debug("Placing service in FlowScope: " + service.getId());
    }
//将service放在FlowScope作用域中
    context.getFlowScope().put("service", service);
return result("success");
}

上面主要做的就是把ticketGrantingTicketId,warnCookieValue和service放到FlowScope的作用域中。

接下来需要做校验:TicketGrantingTicket是都否存在。

如果不存在(首次登录肯定不存在)就会到下一个校验:request中是否有gateway参数(即是都接入网关),如果没有则进入下一个校验:服务认证检查。

ServiceAuthorizationCheck

进入ServiceAuthorizationCheck,他做的操作如下:

protected Event doExecute(final RequestContext context)throws Exception {
final Service service = WebUtils.getService(context);
//No service == plain /login request. Return success indicating transition to the login form
if(service == null) {
return success();
    }
final RegisteredService registeredService = this.servicesManager.findServiceBy(service);
if (registeredService == null) {
        logger.warn("Unauthorized Service Access for Service: [ {} ] - service is not defined in the service registry.", service.getId());
thrownew UnauthorizedServiceException();
    }
elseif (!registeredService.isEnabled()) {
        logger.warn("Unauthorized Service Access for Service: [ {} ] - service is not enabled in the service registry.", service.getId());
thrownew UnauthorizedServiceException();
    }
return success();
}

主要做的就是判断FlowScope作用域中是否存在请求指定的service,如果service存在,查找service的注册信息,看看是都存在和是否被禁用,如果不存在或者禁用了则会抛出未认证服务异常。

然后生成LT为前缀的登录票据loginTicket并将其放到flowScope中。

再然后就是进入登录页面。该页面中有三个隐藏参数:loginTicket、execution、_eventId

AuthenticationViaFormAction

当输入用户名和密码,点击登录按钮时,会执行AuthenticationViaFormAction的doBind方法进行身份绑定。

publicfinalvoiddoBind(final RequestContext context, final Credentials credentials)throws Exception {
final HttpServletRequest request = WebUtils.getHttpServletRequest(context);
//bean中没有注入,这里什么也不做
if (this.credentialsBinder != null && this.credentialsBinder.supports(credentials.getClass())) {
this.credentialsBinder.bind(request, credentials);
    }
}

然后就会执行sumbit方法进行真正的表单提交。

publicfinal String submit(final RequestContext context, final Credentials credentials, final MessageContext messageContext)
throws Exception {
// Validate login ticket
final String authoritativeLoginTicket = WebUtils.getLoginTicketFromFlowScope(context);
final String providedLoginTicket = WebUtils.getLoginTicketFromRequest(context);
//判断FlowScope和request中的loginTicket是否相同
if (!authoritativeLoginTicket.equals(providedLoginTicket)) {
this.logger.warn("Invalid login ticket " + providedLoginTicket);
final String code = "INVALID_TICKET";
        messageContext.addMessage(new MessageBuilder().error().code(code).arg(providedLoginTicket).defaultText(code).build());
return"error";
    }
//FlowScope中获取TGT
final String ticketGrantingTicketId = WebUtils.getTicketGrantingTicketId(context);
//FlowScope中获取service
final Service service = WebUtils.getService(context);
if (StringUtils.hasText(context.getRequestParameters().get("renew"))
            && ticketGrantingTicketId != null && service != null) {
try {
final String serviceTicketId = this.centralAuthenticationService.grantServiceTicket(
                ticketGrantingTicketId, service, credentials);
            WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);
            putWarnCookieIfRequestParameterPresent(context);
return"warn";
        } catch (final TicketException e) {
if (isCauseAuthenticationException(e)) {
                populateErrorsInstance(e, messageContext);
return getAuthenticationExceptionEventId(e);
            }
this.centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);
if (logger.isDebugEnabled()) {
                logger.debug("Attempted to generate a ServiceTicket using renew=true with different credentials", e);
            }
        }
    }
try {
//根据用户凭证构造TGT,把TGT放到requestScope中,同时把TGT缓存到服务器的cache中
        WebUtils.putTicketGrantingTicketInRequestScope(context,
this.centralAuthenticationService.createTicketGrantingTicket(credentials));
        putWarnCookieIfRequestParameterPresent(context);
return"success";
    } catch (final TicketException e) {
        populateErrorsInstance(e, messageContext);
if (isCauseAuthenticationException(e))
return getAuthenticationExceptionEventId(e);
return"error";
    }
}

主要做的就是判断FlowScope和request中的loginTicket是否相同。如果不同跳转到错误页面,如果相同,则根据用户凭证生成TGT(登录成功票据),并放到requestScope作用域中,同时把TGT缓存到服务器的cache中。

SendTicketGrantingTicketAction

根据TGT生成cookie并将其添加到response返回给客户端。

GenerateServiceTicketAction

然后进行service检查,判断FlowScope中是否存在service,如果存在(类似于http://127.0.0.1:8081/cas-server/login?service=http://127.0.0.1:8090/webapp1/main.do)就去调用doExecute方法生成服务票据ServiceTicket

protected Event doExecute(final RequestContext context){
//获取service
final Service service = WebUtils.getService(context);
//获取TGT
final String ticketGrantingTicket = WebUtils.getTicketGrantingTicketId(context);
try {
//根据TGT和service生成service ticket(ST-2-97kwhcdrBW97ynpBbZH5-cas01.example.org)
final String serviceTicketId = this.centralAuthenticationService.grantServiceTicket(ticketGrantingTicket,
            service);
//ST放到requestScope中
        WebUtils.putServiceTicketInRequestScope(context, serviceTicketId);
return success();
    } catch (final TicketException e) {
if (isGatewayPresent(context)) {
return result("gateway");
        }
    }
return error();
}

要做的是获取service和TGT,并根据service和TGT生成以ST为前缀的serviceTicket(例:ST-2-97kwhcdrBW97ynpBbZH5-cas01.example.org),并把serviceTicket放到requestScope中

然后从requestScope中获取serviceTicket,构造response对象,并把response放到requestScope中。

最后重定向到应用系统。

此时流程如下:

跳转到应用系统(http://127.0.0.1:8090/webapp1/main.do?ticket=ST-1-4hH2s5tzsMGCcToDvGCb-cas01.example.org)。

进入CAS客户端的AuthenticationFilter过滤器,由于session中获取名为“const_cas_assertion”的assertion对象不存在,但是request有ticket参数,所以进入到下一个过滤器。

TicketValidationFilter过滤器的validate方法通过httpClient访问CAS服务器端(http://127.0.0.1:8081/cas-server/serviceValidate?ticket=ST-1-4hH2s5tzsMGCcToDvGCb-cas01.example.org&service=http://127.0.0.1:8090/webapp1/main.do)验证ticket是否正确,并返回assertion对象

第一次访问接入cas的另一个应用系统

当第一次访问接入cas的另一个应用系统时,同样跳转到cas-server,还是会先执行InitialFlowSetupAction的初始化,由于之前的系统已经登录过了,FlowScope中存在TGT,则检查FlowScope中是否存在service,如果存在则判断request中是否有renew参数,如果没有就生成serviceTicket,后续和上文流程一样。

总结

本文主要讲了请求跳转到cas-server之后的处理,至此对cas的单点登陆流程及原理有了更多的了解。

参考资料:https://blog.csdn.net/dovejing/article/details/44523545

平时的学习过程记录一下,没有那么高深,希望能帮到大家,与君共同进步。我是敲代码的小鲁班,喜欢的话给个推荐,点赞,关注吧。

原文地址:https://www.cnblogs.com/xuxiaojian/p/9973866.html

时间: 2024-08-28 22:16:56

CAS源码追踪系列三:cas-server端对请求的处理的相关文章

CAS源码追踪系列二:AuthenticationFilter对于请求的处理

上一篇我们说了在web项目中了和spring整合之后,如何进行对应Filter的初始化,如果你还没看过,请点击 <CAS源码追踪系列一:Filter的初始化>. 本篇我们来看看在初始化完成以后,cas-client是如何处理请求的. 源码地址:https://github.com/apereo/java-cas-client 如何你还不太清楚sso的原理,你可以看看这篇文章<单点登录原理与简单实现>. 当访问系统受保护的资源时,cas的过滤器AuthenticationFilter

CAS源码追踪系列一:Filter的初始化

目录 代码跟踪 Spring-web:DelegatingFilterProxy CAS:AuthenticationFilter 总结 最近研究了一下SSO(Single Sign On:单点登录)原理. 于是想借助CAS(基于SSO原理的实现框架)加深一下理解同时参考一下具体代码实现,因此有了此系列文章. 先从CAS-CLIENT说起. 假设你已经掌握了如何在你的web项目中引入CAS.我们以AuthenticationFilter为例,说一说它是如何从初始化的. 代码跟踪 Spring-w

Zookeeper源码阅读(七) Server端Watcher

前言 前面一篇主要介绍了Watcher接口相关的接口和实体类,但是主要是zk客户端相关的代码,如前一篇开头所说,client需要把watcher注册到server端,这一篇分析下server端的watcher. 主要分析Watchmanager类. Watchmanager 这是WatchManager的类图介绍.来看看代码: /** * This class manages watches. It allows watches to be associated with a string *

Spring源码由浅入深系列三 refresh

spring中的refresh是一个相当重要的方法.它完成IOC的第一个阶段,将xml中的bean转化为beanDefinition.详细说明如上图所示. 在上图中,创建obtainFreshBeanFactory的过程值得展开来讲.而IOC的第二个阶段是getBean,都将在后续讲解.

Spring源码由浅入深系列五 CreateBean

blog宗旨:用图说话. 附:目录 Spring源码由浅入深系列五 GetBean Spring源码由浅入深系列四 创建BeanFactory Spring源码由浅入深系列三 refresh Spring源码由浅入深系列二 类结构 Spring源码由浅入深系列一 简介 

[转]jQuery源码分析系列

文章转自:jQuery源码分析系列-Aaron 版本截止到2013.8.24 jQuery官方发布最新的的2.0.3为准 附上每一章的源码注释分析 :https://github.com/JsAaron/jQuery 正在编写的书 - jQuery架构设计与实现 本人在慕课网的教程(完结) jQuery源码解析(架构与依赖模块) 64课时 jQuery源码解析(DOM与核心模块)64课时 jQuery源码分析目录(完结) jQuery源码分析系列(01) : 整体架构 jQuery源码分析系列(

修改CAS源码是的基于DB的认证方式配置更灵活

最近在做CAS配置的时候,遇到了数据源不提供密码等数据的情况下,怎样实现密码输入认证呢? 第一步:新建Java项目,根据假面算法生成CAS加密工具 出于保密需要不提供自定义的加密工具,在您的实际项目中,你可采用cas默认的加密方式比如md5. 第二步:修改CAS源码 找到cas-server-support-jdbc子模块找到包路径cas-server-support-jdbc\src\main\java\org\jasig\cas\adaptors\jdbc\,在复制一份QueryDataba

(六)SSO之CAS框架扩展 修改CAS源码实现与ESS动态密码验证对接

题记: 偶尔的偶尔我们会听到这个网站的数据泄露了,那个网站的用户数据泄露了,让用户重新修改登录密码,所以,对于用户数据安全性越发的引起我们的重视了,尤其是一些保密性要求高的网站,更需要增加安全性了. 正文: 对于安全性问题,我们如何解决呢? 解决方案: 1.避免sql注入问题. 2.用户登录密码加密. 3.使用https安全访问方式. 4.使用第三方设备,像银行一般使用的密码口令. 5.... 前三种方案是比较常见的,这里主要说第四种解决方案,我们在前三种方案的基础上,使用了第三方的设备,就像网

Dubbo源码分析系列-服务的发布

RPC简化类图 RPC模块核心接口和抽象实现 默认实现Dubbo协议的接口和抽象实现 服务发布过程 调用过程 上图是服务提供者暴露服务的主过程: 首先ServiceConfig类拿到对外提供服务的实际类ref(如:HelloWorldImpl),然后通过ProxyFactory类的getInvoker方法使用ref生成一个AbstractProxyInvoker实例,到这一步就完成具体服务到Invoker的转化.接下来就是Invoker转换到Exporter的过程. Dubbo处理服务暴露的关键