CAS代理配置- 掉到坑里- 博客大巴
  • :转载时请以超链接形式标明文章原始出处和作者信息及


    前阵子改造系统,需要做CAS的Proxy,上网找了很久,找到了一些讲述原理的文章,但几乎没有讲述配置的文章,有讲述配置的也是错误的或是不完整的或是针对2.x版本的,还有很多是直接把代理配置当做普通配置使用-_-

    那么,既然网上没有,我就在这里写一下吧

    1. 什么情况下需要使用代理

    显然这是最需要首先搞清楚的一个问题

    假设现在有服务A使用了CAS,我们作为用户zhangsan去访问服务A,这没有什么问题
    然后出现了一个新的需求,有服务B也实现了CAS,要求我们仍然只是访问A,但是A会以我们相同的用户(zhangsan)去访问服务B获取我们所需的内容并呈现出来。
    此时A就被我们称为代理(Proxy),B被称为后台服务(back-end service)或被代理应用(Proxied Application)

    一个简单的应用场景就是Portal,我们只访问Portal,由Portal去各个子系统抓取我们所需的内容并呈现出来
    如果有兄弟没有玩过Portal,那么iGoogle也是个很好的场景,你在iGoogle上登录后就可以直接查看到GMail的内容了,这就是用了Proxy技术

     

    2. 代理运转原理

    运转原理写起来比较麻烦,这里推荐三篇文章,均是介绍CAS的代理运转原理的:

    • 1. 传说中的David Turing写的《》,它的博客里有很多CAS相关的原创
    • 2. 传说中的被open-open.com收录的CAS学习笔记:
    • 3. 传说中的CAS官网的Walkthrough:

    相信看完以上三篇神来之笔后,可以对CAS代理这块的原理有个简单的认识
    但需要深入理解,显然光靠神来之笔是靠不住的

     

    3. 代理相关配置

    OK,直接就到配置了,首先我们需要分为两部分来描述,{dy}部分是代理应用(Proxying Applicatiion)也就是前端的Portal之类的东东:

    <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>http://www.cas-server.com</param-value>
        </init-param>
        <init-param>
            <param-name>redirectAfterValidation</param-name>
            <param-value>true</param-value>
        </init-param>
        <init-param>
            <param-name>exceptionOnValidationFailure</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>http://www.local-app.com</param-value>
        </init-param>
        <init-param>
            <param-name>proxyCallbackUrl</param-name>
            <param-value>http://www.local-app.com/cas-proxy/proxyCallback</param-value>
        </init-param>
        <init-param>
            <param-name>proxyReceptorUrl</param-name>
            <param-value>/proxyCallback</param-value>
        </init-param>
    </filter>

    高亮的两个参数配置将为这个服务提供一个callback的拦截,使得在每次validator去校验ST的时候,CAS服务器都会发起一个回调请求来访问proxyCallbackUrl的位置,将PGT(及PGTIou)告诉这个代理应用,而代理应用会保存这个PGT到Session中

    第二部分是后端服务: 

    <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>http://www.cas-server.com</param-value>
        </init-param>
        <init-param>
            <param-name>redirectAfterValidation</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>exceptionOnValidationFailure</param-name>
            <param-value>false</param-value>
        </init-param>
        <init-param>
            <param-name>serverName</param-name>
            <param-value>http://www.local-app.com</param-value>
        </init-param>
        <init-param>
            <param-name>acceptAnyProxy</param-name>
            <param-value>true</param-value>
        </init-param>

        <init-param>
            <param-name>proxyCallbackUrl</param-name>
            <param-value>http://www.local-app.com/cas-backend/proxyCallback</param-value>
        </init-param>
        <init-param>
            <param-name>proxyReceptorUrl</param-name>
            <param-value>/proxyCallback</param-value>
        </init-param>
    </filter> 

    以上高亮代码的acceptAnyProxy是后端服务所必须的(它与allowedProxyChains两个配置二选一),若没有配置acceptAnyProxy或allowedProxyChains,将无法校验代理票据(Proxy Ticket)

    而proxyCallbackUrl和proxyReceptorUrl是可选的,如果给出则在校验代理票据(Proxy Ticket)时同时CAS会再次执行回调颁发PGT给此后台服务,使之可以同时成为代理用于代理另一个后台服务。若不配置则CAS不会告诉此应用PGT

     

    4. 如何使用代理

    看起来好像大功告成了,但是实际上还有最重要的一点我们还没做,就Server to Server的这个请求要怎么来发的问题

    首先,既然要发送请求,一定是我们的代理端已经完成了到CAS的认证,那么我们首先获取到Assertion:

    然后从assertion中获取到proxy ticket:

    {zh1}将proxyTicket以参数的形式绑定到serviceUrl(待访问的backend service地址)之后并发送请求,参数名必须是ticket:

    URL url = new URL(serviceUrl + "?ticket=" + proxyTicket);
    HttpURLConnection conn = null;
    try {
        conn = (HttpURLConnection) url.openConnection();
        // 业务处理
        ...
    } catch (final Exception ex) {
        ...
    } finally {
        if (conn != null) {
            conn.disconnect();
        }
    }
    ...

    这样,当后台服务(back-end service)获取到这条请求后,会到CAS上去校验ticket是否为合法的PT,若合法则允许通过(此时redirectAfterValidation应该为false以避免直接响应一个302 Redirect请求)

     

    5. 代理使用方案的一点点讨论 

    按照上面的做法,应该说功能都已经实现了,那么现在的问题是什么呢?——性能与压力
    这里面有两次请求是我们看起来多余的:

    • 1. 从代理端Assertion中获取PT需要发送一次请求给CAS(PT由CAS颁发)
    • 2. 从被代理端获取到PT后发送请求给CAS以求校验

    这些请求触发频率是每次代理端向被代理端发送请求时,不光消耗了代理端和被代理端的性能,也对CAS产生了多余的压力。

    那么如何解决这个问题呢?有两种方案:

    • 1. 缓存,在代理和被代理两端将PT缓存起来,这样只有{dy}次双方需要去访问CAS请求颁发PT和校验PT,以后可以直接使用这个PT即可
    • 2. 颁发自己的票据,这与前端Browser-CAS客户端的模式很相似,CAS客户端为浏览器颁发SessionID作为自己的认证标识,从而撇开了CAS服务器,那么这里我们也可以由被代理端向代理端颁发一个{wy}的标识来作为自己的认证标识

    这两种方法如出一辙,其实本质上没什么区别。在我们的系统改造中,{zh1}使用的是后者,这是由于它本身就存在一个自己的票据的功能

    OK,接下来我们看下使用第二种方案的改造细节吧

    方案决定了我们只是需要在{dy}次代理端到被代理端认证的时候发起与CAS的认证,因此我们继承了org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter类,实现了自己的ValidationFilter只扩展了它的一个方法:

    protected void onSuccessfulValidation(final HttpServletRequest request, final HttpServletResponse response, final Assertion assertion) {
        // 认证服务地址
        final String loginServiceUrl = "";
       
        // serviceUrl为要发送的目标地址——被代理端上的一个URL   
        final String proxyTicket = assertion.getPrincipal().getProxyTicketFor(loginServiceUrl);
        final String username = assertion.getPrincipal().getName();

        // 获取到URLConnection或是HttpClient
        URL url = new URL(loginServiceUrl + "?ticket=" + proxyTicket);
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection) url.openConnection();
            // 业务处理
            // 将响应回来的myticket存储到Session中,以后每次发送请求都将携带这个myticket
            // 而被代理端也将myticket存储到Session中,以后每次互相访问都只需校验此值即可
            ...
        } catch (final Exception ex) {
            ...
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
        ...
    }

    这个方法将在票据{dy}次被校验成功的时候触发

    在被代理端,我们新建一个地址为/loginService的服务,大致逻辑如下:

    // 获取CAS票据
    String proxyTicket = req.getParameter("ticket");
    if (proxyTicket == null) {
        throw new WebScriptException(HttpServletResponse.SC_BAD_REQUEST, "Proxy ticket not specified");
    }

    try {

        // 使用CAS代理票据校验器校验PT
        final Cas20ProxyTicketValidator validator = new Cas20ProxyTicketValidator(casServerUrlPrefix);
        validator.setAcceptAnyProxy(true);
        final String serviceUrl = req.getServerPath() + req.getServicePath();
        final Assertion assertion = validator.validate(proxyTicket, serviceUrl);

        // 完成认证并从Session中获取myticket
        authenticationComponent.setCurrentUser(username);
        final String myTicket = authenticationService.getCurrentTicket();
       
        render(myTicket);
       
    } catch (AuthenticationException ex) {
        logger.error("Login failed", ex);
        throw new WebScriptException(HttpServletResponse.SC_FORBIDDEN, "Login failed");

    } catch (TicketValidationException ex) {
        logger.error("Ticket validate failred.", ex);
        throw new WebScriptException(HttpServletResponse.SC_FORBIDDEN, "Ticket validate failred");
       
    } finally {
        // ...
    }

    可以看到,我们自己实例化了一个CAS的Validator来完成校验而不是通过那些CAS的Filter,这使得整个后端服务可以无需被CAS Filter拦截(当然,被拦截了也没啥关系)

    OK,至此,一个较为完整的CAS代理就完成了,have fun





郑重声明:资讯 【CAS代理配置- 掉到坑里- 博客大巴】由 发布,版权归原作者及其所在单位,其原创性以及文中陈述文字和内容未经(企业库qiyeku.com)证实,请读者仅作参考,并请自行核实相关内容。若本文有侵犯到您的版权, 请你提供相关证明及申请并与我们联系(qiyeku # qq.com)或【在线投诉】,我们审核后将会尽快处理。
—— 相关资讯 ——