`

CAS 源码分析 (非proxy模式)

    博客分类:
  • cas
阅读更多

 

一、CAS 基本原理   (3,4,5,9.2,9.3是主要步骤)

第一次访问:
1. 浏览器   发起访问WebAPP 请求:  http://www.web.com/app
2. 客户端  AuthenticationFilter Filter 发现Session中无 Assertion,且URL中无 ticket 变量。生成 service url 变量,并重定向到:  https://www.cas-server.com/cas/login?service=http://www.web.com/app



3. CAS server  生成 Login ticket, service 对象,并展示 login 页面,默认提供 username / password 给用户验证。
4. CAS server 端,用户输入 username / password 验证,若通过则生成TGT,存入服务器段(默认为 Map 类型的 cache),同时将TGT id 作为 content创建 cookie 并发送到浏览器。
5. CAS server 端通过TGT 生成service ticket.  重定向到 http://www.web.com/app?ticket=ST-xxx

 

 

6. 客户端   访问 http://www.web.com/app?ticket=ST-xxx
7. 客户端   AuthenticationFilter Filter 发现URL中有 ticket, 跳过 AuthenticationFilter过滤器,到达 Cas20ProxyReceivingTicketValidationFilter过滤器。


8. 客户端 生成验证 service url:  http://www.web.com/app
9. 客户端   Cas20ProxyReceivingTicketValidationFilter 过滤器,使用6处的ticket 与8处的 service 作为参数验证。

  9.1  客户端 生成验证 servlet: https://www.cas-server.com/cas/serviceValidate?ticket=ST-xxx&service=http://www.web.com/app
  9.2  客户端 通过HttpClient访问 9.1 的 url
                注:AbstractUrlBasedTicketValidator.java line 207,如果是用CAS ptotocol验证,则第二个参数 ticket无用。
                得到如下形式的 response:

546 <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
547         <cas:authenticationSuccess>
548                 <cas:user>jack</cas:user>
549 
550 
551         </cas:authenticationSuccess>
552 </cas:serviceResponse>

   9.3  客户端 解析 response 字符串,生成 assertion (包含 username, validate info 等)

   9.4  客户端 设置 assertion 为 request 的 _const_cas_assertion_ 属性。

   9.5  客户端 如果设置了重定向属性,则重定向到 http://www.web.com/app  --  转步骤10

              否则继续执行以后的 filter,通过servlet 访问 http://www.web.com/app 服务,结束CAS的验证。

 

用户已成功登录,非第一次访问:

10. 客户端 通过重定向访问 http://www.web.com/app

11. 客户端 AuthenticationFilter Filter 发现 session 中有Assertion, 结束本过滤器,转移到下一个过滤器 Cas20ProxyReceivingTicketValidationFilter.

12. 客户段 Cas20ProxyReceivingTicketValidationFilter 发现 本次访问的URL 无 ticket,结束本次过滤,转移到下一个过滤器,继续执行以后的 filter,通过servlet 访问 http://www.web.com/app 服务。

 

二 源码解析

 

1. 客户端 web.xml  片段:

 

...

<filter>
		  <filter-name>CAS Authentication Filter</filter-name>
		  <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
		  <init-param>
			    <param-name>casServerLoginUrl</param-name>
			    <param-value>https://www.colorcc.com:8443/cas/login</param-value>
		  </init-param>
		  <init-param>
			    <param-name>serverName</param-name>
			    <param-value>http://localhost:8080</param-value>
		  </init-param>
	</filter>
	<filter>
		  <filter-name>CAS Validation Filter</filter-name>
		  <filter-class>org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter</filter-class>
		  <init-param>
			    <param-name>casServerUrlPrefix</param-name>
			    <param-value>https://www.colorcc.com:8443/cas</param-value>
		  </init-param>
		  <init-param>
			    <param-name>serverName</param-name>
			    <param-value>http://localhost:8080</param-value>
		  </init-param>
 	<!--	  <init-param>
			    <param-name>redirectAfterValidation</param-name>
			    <param-value>false</param-value>
		  </init-param> -->
	</filter>

	<filter-mapping>
		<filter-name>CAS Authentication Filter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
	<filter-mapping>
		<filter-name>CAS Validation Filter</filter-name>
		<url-pattern>/*</url-pattern>
	</filter-mapping>
...

 

2.  AuthenticationFilter 代码:

public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {
        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final HttpSession session = request.getSession(false);
        final Assertion assertion = session != null ? (Assertion) session.getAttribute(CONST_CAS_ASSERTION) : null;
        // 如果 session 中有 assertion,则结束 authentication 过滤器,直接跳到下一个过滤器
        if (assertion != null) {
            filterChain.doFilter(request, response);
            return;
        }

        //  2.1 如果 session 中无 assertion, 则构造 service,  如 http://www.web.com/a1
        final String serviceUrl = constructServiceUrl(request, response);





        final String ticket = CommonUtils.safeGetParameter(request,getArtifactParameterName());
        final boolean wasGatewayed = this.gatewayStorage.hasGatewayedAlready(request, serviceUrl);

        // 如果 request 中有 ticke变量,则结束本过滤器,直接跳到下一个过滤器
        if (CommonUtils.isNotBlank(ticket) || wasGatewayed) {
            filterChain.doFilter(request, response);
            return;
        }

        final String modifiedServiceUrl;

        log.debug("no ticket and no assertion found");
        if (this.gateway) {
            log.debug("setting gateway attribute in session");
            modifiedServiceUrl = this.gatewayStorage.storeGatewayInformation(request, serviceUrl);
        } else {
            modifiedServiceUrl = serviceUrl;

        }

        if (log.isDebugEnabled()) {
            log.debug("Constructed service url: " + modifiedServiceUrl);
        }

        // 2.2 否则构造重定向 URL, 其中 casServerLoginUrl 为 web.xml 中 filter 配置,eg: https://www.cas-server.com/cas/login?service=http://www.web.com/a1
       final String urlToRedirectTo = CommonUtils.constructRedirectUrl(this.casServerLoginUrl, getServiceParameterName(), modifiedServiceUrl, this.renew, this.gateway);


        if (log.isDebugEnabled()) {
            log.debug("redirecting to \"" + urlToRedirectTo + "\"");
        }

        //  2.3 重定向到 CAS server 
        response.sendRedirect(urlToRedirectTo);






    }

  2.1 构造 service url:    http://www.web.com/a1

protected final String constructServiceUrl(final HttpServletRequest request, final HttpServletResponse response) {
        return CommonUtils.constructServiceUrl(request, response, this.service, this.serverName, this.artifactParameterName, this.encodeServiceUrl);
    }

 

3. 重定向URL:  https://www.cas-server.com/cas/login?service=http://www.web.com/a1, 其中 cas server的 web.xml:

<servlet>
    <servlet-name>cas</servlet-name>
    <servlet-class>
      org.jasig.cas.web.init.SafeDispatcherServlet
    </servlet-class>
    <init-param>
      <param-name>publishContext</param-name>
      <param-value>false</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
  </servlet>
	
  <servlet-mapping>
    <servlet-name>cas</servlet-name>
    <url-pattern>/login</url-pattern>
  </servlet-mapping>

 

     3.1  SafeDispatcherServlet 使用 Spring DispatcherServlet 作为 delegate

 

public final class SafeDispatcherServlet extends HttpServlet {

    // 定义 Spring DispatcherServlet 作为 delegate
    private DispatcherServlet delegate = new DispatcherServlet();

    // 使用 delegate 初始化 servlet 
    public void init(final ServletConfig config) {
        try {
            this.delegate.init(config);

        } catch (final Throwable t) {
         ...

     // 使用 delegate 的 service 执行 web 操作
     public void service(final ServletRequest req, final ServletResponse resp)
        throws ServletException, IOException {
        if (this.initSuccess) {
            this.delegate.service(req, resp);
        } else {
            throw new ApplicationContextException(
                "Unable to initialize application context.");
        }
    }
 

    3.2 cas-servlet.xml 配置文件如下, 可以看到 login 对应的 webflow 为: login-webflow.xml

 

  <webflow:flow-registry id="flowRegistry" flow-builder-services="builder">
    <webflow:flow-location path="/WEB-INF/login-webflow.xml" id="login"/>
  </webflow:flow-registry>

    3.3  根据 login-webflow.xml 配置文件(结合 cas-servlet.xml):

 

<on-start>
        <evaluate expression="initialFlowSetupAction" />
    </on-start>

<bean id="initialFlowSetupAction" class="org.jasig.cas.web.flow.InitialFlowSetupAction"
        p:argumentExtractors-ref="argumentExtractors"
        p:warnCookieGenerator-ref="warnCookieGenerator"
        p:ticketGrantingTicketCookieGenerator-ref="ticketGrantingTicketCookieGenerator"/>

     3.4 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;
        }
        
        // 给 FlowScope 的设置 ticketGrantingTicketId, warnCookieValue 参数
        context.getFlowScope().put(
            "ticketGrantingTicketId", this.ticketGrantingTicketCookieGenerator.retrieveCookieValue(request));
        context.getFlowScope().put("warnCookieValue",
            Boolean.valueOf(this.warnCookieGenerator.retrieveCookieValue(request)));
       
         // 3.4.1 抽取 service 参数
        final Service service = WebUtils.getService(this.argumentExtractors, context);

        if (service != null && logger.isDebugEnabled()) {
            logger.debug("Placing service in FlowScope: " + service.getId());
        }

        // 给 FlowScope 的设置 service 参数
        context.getFlowScope().put("service", service);

        return result("success");
    }

     3.4.1  WebApplicationService.getService

public static WebApplicationService getService(
        final List<ArgumentExtractor> argumentExtractors,
        final HttpServletRequest request) {
        for (final ArgumentExtractor argumentExtractor : argumentExtractors) {
            
            // 3.4.1.1 通过配置的 argumentExtractor 抽取 service
            final WebApplicationService service = argumentExtractor.extractService(request);

            if (service != null) {
                return service;
            }
        }

        return null;
    }

 

     3.4.1.1  CasArgumentExtractor 代码

public final class CasArgumentExtractor extends AbstractSingleSignOutEnabledArgumentExtractor {

    public final WebApplicationService extractServiceInternal(final HttpServletRequest request) {
        return SimpleWebApplicationServiceImpl.createServiceFrom(request, getHttpClientIfSingleSignOutEnabled());
    }
}

// SimpleWebApplicationServiceImpl
    private static final String CONST_PARAM_SERVICE = "service";
    private static final String CONST_PARAM_TARGET_SERVICE = "targetService";
    private static final String CONST_PARAM_TICKET = "ticket";
    private static final String CONST_PARAM_METHOD = "method";

public static SimpleWebApplicationServiceImpl createServiceFrom(
        final HttpServletRequest request, final HttpClient httpClient) {
        final String targetService = request
            .getParameter(CONST_PARAM_TARGET_SERVICE);
        final String method = request.getParameter(CONST_PARAM_METHOD);
        final String serviceToUse = StringUtils.hasText(targetService)
            ? targetService : request.getParameter(CONST_PARAM_SERVICE);

        if (!StringUtils.hasText(serviceToUse)) {
            return null;
        }

        final String id = cleanupUrl(serviceToUse);
        final String artifactId = request.getParameter(CONST_PARAM_TICKET);

        return new SimpleWebApplicationServiceImpl(id, serviceToUse,
            artifactId, "POST".equals(method) ? ResponseType.POST
                : ResponseType.REDIRECT, httpClient);
    }

private SimpleWebApplicationServiceImpl(final String id,
        final String originalUrl, final String artifactId,
        final ResponseType responseType, final HttpClient httpClient) {
        super(id, originalUrl, artifactId, httpClient);
        this.responseType = responseType;
    }

protected AbstractWebApplicationService(final String id, final String originalUrl, final String artifactId, final HttpClient httpClient) {
        this.id = id;
        this.originalUrl = originalUrl;
        this.artifactId = artifactId;
        this.httpClient = httpClient;
    }
 

 

3. Cas20ProxyReceivingTicketValidationFilter 及 AbstractTicketValidationFilter代码:

public final void doFilter(final ServletRequest servletRequest, final ServletResponse servletResponse, final FilterChain filterChain) throws IOException, ServletException {

        if (!preFilter(servletRequest, servletResponse, filterChain)) {
            return;
        }

        final HttpServletRequest request = (HttpServletRequest) servletRequest;
        final HttpServletResponse response = (HttpServletResponse) servletResponse;
        final String ticket = CommonUtils.safeGetParameter(request, getArtifactParameterName());

        // 如果 URL 中包含 ticket 参数,则执行 service 验证工作
        if (CommonUtils.isNotBlank(ticket)) {
            if (log.isDebugEnabled()) {
                log.debug("Attempting to validate ticket: " + ticket);
            }

            try {
               
                final Assertion assertion = this.ticketValidator.validate(ticket, constructServiceUrl(request, response));

                if (log.isDebugEnabled()) {
                    log.debug("Successfully authenticated user: " + assertion.getPrincipal().getName());
                }

                request.setAttribute(CONST_CAS_ASSERTION, assertion);

                if (this.useSession) {
                    request.getSession().setAttribute(CONST_CAS_ASSERTION, assertion);
                }
                onSuccessfulValidation(request, response, assertion);

                if (this.redirectAfterValidation) {
                    log. debug("Redirecting after successful ticket validation.");
                    response.sendRedirect(constructServiceUrl(request, response));
                    return;
                }
            } catch (final TicketValidationException e) {
                response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                log.warn(e, e);

                onFailedValidation(request, response);

                if (this.exceptionOnValidationFailure) {
                    throw new ServletException(e);
                }

                return;
            }
        }

        // 如果不包含 ticket, 直接跳过CAS Filter验证,继续其他 filter 或 web app 操作
        filterChain.doFilter(request, response);

    }
 

 

 

 

 

 

 

 

  • 大小: 228.1 KB
分享到:
评论
1 楼 wangv 2016-08-12  
    

相关推荐

    cas-proxy认证

    ### CAS-Proxy 认证详解 #### 一、概述 CAS (Central Authentication Service) 是一个开源项目,主要用于简化Web应用的单点登录流程。CAS 支持多种认证机制,并允许客户端应用通过代理的方式进行认证,即所谓的...

    Proxy 模式学习代码

    Proxy模式是一种设计模式,它允许我们为一个对象创建一个代理对象,这个代理对象在客户端和目标对象之间起到...通过分析和运行这些示例,你可以更深入地掌握如何在Java中使用Proxy模式来增强对象的功能或控制其访问。

    apache开源项目源码commons-proxy-1.0-src(全部高质量代理模式proxy的java源程序)

    java.proxy,代理模式源码,设计模式,apache开源项目源码commons-proxy-1.0-src 各种代理模式操作的工具类源码以及代理模式案例源码,你会从中得到意想不到的效果! apache开源组织开发的开源项目源码,其优良的代码...

    CAS代理模式

    5. **源码分析**:对于开发者来说,理解CAS代理模式的源码实现至关重要。通过阅读源码,我们可以深入了解其工作流程,包括票证的生成、验证以及代理过程中的安全控制机制。 **使用工具进行CAS代理模式的实现** 在...

    简单工厂模式,工厂方法模式,抽象工厂模式和Proxy模式

    到工厂方法到抽象工厂,这几种都带有“工厂”的模式,总是容易叫人迷糊,我仔细研究了下,然后用简单的例子做类比,列出了自己的通俗理解和比较,大部分是自己的体会,感觉理的比较清楚,末尾提了下Proxy模式。

    Proxy模式

    Proxy模式是一种设计模式,它在软件工程中扮演着重要的角色...在阅读和分析源码时,识别出Proxy模式有助于我们更好地理解和优化代码。通过实践,我们可以将Proxy模式应用于各种场景,提升软件系统的可维护性和扩展性。

    CAS-SSO源码包(两个版本)

    CAS(Central ...同时,如果你正在进行CAS的二次开发或者想要定制化CAS以适应特定环境,源码分析是必不可少的步骤。通过对源码的学习,你可以深入理解其内部工作原理,从而更好地利用和调整CAS以满足需求。

    设计模式C++学习之代理模式(Proxy)

    代理模式是一种设计模式,它是结构型模式之一,主要用于在客户端和目标对象之间建立一个代理对象,以便控制对目标对象的访问。在C++中,代理模式可以用来为其他对象提供一种代理以控制对这个对象的访问,或者增加...

    安卓Android源码——TorProxy和Shadow开源项目.zip

    安卓Android源码——TorProxy和Shadow开源项目.zip

    Proxy源代码分析(包括源码分析和可在Linux下运行的源码)

    这段代码虽然只是描述了最简单的proxy操作,但它的确是经典,它不仅清晰地描述了客户机/服务器系统的概念,而且几乎包括了Linux网络编程的方方面面,非常适合Linux网络编程的初学者学习。  这段Proxy程序的用法是...

    设计模式之代理模式proxy.zip

    JDK动态代理基于接口实现,它要求目标对象必须实现至少一个接口,然后通过Proxy类和InvocationHandler接口来创建并控制代理对象。当我们调用代理对象的方法时,实际执行的是InvocationHandler的invoke方法,这样可以...

    cas代理模式代码示例

    在本示例中,我们将重点讨论如何在Java环境下使用代理模式来实现代理逻辑,特别是在CAS(Central Authentication Service)认证框架中的应用。CAS是一种基于Web的单点登录(SSO)解决方案,广泛用于网络应用程序的...

    Proxy源代码分析

    Proxy源代码分析可以帮助我们深入了解网络通信的原理以及如何在Linux环境下实现客户端-服务器模型。以下是对Proxy源代码及其涉及的关键知识点的详细说明: 1. **套接字(socket)**:在Linux网络编程中,套接字是进程...

    CAS-4.2.7源码

    4. **代理票证(Proxy Ticket)**:在需要代表用户访问其他资源时,CAS服务器会生成的一种特殊的票证。 5. **Ticket Granting Ticket(TGT)**:用户首次登录时,CAS服务器生成的主票证,用于生成后续的服务票证。 ...

    C++ Proxy模式

    Proxy模式是软件设计模式中的一个关键元素,属于结构型模式的一种。在C++中,Proxy模式主要用于为其他对象提供一种代理以控制对这个对象的访问。这种模式的核心思想是通过创建一个代理对象来充当目标对象的中介,...

    dotnet的casclient源码

    **源码分析** 在提供的压缩包中,包含以下几个关键文件和项目: 1. **DotNetCasClient.sln** 和 **DotNetCasClient.vs2010.sln**:这是解决方案文件,用于在Visual Studio中打开和管理项目。两个版本的解决方案...

    基于Proxy模式的分布式MySQL数据库中间件设计源码

    MySQL数据库中间件:基于C语言开发,包含59个文件,包括22个C源文件...该项目是一个分布式MySQL数据库中间件,采用Proxy模式设计,基于核心业务对象切分,旨在提供高效的数据库查询和处理能力,适用于分布式系统环境。

    proxy.rar java三种代理模式源码

    在"DynamicProxy"目录下的源码中,可以看到如何使用这些类和接口创建并操作动态代理的例子。 3. 接口代理(JDK动态代理): 接口代理是基于Java的反射机制实现的动态代理,适用于目标对象实现了接口的情况。在...

    cas 配置client 1.0 &2.0 及proxy DEMO 说明

    cas 配置client 1.0 &2.0 及proxy DEMO 说明 1 cas server 搭建 1.1 资源准备 cas server 下载 http://www.ja-sig.org/downloads/cas/cas-server-3.3.1-release.zip 1.2 解压后打开cas-server-3.3.1-release\cas-...

Global site tag (gtag.js) - Google Analytics