CAS 3.x代理配置
http://fallenlord.blogbus.com/logs/57175888.html
前阵子改造系统,需要做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 Proxy的设计原理》,它的博客里有很多CAS相关的原创2. 传说中的被open-open.com收录的CAS学习笔记:http://linliangyi2007.iteye.com/blog/1653103. 传说中的CAS官网的Walkthrough:http://www.ja-sig.org/wiki/display/CAS/Proxy+CAS+Walkthrough相信看完以上三篇神来之笔后,可以对CAS代理这块的原理有个简单的认识
但需要深入理解,显然光靠神来之笔是靠不住的
?
3. 代理相关配置
OK,直接就到配置了,首先我们需要分为两部分来描述,第一部分是代理应用(Proxying Applicatiion)也就是前端的Portal之类的东东:
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. 代理使用方案的一点点讨论?
按照上面的做法,应该说功能都已经实现了,那么现在的问题是什么呢?——性能与压力
这里面有两次请求是我们看起来多余的:
这些请求触发频率是每次代理端向被代理端发送请求时,不光消耗了代理端和被代理端的性能,也对CAS产生了多余的压力。
那么如何解决这个问题呢?有两种方案:
1. 缓存,在代理和被代理两端将PT缓存起来,这样只有第一次双方需要去访问CAS请求颁发PT和校验PT,以后可以直接使用这个PT即可2. 颁发自己的票据,这与前端Browser-CAS客户端的模式很相似,CAS客户端为浏览器颁发SessionID作为自己的认证标识,从而撇开了CAS服务器,那么这里我们也可以由被代理端向代理端颁发一个唯一的标识来作为自己的认证标识这两种方法如出一辙,其实本质上没什么区别。在我们的系统改造中,最后使用的是后者,这是由于它本身就存在一个自己的票据的功能
OK,接下来我们看下使用第二种方案的改造细节吧
方案决定了我们只是需要在第一次代理端到被代理端认证的时候发起与CAS的认证,因此我们继承了org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter类,实现了自己的ValidationFilter只扩展了它的一个方法:
??? HttpURLConnection conn = null;??? try {
??????? conn = (HttpURLConnection) url.openConnection();
??????? // 业务处理
??????? // 将响应回来的myticket存储到Session中,以后每次发送请求都将携带这个myticket
??????? // 而被代理端也将myticket存储到Session中,以后每次互相访问都只需校验此值即可
??????? ...
??? } catch (final Exception ex) {
??????? ...
??? } finally {
??????? if (conn != null) {
??????????? conn.disconnect();
??????? }
??? }
??? ...
}
这个方法将在票据第一次被校验成功的时候触发
在被代理端,我们新建一个地址为/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