STEP 1,搭建Java Web服务器环境
安装 JDK + Tomcat 6.0.14 , HTTP端口8080 , HTTPS端口8443
JAVA_HOME = D:/Java/jdk1.6.0_04
CATALINA_HOME = D:/Java/apache-tomcat-6.0.14
安装完毕,启动Tomcat ,在浏览器上 测试 http://Linly:8080/
出现上述界面,表明系统STEP1成功搭建。
STEP 2,使用Java Keytool工具为系统生成HTTPS证书,并为系统注册
(Java Keytool相关资料可参阅:Java keytool 安全证书学习笔记), 在DOS窗体运行以下指令(建议编写一个BAT批处理文件执行)
cls
rem please set the env JAVA_HOME before run this bat file
rem delete alia tomcat if it is existed
keytool -delete -alias tomcatsso -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit
keytool -delete -alias tomcatsso -storepass changeit
(注释: 清除系统中可能存在的名字为tomcatsso 的同名证书)
rem list all alias in the cacerts
keytool -list -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit
(注释: 列出系统证书仓库中存在证书名称列表)
rem generator a key
keytool -genkey -keyalg RSA -alias tomcatsso -dname "cn=linly" -storepass changeit
(注释:指定使用RSA算法,生成别名为tomcatsso的证书,存贮口令为changeit,证书的DN为"cn=linly" ,这个DN必须同当前主机完整名称一致哦,切记!!!)rem export the key
keytool -export -alias tomcatsso -file %java_home%/jre/lib/security/tomcatsso.crt -storepass changeit
(注释: 从keystore中导出别名为tomcatsso的证书,生成文件tomcatsso.crt)rem import into trust cacerts
keytool -import -alias tomcatsso -file %java_home%/jre/lib/security/tomcatsso.crt -keystore %java_home%/jre/lib/security/cacerts -storepass changeit
(注释:将tomcatsso.crt导入jre的可信任证书仓库。注意,安装JDK是有两个jre目录,一个在jdk底下,一个是独立的jre,这里的目录必须同Tomcat使用的jre目录一致,否则后面Tomcat的HTTPS通讯就找不到证书了)
rem list all alias in the cacerts
keytool -list -keystore %JAVA_HOME%/jre/lib/security/cacerts -storepass changeit
(注释:列出jre可信任证书仓库中证书名单,验证先前的导入是否成功,如果导入成功,应该在列表中能找到tomcatsso这个别名,如下图)[/quote]
同时,在D:/Java/jdk1.6.0_04/jre/lib/security目录下能找到“tomcatsso.crt”这个文件;在C:/Documents and Settings/Linly目录下能找到“.keystore”文件。
满足上述条件则STEP2部署完成。
STEP 3,配置Tomcat的HTTPS服务
编辑D:/Java/apache-tomcat-6.0.14/conf下的server.xml文件,在connector的配置位置添加以下的配置:
port="8443" minSpareThreads="5" maxSpareThreads="75"
enableLookups="true" disableUploadTimeout="true"
acceptCount="100" maxThreads="200"
scheme="https" secure="true" SSLEnabled="true"
keystoreFile="C:/Documents and Settings/new/.keystore" keystorePass="changeit"
truststoreFile="D:/Java/jdk1.6.0_04/jre/lib/security/cacerts"
clientAuth="false" sslProtocol="TLS"/>
启动Tomcat,访问https://linly:8443/,出现以下界面说明HTTPS配置生效:
STEP 4,为HelloWorldExample程序配置CAS过滤器
访问http://linly:8080/examples/servlets/servlet/HelloWorldExample,出现以下界面说明应用正常启动:
编辑D:/Java/apache-tomcat-6.0.14/webapps/examples/WEB-INF下的web.xml文件,添加如下信息:
<filter-name>CAS Filter</filter-name>
<filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>
<param-value>https://Linly:8443/cas/login</param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
<param-value>https://Linly:8443/cas/serviceValidate</param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
<param-value>Linly:8080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/servlets/servlet/HelloWorldExample</url-pattern>
</filter-mapping>
拷贝casclient.jar文件到目录D:/Java/apache-tomcat-6.0.14/webapps/examples/WEB-INF/lib下。
由于我们使用的是Tomcat6.0.14,因此,还要拷贝commons-logging-1.0.4.jar到该目录下。
STEP 5,部署JA-SIG(CAS)服务器
拷贝cas.war到D:/Java/apache-tomcat-6.0.14/webapps目录下。启动Tomcat,访问网址http://linly:8080/cas/index.jsp,出现以下画面:
输入用户名/密码 :linly/linly(任意两个相同的字窜),点击“登录”,出现以下画面:
表示CAS服务器配置运行成功。
STEP 6,测试JA-SIG(CAS)部署结果
启动Tomcat。
测试使用浏览器登陆以下网址:http://linly:8080/examples/servlets/servlet/HelloWorldExample,页面将弹出以下认证框,点击“确定”
页面将重定向到JA-SIG的SSO登录认证页面
输入用户名=密码,如:linly/linly,则通过验证,进入应用的入口界面,如下:
细心的用户将发现,此时的URL不再是:
http://linly:8080/examples/servlets/servlet/HelloWorldExample,
URL的尾端带上了一个ticket参数:
http://linly:8080/examples/servlets/servlet/HelloWorldExample?ticket=ST-2-qTcfDrdFb0bWndWgaqZD
到此,JA-SIG(CAS)服务器的初步SSO部署宣告成功。
什么是SSO(Single Sign On)单点登录:
所谓单点登录是指基于用户/会话认证的一个过程,用户只需一次性提供凭证(仅一次登录),就可以访问多个应用。
目前单点登录主要基于Web的多种应用程序,即通过浏览器实现对多个B/S架构应用的统一账户认证。
JA-SIG(CAS)的设计愿景:
简单的说,CAS(Central Authentication Service – 中心认证服务)的目的就是使分布在一个企业内部各个不同异构系统的认证工作集中在一起,通过一个公用的认证系统统一管理和验证用户的身份。在CAS上认证的用户将获得CAS颁发的一个证书,使用这个证书,用户可以在承认CAS证书的各个系统上自由穿梭访问,不需要再次的登录认证。打个比方:对于加入欧盟的国家而言,在他们国家中的公民可以凭借着自己的身份证,在整个欧洲旅行,不用签证。对于企业内部系统而言,CAS就是这个颁发欧盟认证的系统,其它系统都是加入欧盟的国家,它们要共同遵守和承认CAS的认证规则。
因此CAS的设计愿景就是:
1。实现一个易用的、能跨不同Web应用的单点登录认证中心;
2。实现统一的用户身份和密钥管理,减少多套密码系统造成的管理成本和安全漏洞;
3。降低认证模块在IT系统设计中的耦合度,提供更好的SOA设计和更弹性的安全策略
CAS1.0服务架构实现:
传统的用户认证流程
我们以A公司的员工日志管理系统为例,如下图:
![](http://linliangyi2007.javaeye.com/upload/picture/pic/8430/a4e2c08e-2186-3134-b524-5dcfc7c0acba.gif)
使用CAS后的用户认证流程
![](http://linliangyi2007.javaeye.com/upload/picture/pic/8432/f9331683-d112-3152-b5b0-7af63754b05c.gif)
示意图中,CAS相关部分被标示为蓝色。在这个流程中,员工AT向日志系统请求进入主页面,他的浏览器发出的HTTP请求被嵌入在日志系统中的CAS客户端(HTTP过滤器)拦截,并判断该请求是否带有CAS的证书;如果没有,员工AT将被定位到CAS的统一用户登录界面进行登录认证,成功后,CAS将自动引导AT返回日志系统的主页面。
CAS的程序逻辑实现
要完成上述的认证业务,CAS需要一个认证中心服务器CAS -Server和嵌入在不同业务系统方的认证客户端CAS-Client的协同。
在CAS1.0协议中,CAS-Server提供的三个验证服务接口(web服务URL):
1. 用户登录URL,形如 https://casserver/cas/servlet/login
2. 用户凭证校验URL,形如 https://casserver/cas/servlet/validate
3. 用户登出URL,形如 https://casserver/cas/servlet/logout
在CAS-Client端,CAS提供了多样化的语言支持,其中用于java的是一个casclient.jar包。目前的版本为2.1.1,其中提供了三种形式的凭证校验:
1. 用于Java Servlets的Filter — edu.yale.its.tp.cas.client.filter.CASFilter
2. 用于JSP页面的CAS Tag Library
3. 通用Java API Object — ServiceTicketValidator / ProxyTicketValidator
通常,企业应用程序基于浏览器的B/S模式,这种情况下,系统的用户凭证(一个由CAS服务器生成的唯一 id号,也称之为ticket)借助cookie和URL参数方式实现;在B/S环境中,大多情况下,我们只需要配置CAS Filter或者使用CAS Tag Library就可以轻松实现的验证客户端。
如果应用是以普通的C/S模式运行,则需要应用程序自己来维护这个ticket在上下文环境中的传输和保存了。这时候就需要手工调用ServiceTicketValidator / ProxyTicketValidator对象的方法,向CAS 服务器提交认证,并获取认证结果进行相应的处理。
CAS服务的具体实现
环境假设:用户User要访问业务系统Biz;Biz系统部署在bizserver上;CAS的系统搭建在服务器casserver上。
![点击查看原始大小图片](http://linliangyi2007.javaeye.com/upload/picture/pic/8434/b67ef0b8-def6-36fb-93a5-817c761b39f2.gif)
图例说明:
Step1: 用户第一次访问Biz系统主页http://bizserver/index.jsp ;部署在Biz系统上的CASFilter发现用户尚未登录,将用户重定向的CAS登录界面 https://casserver/cas/servlet/login?service=http://bizserver/index.jsp ,同时在重定向的URL上用service参数将用户的目标地址传给CAS服务器。
Step2:用户在CAS的登录页上输入用户名密码登录,CAS服务器认证通过后,生成一个ticket,并带在目标地址的尾部返回客户端的浏览器redirect:http://bizserver/index.jsp?ticket=casticket.
Step3:客户端浏览器获得CAS服务器的认证应答,取得凭证ticket后,使用重定向的链接http://bizserver/index.jsp?ticket=casticket访问Biz服务
Step4: BizServer上的CASFilter再次过滤访问请求,并获得ticket凭证。Filter将使用该凭证通过URL https://casserver/cas/servlet/validate?service= http://bizserver/index.jsp &ticket=casticket 向CAS认证中心确认对应的服务请求和凭证是否有效。根据CAS服务器返回的结果,如果凭证有效,则CASFilter允许用户进入http://bizserver/index.jsp 所指向的页面;否则,再次重定向到https://casserver/cas/servlet/login?service=http://bizserver/index.jsp 上要求用户进行认证。
CAS2.0服务架构实现:
CAS2.0的协议主要是针对web应用的SSO功能增强的协议,它在1.0协议基础上扩展了Proxy Authentication(代理认证)能力。那么什么是Proxy Authentication呢?!
代理认证Proxy Authentication
假设有一下这样的应用场景:用户AT早晨来到公司,他的第一件事就是进入公司的Portal系统浏览一天的新咨询,如股票信息、天气情况、业界新闻。他通过CAS的身份认证登录了门户系统,看到了他订制的信息。之后,他要访问portal中的邮件信息,看看有没有新的邮件。这时候Portal系统必须访问他的IMAP服务器,这需要他的私人密码。我们知道Portal是通过CAS对AT进行认证的,因此Portal上没有AT的个人密码信息。这时,我们发现,Portal需要代表AT的身份向IMAP服务器提交身份认证,而这正是Proxy Authentication的作用。
CAS2.0系统架构中的角色
![](http://linliangyi2007.javaeye.com/upload/picture/pic/8436/8981a0de-5d35-3e9c-8e7e-3c48a4cb1189.gif)
CAS2.0系统中的用到的凭证(ticket)
![](http://linliangyi2007.javaeye.com/upload/picture/pic/8438/bdd8ec3e-766b-3a0f-bc3f-86113aad7e28.gif)
以上对于CAS2.0协议中用到的5种ticket的说明,乍看起来也许会让你云里雾里的。没关系,下面我们就来详细阐述这5种凭证在实际认证流程中的作用。在阐述具体流程前,我们要先关注一下2.0协议中对客户端配置的需求.
CAS2.0的客户端配置
在2.0协议中,CAS-Server端的配置与1.0基本一致。但在客户端上,多增加了一个call back URL,该URL用来提供server端向client端传输PGT时使用。因此,除了要配置edu.yale.its.tp.cas.client.filter.CASFilter作为认证过滤器外,还要配置edu.yale.its.tp.cas.proxy.ProxyTicketReceptor这个servlet,作为server回传PGT的call back URL,如下:
![](http://linliangyi2007.javaeye.com/upload/picture/pic/8440/e0bb9188-5f6a-3659-810e-52ea0aa0c897.gif)
CAS2.0代理认证流程
以下的流程图模拟上述的用户AT通过Portal向他的IMAP邮件服务器请求电子邮件的认证过程。在该过程中,充当Service和Proxy两个角色的Portal使用CAS Filter对访问其自身的用户进行CAS认证;同时Portal要使用ProxyTicketReceptor servlet接收来自CAS server的PGT信息,并使用ProxyTicketValidator对象向CAS获取访问IMAP服务器的Proxy Ticket凭证;最终从IMAP服务器上获取AT用户的mail信息。同样的,这里的IMAP服务器也要接受并认可CAS对其用户的认证管理,同时它自己也成为二级Proxy,在有需要的情况下,一样可以向它的back-end Service发起Proxy Authentication代理认证请求……
![](http://linliangyi2007.javaeye.com/upload/picture/pic/8442/96fec43d-c28e-339d-951b-2cd26160200d.gif)
其中蓝色线表示HTTP或HTTPS的请求;红色线表示应答;黑色线表示来自CAS server端的回调操作。
到此,本章节对JA-SIG(CAS)的整体功能和身份认证业务架构进行初步的讲解,在后续的章节中,我们将对CAS平台的服务端和客户端的编程与应用定制等相关内容的进行介绍。
JA-SIG(CAS)学习笔记3
关键字: cas sso 统一身份认证 java ja-sigJA-SIG CAS服务环境搭建,请参考 : JA-SIG(CAS)学习笔记1
JA-SIG CAS业务架构介绍,请参考 : JA-SIG(CAS)学习笔记2
HTTPS所涉及的Java安全证书知识,请参考 : Java keytool 安全证书学习笔记
CAS技术框架
CAS Server
目前,我们使用的CAS Server 3.1.1的是基于Spring Framework编写的,因此在CAS服务器端的配置管理中,绝大多数是Spring式的Java Bean XML配置。CAS 的服务器提供了一套易于定制的用户认证器接口,用户可以根据自身企业的在线系统的认证方式,来定制自己的认证逻辑。不论是传统的用户名/密码方式,还是基于安全证书的方式;是基于关系数据库的存储,还是采用LDAP服务器,CAS Server给我们提供了这些常用的验证器模板代码,只要稍作修改,便可灵活使用了。
对于广大的中国企业用户而言,另一个需要定制的功能莫过于全中文、企业特色的用户身份认证页面了。CAS Server提供了两套系统界面,一套是默认的CAS英文标准页面,另一套则是专门提供给用户来定制修改的。(PS:老外们做事情就是人性化啊~~)那么对CAS Server端的后续学习,我们将围绕着身份认证模块定制和界面定制这两方面展开。
CAS Client
客户端我们使用的是CAS Client 2.1.1。虽然在官方网站上已出现了3.1.0版本的下载,但该版本地代码已经完全重写,使用的package和类名同2.1.1大相径庭了,最关键的是,该版本暂时没有对应的API说明文档。虽然咖啡我对程序版本怀有极大的“喜新厌旧”的心态,但安全起见,还是先2.1.1吧,相信3.1.0的文档耶鲁大学的大牛们已经在整理了,期待中……
CAS Client2.1.1.jar中的代码是相当精炼的,有兴趣的朋友建议阅读一下源码。Jar包中的代码分成三个大部分
1. edu.yale.its.tp.cas.util 包,其中只有一个工具类 SecureURL.java 用来访问HTTPS URL
2. edu.yale.its.tp.cas.proxy包,用来处理Proxy Authentication代理认证的3个类,其中ProxyTicketReceptor.java是 接收PGT回调的servlet,在下文中我们会提及。
3. edu.yale.its.tp.cas.client包,其中包含了CAS Filter ,Tag Library等主要的认证客户端工具类,我们在后面会进行重点介绍。
针对CAS Client的学习,我们的重点将放在CAS Filter 和ProxyTicketReceptor 的配置以及在Java SE环境下,直接使用 ServiceTicketValidator进行Ticket认证实现上。
CAS服务器端应用
定制适合你的身份认证程序
通过前面的学习,我们了解了CAS具有一个良好而强大的SSO功能框架。接下来,我们要学习如何将实际企业应用中的身份认证同CAS进行整合。
简单的说,要将现有企业应用中的认证集成到CAS Server中,只要实现一个名为AuthenticationHandler的一个认证处理Java接口就行。以下是该接口的源代码:
- public interface AuthenticationHandler {
- /**
- * 该方法决定一个受支持的credentials是否是可用的,
- * 如果可用,该方法返回true,则说明身份认证通过
- */
- boolean authenticate(Credentials credentials) throws AuthenticationException;
- /**
- * 该方法决定一个credentials是否是当前的handle所支持的
- */
- boolean supports(Credentials credentials);
- }
- public interface AuthenticationHandler {
- /**
- * 该方法决定一个受支持的credentials是否是可用的,
- * 如果可用,该方法返回true,则说明身份认证通过
- */
- boolean authenticate(Credentials credentials) throws AuthenticationException;
- /**
- * 该方法决定一个credentials是否是当前的handle所支持的
- */
- boolean supports(Credentials credentials);
- }
public interface AuthenticationHandler {
/**
* 该方法决定一个受支持的credentials是否是可用的,
* 如果可用,该方法返回true,则说明身份认证通过
*/
boolean authenticate(Credentials credentials) throws AuthenticationException;
/**
* 该方法决定一个credentials是否是当前的handle所支持的
*/
boolean supports(Credentials credentials);
}
这里我们要说明一下Credentials这个CAS的概念。所谓Credentials是由外界提供给CAS来证明自身身份的信息,简单的如一个用户名/密码对就是一个Credentials,或者一个经过某种加密算法生成的密文证书也可以是一个Credentials。在程序的实现上,Credentials被声明为一个可序列化的接口,仅仅起着标识作用,源代码如下:
- public interface Credentials extends Serializable {
- // marker interface contains no methods
- }
- public interface Credentials extends Serializable {
- // marker interface contains no methods
- }
public interface Credentials extends Serializable {
// marker interface contains no methods
}
CAS的API中,已经为我们提供了一个最常用的实现UsernamePasswordCredentials 用户名/密码凭证,代码如下:
- public class UsernamePasswordCredentials implements Credentials {
- /** Unique ID for serialization. */
- private static final long serialVersionUID = -8343864967200862794L;
- /** The username. */
- private String username;
- /** The password. */
- private String password;
- public final String getPassword() {
- return this.password;
- }
- public final void setPassword(final String password) {
- this.password = password;
- }
- public final String getUsername() {
- return this.username;
- }
- public final void setUsername(final String userName) {
- this.username = userName;
- }
- public String toString() {
- return this.username;
- }
- public boolean equals(final Object obj) {
- if (obj == null || !obj.getClass().equals(this.getClass())) {
- return false;
- }
- final UsernamePasswordCredentials c = (UsernamePasswordCredentials) obj;
- return this.username.equals(c.getUsername())
- && this.password.equals(c.getPassword());
- }
- public int hashCode() {
- return this.username.hashCode() ^ this.password.hashCode();
- }
- }
- public class UsernamePasswordCredentials implements Credentials {
- /** Unique ID for serialization. */
- private static final long serialVersionUID = -8343864967200862794L;
- /** The username. */
- private String username;
- /** The password. */
- private String password;
- public final String getPassword() {
- return this.password;
- }
- public final void setPassword(final String password) {
- this.password = password;
- }
- public final String getUsername() {
- return this.username;
- }
- public final void setUsername(final String userName) {
- this.username = userName;
- }
- public String toString() {
- return this.username;
- }
- public boolean equals(final Object obj) {
- if (obj == null || !obj.getClass().equals(this.getClass())) {
- return false;
- }
- final UsernamePasswordCredentials c = (UsernamePasswordCredentials) obj;
- return this.username.equals(c.getUsername())
- && this.password.equals(c.getPassword());
- }
- public int hashCode() {
- return this.username.hashCode() ^ this.password.hashCode();
- }
- }
public class UsernamePasswordCredentials implements Credentials {
/** Unique ID for serialization. */
private static final long serialVersionUID = -8343864967200862794L;
/** The username. */
private String username;
/** The password. */
private String password;
public final String getPassword() {
return this.password;
}
public final void setPassword(final String password) {
this.password = password;
}
public final String getUsername() {
return this.username;
}
public final void setUsername(final String userName) {
this.username = userName;
}
public String toString() {
return this.username;
}
public boolean equals(final Object obj) {
if (obj == null || !obj.getClass().equals(this.getClass())) {
return false;
}
final UsernamePasswordCredentials c = (UsernamePasswordCredentials) obj;
return this.username.equals(c.getUsername())
&& this.password.equals(c.getPassword());
}
public int hashCode() {
return this.username.hashCode() ^ this.password.hashCode();
}
}
很简单不是吗?就是存储一个用户名和密码的java bean而已。
接下来,我们将一个Credentials传给一个AuthenticationHandler进行认证,首先调用boolean supports(Credentials credentials)方法察看当前传入的Credentials实例,AuthenticationHandler实例现是否支持它?如果支持,再调用boolean authenticate(Credentials credentials)方法进行认证。由于用户名/密码方式是最常用的认证方法,因此CAS为我们提供了一个现成的基于该方式的抽象认证处理类AbstractUsernamePasswordAuthenticationHandler。通常我们只需要继承该类,并实现其中的 authenticateUsernamePasswordInternal方法即可。下面我们给出一个Demo的实现类,它的校验逻辑很简单——仅校验用户名的字符长度是否与密码的相等(这里密码是一个表示长度的整数),如果相等则认为认证通过,请看代码:
- public class UsernameLengthAuthnHandler
- extends AbstractUsernamePasswordAuthenticationHandler {
- protected boolean authenticateUsernamePasswordInternal( UsernamePasswordCredentials credentials) throws AuthenticationException {
- /*
- * 这里我们完全可以用自己的认证逻辑代替,比如将用户名/密码传入一个SQL语句
- * 向数据库验证是否有对应的用户账号,这不是我们最经常干的事么?
- * 只需要将下面的程序替换掉就OK了!!So easy,so simple!
- /
- String username = credentials.getUsername();
- String password = credentials.getPassword();
- String correctPassword = Integer.toString(username.length());
- return correctPassword.equals(password);
- }
- }
- public class UsernameLengthAuthnHandler
- extends AbstractUsernamePasswordAuthenticationHandler {
- protected boolean authenticateUsernamePasswordInternal( UsernamePasswordCredentials credentials) throws AuthenticationException {
- /*
- * 这里我们完全可以用自己的认证逻辑代替,比如将用户名/密码传入一个SQL语句
- * 向数据库验证是否有对应的用户账号,这不是我们最经常干的事么?
- * 只需要将下面的程序替换掉就OK了!!So easy,so simple!
- /
- String username = credentials.getUsername();
- String password = credentials.getPassword();
- String correctPassword = Integer.toString(username.length());
- return correctPassword.equals(password);
- }
- }
public class UsernameLengthAuthnHandler
extends AbstractUsernamePasswordAuthenticationHandler {
protected boolean authenticateUsernamePasswordInternal( UsernamePasswordCredentials credentials) throws AuthenticationException {
/*
* 这里我们完全可以用自己的认证逻辑代替,比如将用户名/密码传入一个SQL语句
* 向数据库验证是否有对应的用户账号,这不是我们最经常干的事么?
* 只需要将下面的程序替换掉就OK了!!So easy,so simple!
/
String username = credentials.getUsername();
String password = credentials.getPassword();
String correctPassword = Integer.toString(username.length());
return correctPassword.equals(password);
}
}
介绍到这里,大家应该清楚如何定制自己的AuthenticationHandler类了吧!这里要附带说明的是,在CAS Server的扩展API中已经提供了大量常用认证形式的实现类,它们同CAS Server的war包一同分发:
cas-server-support-generic-3.1.1.jar ——使用Map记录用户认证信息的实现
cas-server-support-jdbc-3.1.1.jar —— 基于Spring JDBC的数据库实现(我们常用的)
cas-server-support-ldap-3.1.1.jar —— 基于LDAP的用户认证实现
更多其他形式的实现各位看官有兴趣的,可以一一阅读源码。
配置你的身份认证程序
完成了定制认证类的代码编写,接下来就是要让CAS Server来调用它了。在CAS的框架中,对程序的配置都是使用Spring Framework的xml文件,这对于熟悉Spring的程序员而言算驾轻就熟了。
配置文件位于应用部署目录的WEB-INF子目录下——deployerConfigContext.xml。在bean id=authenticationManager 的 authenticationHandlers属性中配置我们的AuthenticationHandlers:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd">
<bean id="authenticationManager"
class="org.jasig.cas.authentication.AuthenticationManagerImpl">
。。。
。。。
<property name="authenticationHandlers">
<list>
<bean class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref="httpClient" />
<!—下面就是系统默认的验证器配置,你可以替换它,或者增加一个新的handler -->
<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />
</list>
</property>
</bean>
。。。
。。。
</beans>
我们发现authenticationHandlers属性是一个list,在这个list中可以配置多个AuthenticationHandlers。这些AuthenticationHandlers形成了一个验证器链,所有提交给CAS的Credentials信息将通过这个验证器链的链式过滤,只要这链中有一个验证器通过了对Credentials的验证,就认为这个Credentials是合法的。这样的设计使得我们可以很轻松的整合不同验证体系的已有应用到同一个CAS上,比如:A验证器负责校验alpha系统提交的Credentials,它是基于LDAP服务器的;B验证器负责校验beta系统提交的Credentials,它是一个传统的RDB用户表认证;C验证器负责校验gamma系统提交的基于RSA证书加密的Credentials。3种完全不同的用户身份认证通过配置就可以统一在同一个CAS服务内,很好很强大,不是吗!!
定制身份验证登录界面
CAS Server在显示界面层view使用了“主题Theme”的概念。在{project.home}/webapp/WEB-INF/view/jsp/目录下,系统默认提供了两套得UI —— default和simple 。default方案使用了CSS等相对复杂得界面元素,而simple方案提供了最简化的界面表示方式。在整个的CAS Server服务器端,有四个界面是我们必须要实现的:
casConfirmView.jsp —— 确认信息(警告信息)页面
casGenericSuccess.jsp —— 登陆成功提示页面
casLoginView.jsp —— 登录输入页面
casLogoutView.jsp —— SSO登出提示页面
这些都是标准的jsp页面,如何实现他们,完全由您说了算,除了名字不能改。
CAS为view的展示提供了3个级别的定制方式,让我们从最直观简单的开始吧。
1. 采用文件覆盖方式:直接修改default中的页面或者将新写好的四个jsp文件覆盖到default目录中。这种方式最直观和简单,但咖啡建议各位在使用这种方式前将原有目录中的文件备份一下,以备不时之需。
2. 修改UI配置文件,定位UI目录:在CAS Server端/webapp/WEB-INF/classes/ 目录下,有一个名为default_views.properties的属性配置文件,你可以通过修改配置文件中的各个页面文件位置,指向你新UI文件,来达到修改页面展示的目的。
3. 修改配置文件的配置文件,这话看起来有点别扭,其实一点不难理解。在方法2中的default_views.properties文件是一整套的UI页面配置。如果我想保存多套的UI页面配置就可以写多个的properties文件来保存这些配置。在CAS Server端/webapp/WEB-INF/目录下有cas-servlet.xml和cas.properties两个文件,cas-servlet.xml使用了cas.properties文件中的cas.viewResolver.basename属性来定义view属性文件的名字,因此你可以选者直接修改cas-servlet.xml中的viewResolver 下的basenames属性,或者修改cas.properties中的cas.viewResolver.basename属性,指定新的properties文件名,这样可以轻松的替换全套UI。
CAS客户端配置及API应用
CASFilter的配置
对于大部分web应用而言,使用CAS集成统一认证是相对简单的事,只要为需要认证的URL配置edu.yale.its.tp.cas.client.filter.CASFilter认证过滤器。下面我们就针对过滤器的配置进行说明。首先参看一下Filter的基本配置:
...
<filter>
<filter-name>CAS Filter</filter-name>
<filter-class>edu.yale.its.tp.cas.client.filter.CASFilter</filter-class>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.loginUrl</param-name>
<param-value>https://secure.its.yale.edu/cas/login<;/param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
<param-value>https://secure.its.yale.edu/cas/serviceValidate<;/param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
<param-value>your server name and port (e.g., www.yale.edu:8080)</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Filter</filter-name>
<url-pattern>/requires-cas-authetication/*</url-pattern>
</filter-mapping>
...
</web-app>
上述配置中的init-param是filter的3个必备的属性,下面这张表则是filter全部属性的详细说明:
![](http://linliangyi2007.javaeye.com/upload/picture/pic/8444/fdb2f2cb-9707-3228-8235-7e6658c0016e.gif)
ProxyTicketReceptor的配置
大家还记得在前面我们说过的Proxy Authentication中的call back URL吗?ProxyTicketReceptor是部署在client端的一个servlet,提供server端回传PGT和PGTIOU的。它的xml部署如下:
<web-app>
...
<servlet>
<servlet-name>ProxyTicketReceptor</servlet-name>
<servlet-class>edu.yale.its.tp.cas.proxy.ProxyTicketReceptor</servlet-class>
<init-param>
<param-name>edu.yale.its.tp.cas.proxyUrl</param-name>
<param-value>https://secure.its.yale.edu/cas/proxy<;/param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>ProxyTicketReceptor</servlet-name>
<url-pattern>/CasProxyServlet</url-pattern>
</servlet-mapping>
...
</webapp>
这里要说明的是它的参数edu.yale.its.tp.cas.proxyUrl。在服务端通过ProxyTicketReceptor将PGT和PGTIOU传给客户端后,ProxyTicketReceptor在进行Proxy Authentication的过程中需要向服务端请求一个ProxyTicket(PT),这个proxyUrl就是服务端的请求入口了。(关于Proxy Authentication的运作原理,参见 JA-SIG(CAS)学习笔记2 )
CAS Client端的API应用1.用户可以通过以下两种方式的任意一种,从JSP或servlet中获取通过认证的用户名:
或者
String username = (String)session.getAttribute("edu.yale.its.tp.cas.client.filter.user");
2.获得更完整的受认证用户信息对象CASReceipt Java Bean,可以使用以下语句的任一:
或者
CASReceipt receipt = (CASReceipt )session.getAttribute("edu.yale.its.tp.cas.client.filter.receipt");
3.手工编码使用CAS Java Object进行用户验证,使用ServiceTicketValidator或者 ProxyTicketValidator(代理认证模式下),在servlet中对用户身份进行验证。
3-1.ServiceTicketValidator
- import edu.yale.its.tp.cas.client.*;
- ...
- String user = null;
- String errorCode = null;
- String errorMessage = null;
- String xmlResponse = null;
- /* instantiate a new ServiceTicketValidator */
- ServiceTicketValidator sv = new ServiceTicketValidator();
- /* set its parameters */
- sv.setCasValidateUrl("https://secure.its.yale.edu/cas/serviceValidate");
- sv.setService(urlOfThisService);
- sv.setServiceTicket(request.getParameter("ticket"));
- String urlOfProxyCallbackServlet = "https://portal.yale.edu/CasProxyServlet";
- sv.setProxyCallbackUrl(urlOfProxyCallbackServlet);
- /* contact CAS and validate */
- sv.validate();
- /* if we want to look at the raw response, we can use getResponse() */
- xmlResponse = sv.getResponse();
- if(sv.isAuthenticationSuccesful()) {
- user = sv.getUser();
- } else {
- errorCode = sv.getErrorCode();
- errorMessage = sv.getErrorMessage();
- }
- /* The user is now authenticated. */
- /* If we did set the proxy callback url, we can get proxy tickets with: */
- String urlOfTargetService = "http://hkg2.its.yale.edu/someApp/portalFeed";
- String proxyTicket = ProxyTicketReceptor.getProxyTicket( sv.getPgtIou() , urlOfTargetService);
- import edu.yale.its.tp.cas.client.*;
- ...
- String user = null;
- String errorCode = null;
- String errorMessage = null;
- String xmlResponse = null;
- /* instantiate a new ServiceTicketValidator */
- ServiceTicketValidator sv = new ServiceTicketValidator();
- /* set its parameters */
- sv.setCasValidateUrl("https://secure.its.yale.edu/cas/serviceValidate");
- sv.setService(urlOfThisService);
- sv.setServiceTicket(request.getParameter("ticket"));
- String urlOfProxyCallbackServlet = "https://portal.yale.edu/CasProxyServlet";
- sv.setProxyCallbackUrl(urlOfProxyCallbackServlet);
- /* contact CAS and validate */
- sv.validate();
- /* if we want to look at the raw response, we can use getResponse() */
- xmlResponse = sv.getResponse();
- if(sv.isAuthenticationSuccesful()) {
- user = sv.getUser();
- } else {
- errorCode = sv.getErrorCode();
- errorMessage = sv.getErrorMessage();
- }
- /* The user is now authenticated. */
- /* If we did set the proxy callback url, we can get proxy tickets with: */
- String urlOfTargetService = "http://hkg2.its.yale.edu/someApp/portalFeed";
- String proxyTicket = ProxyTicketReceptor.getProxyTicket( sv.getPgtIou() , urlOfTargetService);
import edu.yale.its.tp.cas.client.*;
...
String user = null;
String errorCode = null;
String errorMessage = null;
String xmlResponse = null;
/* instantiate a new ServiceTicketValidator */
ServiceTicketValidator sv = new ServiceTicketValidator();
/* set its parameters */
sv.setCasValidateUrl("https://secure.its.yale.edu/cas/serviceValidate");
sv.setService(urlOfThisService);
sv.setServiceTicket(request.getParameter("ticket"));
String urlOfProxyCallbackServlet = "https://portal.yale.edu/CasProxyServlet";
sv.setProxyCallbackUrl(urlOfProxyCallbackServlet);
/* contact CAS and validate */
sv.validate();
/* if we want to look at the raw response, we can use getResponse() */
xmlResponse = sv.getResponse();
if(sv.isAuthenticationSuccesful()) {
user = sv.getUser();
} else {
errorCode = sv.getErrorCode();
errorMessage = sv.getErrorMessage();
}
/* The user is now authenticated. */
/* If we did set the proxy callback url, we can get proxy tickets with: */
String urlOfTargetService = "http://hkg2.its.yale.edu/someApp/portalFeed";
String proxyTicket = ProxyTicketReceptor.getProxyTicket( sv.getPgtIou() , urlOfTargetService);
3-2.ProxyTicketValidator
- import edu.yale.its.tp.cas.client.*;
- ...
- String user = null;
- String errorCode = null;
- String errorMessage = null;
- String xmlResponse = null;
- List proxyList = null;
- /* instantiate a new ProxyTicketValidator */
- ProxyTicketValidator pv = new ProxyTicketValidator();
- /* set its parameters */
- pv.setCasValidateUrl("https://secure.its.yale.edu/cas/proxyValidate");
- pv.setService(urlOfThisService);
- pv.setServiceTicket(request.getParameter("ticket"));
- String urlOfProxyCallbackServlet = "https://portal.yale.edu/CasProxyServlet";
- pv.setProxyCallbackUrl(urlOfProxyCallbackServlet);
- /* contact CAS and validate */
- pv.validate();
- /* if we want to look at the raw response, we can use getResponse() */
- xmlResponse = pv.getResponse();
- /* read the response */
- if(pv.isAuthenticationSuccesful()) {
- user = pv.getUser();
- proxyList = pv.getProxyList();
- } else {
- errorCode = pv.getErrorCode();
- errorMessage = pv.getErrorMessage();
- /* handle the error */
- }
- /* The user is now authenticated. */
- /* If we did set the proxy callback url, we can get proxy tickets with this method call: */
- String urlOfTargetService = "http://hkg2.its.yale.edu/someApp/portalFeed";
- String proxyTicket = ProxyTicketReceptor.getProxyTicket( pv.getPgtIou() , urlOfTargetService);
- import edu.yale.its.tp.cas.client.*;
- ...
- String user = null;
- String errorCode = null;
- String errorMessage = null;
- String xmlResponse = null;
- List proxyList = null;
- /* instantiate a new ProxyTicketValidator */
- ProxyTicketValidator pv = new ProxyTicketValidator();
- /* set its parameters */
- pv.setCasValidateUrl("https://secure.its.yale.edu/cas/proxyValidate");
- pv.setService(urlOfThisService);
- pv.setServiceTicket(request.getParameter("ticket"));
- String urlOfProxyCallbackServlet = "https://portal.yale.edu/CasProxyServlet";
- pv.setProxyCallbackUrl(urlOfProxyCallbackServlet);
- /* contact CAS and validate */
- pv.validate();
- /* if we want to look at the raw response, we can use getResponse() */
- xmlResponse = pv.getResponse();
- /* read the response */
- if(pv.isAuthenticationSuccesful()) {
- user = pv.getUser();
- proxyList = pv.getProxyList();
- } else {
- errorCode = pv.getErrorCode();
- errorMessage = pv.getErrorMessage();
- /* handle the error */
- }
- /* The user is now authenticated. */
- /* If we did set the proxy callback url, we can get proxy tickets with this method call: */
- String urlOfTargetService = "http://hkg2.its.yale.edu/someApp/portalFeed";
- String proxyTicket = ProxyTicketReceptor.getProxyTicket( pv.getPgtIou() , urlOfTargetService);
import edu.yale.its.tp.cas.client.*;
...
String user = null;
String errorCode = null;
String errorMessage = null;
String xmlResponse = null;
List proxyList = null;
/* instantiate a new ProxyTicketValidator */
ProxyTicketValidator pv = new ProxyTicketValidator();
/* set its parameters */
pv.setCasValidateUrl("https://secure.its.yale.edu/cas/proxyValidate");
pv.setService(urlOfThisService);
pv.setServiceTicket(request.getParameter("ticket"));
String urlOfProxyCallbackServlet = "https://portal.yale.edu/CasProxyServlet";
pv.setProxyCallbackUrl(urlOfProxyCallbackServlet);
/* contact CAS and validate */
pv.validate();
/* if we want to look at the raw response, we can use getResponse() */
xmlResponse = pv.getResponse();
/* read the response */
if(pv.isAuthenticationSuccesful()) {
user = pv.getUser();
proxyList = pv.getProxyList();
} else {
errorCode = pv.getErrorCode();
errorMessage = pv.getErrorMessage();
/* handle the error */
}
/* The user is now authenticated. */
/* If we did set the proxy callback url, we can get proxy tickets with this method call: */
String urlOfTargetService = "http://hkg2.its.yale.edu/someApp/portalFeed";
String proxyTicket = ProxyTicketReceptor.getProxyTicket( pv.getPgtIou() , urlOfTargetService);
在这里,我们假设上下文环境中的用户已经通过了CAS登录认证,被重定向到当前的servlet下,我们在servlet中获取ticket凭证,servlet的URL对用户身份进行确认。如果上下文参数中无法获取ticket凭证,我们就认为用户尚未登录,那么,该servlet必须负责将用户重定向到CAS的登录页面去。
初战告捷
到今天为止,我们已经通过JA-SIG学习笔记的1-3部分,对CAS这个开源SSO的框架有了个大体的了解和初步的掌握,希望这些知识能为各位步入CAS殿堂打开一扇的大门。咖啡希望在今后的工作应用中,能同大家一块共同探讨,进一步深入了解CAS。