一. SSO (Single Sign-on)原理
SSO 分为Web-SSO和桌面SSO。桌面 SSO 体现在操作系统级别上。Web-SSO体现在客户端,主要特点是: SSO 应用之间使用 Web 协议 ( 如 HTTPS) ,并且只有一个登录入口。我们所讲的SSO,指 Web SSO 。
SSO 的体系中,有下面三种角色:
User(多个)
Web应用(多个)
SSO认证中心(一个)
SSO 实现模式千奇百怪,但万变不离其宗,包含以下三个原则:
所有的登录都在 SSO 认证中心进行。
SSO 认证中心通过一些方法来告诉 Web 应用当前访问用户究竟是不是通过认证的用户。
SSO 认证中心和所有的 Web 应用建立一种信任关系。
二. CAS 的基本原理
CAS(Central Authentication Service) 是 Yale 大学发起的构建 Web SSO 的 Java开源项目。
1. CAS 的结构体系
CAS Server
CAS Server 负责完成对用户信息的认证,需要单独部署,CAS Server 会处理用户名 / 密码等凭证 (Credentials) 。
CAS Client
CAS Client部署在客户端,当有对本地 Web 应用受保护资源的访问请求,并且需要对请求方进行身份认证,重定向到 CAS Server 进行认证。
2. CAS 协议
基础协议
上图是一个基础的 CAS 协议, CAS Client 以 过滤器的方式保护 Web 应用的受保护资源,过滤从客户端过来的每一个 Web 请求,同时, CAS Client 会分析 HTTP 请求中是否包请求 Service Ticket( 上图中的 Ticket) ,如果没有,则说明该用户是没有经过认证的, CAS Client 会重定向用户请求到 CAS Server ( Step 2 )。 Step 3 是用户认证过程,如果用户提供了正确的认证信息 , CAS Server 会产生一个随机的 Service Ticket ,会向 User 发送一个 Ticket granting cookie (TGC) 给 User 的浏览器,并且重定向用户到 CAS Client (附带刚才产生的 Service Ticket),Step 5 和 Step6 是 CAS Client 和 CAS Server 之间完成了一个对用户的身份核实,用 Ticket 查到 Username ,认证通过。
3. CAS 如何实现 SSO
当用户访问Helloservice2再次被重定向到 CAS Server 的时候, CAS Server 会主动获到这个 TGC cookie ,然后做下面的事情:
1) 如果 User 的持有 TGC 且其还没失效,那么就走基础协议图的 Step4 ,达到了 SSO 的效果。
2) 如果 TGC 失效,那么用户还是要重新认证 ( 走基础协议图的 Step3) 。
三. 实践配置
下面我们以tomcat 5.5 为例进行说明(这里,我将Server和Client同时放在了同一个Tomcat服务器下)。
软件环境:tomcat 5.5 ant-1.6.5, jdk1.5.0_06
下载cas-server-3.0.4.zip和cas-client和cas-server-jdbc-3.0.5-rc2.jar和mysql 5.0.16和tomcat 5.5.15
http://www.ja-sig.org/downloads/cas/cas-server-3.0.4.zip http://www.yale.edu/tp/cas/cas-client-2.0.11.zip
http://developer.ja-sig.org/maven/cas/jars/cas-server-jdbc-3.0.5-rc2.jar
http://www.mysql.com
http://tomcat.apache.org/
(一) 将一个或者一些页面进行支持HTTPS传输协议(意义:对某些页面进行了安全传输)(重点掌握)
1. 产生SERVER的证书库文件
keytool -genkey -alias tomcat -keyalg RSA [-keystore keystore-file]
并将证书文件放在web容器的目录下。
2. (在server端)配置tomcat使用HTTPS
$CATALINA_HOME/conf/server.xml里
<Connector port="8443" maxHttpHeaderSize="8192"
keystorePass="changeit" keystoreFile="/conf/.keystore"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" />
注意:keystorePass="changeit"(这个问证书库文件的密码,也就是上面配置产生的一个密码) keystoreFile="/.keystore"(这是证书库文件的存放路径,其中根目录"/"为tomcat的安装路径)
3. 在WEB-INF/web.xml文件中增加
<security-constraint>
<web-resource-collection >
<web-resource-name >SSL</web-resource-name> <!--名字随便取-->
<url-pattern>/jsp2/el/*</url-pattern>
</web-resource-collection>
<user-data-constraint>
<transport-guarantee>CONFIDENTIAL</transport-guarantee>
</user-data-constraint>
</security-constraint>
解释:transport-guarantee元素指定了客户端和服务端的通信关系,有NONE,INTEGRAL,CONFIDENTIAL。NONE表示着应用不需要任何传输保障。INTEGRAL表示着在数据在客户端到服务端的过程中不能有任何改变。CONFIDENTIAL表示在传输过程中防止其他传输内容的干扰。在使用SSl时常用的就INTEGRAL或CONFIDENTIL。
4. 进行访问测试
(二) 实现CAS系统
1. 产生SERVER的证书库文件
keytool -genkey -alias tomcat -keyalg RSA [-keystore keystore-file]
并将证书文件放在web容器的目录下。
2. (在server端)配置tomcat使用HTTPS
$CATALINA_HOME/conf/server.xml里
<Connector port="8443" maxHttpHeaderSize="8192"
keystorePass="changeit" keystoreFile="/.keystore"
maxThreads="150" minSpareThreads="25" maxSpareThreads="75"
enableLookups="false" disableUploadTimeout="true"
acceptCount="100" scheme="https" secure="true"
clientAuth="false" sslProtocol="TLS" />
注意:keystorePass="changeit"(这个问证书库文件的密码,也就是上面配置产生的一个密码) keystoreFile="/.keystore"(这是证书库文件的存放路径,其中根目录"/"为tomcat的安装路径)
3. 将cas-server-3.0.4.zip解压,并将target/cas.war拷贝到webapps下。
4. 将cas-client-2.0.11.zip解压,把cas-client-2.0.11/java/lib/casclient.jar拷贝到client服务器上(这里为同一tomcat)的
webapps/servlets-examples/WEB-INF/lib目录下(如果没有就建一个)
5. 在要使用CAS的客户端应用里设置(以servlets-examples这个APP为例,在应用时,所有客户端均进行类似配置),我们使用ServletFilter(CAS client里提供的)来实现SSO的检查。
修改servlets-examples/WEB-INF/web.xml
<filter>
<filter-name>CASFilter</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://your.cas.server.name:port<!--这里是CAS server的loginURL-->/cas/login</param-value>
</init-param>
<init-param><param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
<param-value>https://your.cas.server.name:port<!--这里是CAS server的URL验证器--> /cas/proxyValidate</param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
<param-value>your.client.server.name:port <!--client:port就是需要CAS需要拦截的地址和端口,一般就是这个TOMCAT所启动的IP(默认为localhost)和port(默认8080)--></param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/servlet/*</url-pattern>
</filter-mapping>
配置好的例子:
<filter>
<filter-name>CASFilter</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://localhost:8443/cas/login</param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.validateUrl</param-name>
<param-value>https://localhost:8443/cas/proxyValidate</param-value>
</init-param>
<init-param>
<param-name>edu.yale.its.tp.cas.client.filter.serverName</param-name>
<param-value>localhost:8080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
6. .导出SERVER端的的证书文件(证书文件只包含公钥)
keytool -export -file myserver.crt -alias my-alias-name -keystore keystore-file
// keytool -export -file myserver.crt -alias tomcat -keystore .keystore
7. 在客户端的JVM里的证书库cacerts中导入信任的SERVER的证书(根据情况有可能需要管理员权限)
keytool -import -keystore cacerts -file myserver.crt -alias hostname(别名)
然后将cacerts 复制到%JAVA_HOME%/jre/lib/security/目录下
//keytool -import -keystore cacerts -file myserver.crt -alias tomcat
8. 测试.
把server和client分别起来(这里为同一个Tomcat,实际应用时可以在多个服务器上,且client可以为多个应用),检查启动的LOG是否正常,如果一切OK,就访问
http://localhost:8080/servlets-examples/servlet/HelloWorldExample
系统会自动跳转到一个验证页面,随便输入一个相同的账号,密码,验正通过之后就会访问
到真正的HelloWorldExample这个servlet了
四. 结合实际的环境的扩展
1 多个web应用如何实现单点登陆
(!--大家思考一下:如果我想在配置一个客户端,需要什么步骤?)
下面以jsp-examples为例子,进行下面得阐述:
(1)在/webapps/jsp-examples/WEB-INF/web.xml文件中进行配置:
<filter>
<filter-name>CASFilter</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://localhost:8443/cas/login</param-value>
</init-param>
<init-param>
<param-name>
edu.yale.its.tp.cas.client.filter.validateUrl
</param-name>
<param-value>
https://localhost:8443/cas/proxyValidate
</param-value>
</init-param>
<init-param>
<param-name>
edu.yale.its.tp.cas.client.filter.serverName
</param-name>
<param-value>localhost:8080</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/jsp2/simpletag/*</url-pattern>
</filter-mapping>
(2)在webapps/jsp-examples/WEB-INF/lib增加引用得jar包,casclient.jar。
2 认证业务方法的扩展:
2.1.1 配置CAS使用数据库进行验证
在MySql中的Test库中新建user表
Create TABLE `app_user` (
`username` varchar(30) NOT NULL default '',
`password` varchar(45) NOT NULL default '',
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
添加以下用户:
Insert INTO `app_user` (`username`,`password`) VALUES
('test',' test '),
(' test 1',' test 1');
2.1.2 修改cas项目中的deployerConfigContext.xml文件
<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />
注释掉该行,在其下加入:
<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="sql" value="select password from app_user where username=?" />
<property name="dataSource" ref="dataSource" />
</bean>
并添加一个bean:
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close">
<property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property>
<property name="url"><value>jdbc:mysql://localhost:3306/test</value></property>
<property name="username"><value>root</value></property>
<property name="password"><value>root</value></property>
</bean>
2.1.3 拷贝cas-server-jdbc-3.0.6.jar和mysql-connector-java-3.1.11-bin.jar到webapps/cas/WEB-INF/lib下。
3 如何在这取得用户名称
<%@page contentType="text/html;charset=GBK"%>
<%
String username=(String)session.getAttribute("edu.yale.its.tp.cas.client.filter.user");
%>
<p>当前得登陆用户:<%=username%></p>
<%
username = (String)session.getAttribute("edu.yale.its.tp.cas.client.filter.user");
%>
4 登陆页面的扩展:
(1) 现在CAS系统中存在的两套登陆页面{project.home}/webapp/WEB-INF/view/jsp/default/ui/和{project.home}/webapp/WEB-INF/view/jsp/simple/ui
(2) {project.home}/webapp/WEB-INF/cas-servlet.xml
<bean
id="viewResolver"
class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property
name="basename"
value="simple_views" />
<property
name="order"
value="0" />
</bean>
这个bean中basename属性决定由哪个属性文件加载,simple_views. properties还是default_views. Properties
而属性文件:
{project.home}/webapp/WEB-INF/classes/default_views.properties
{project.home}/webapp/WEB-INF/classes/simple_views.properties
### Login view (/login)
casLoginView.(class)=org.springframework.web.servlet.view.JstlView
casLoginView.url=/WEB-INF/view/jsp/default/ui/casLoginView.jsp
(登陆页面)
### Login confirmation view (logged in, warn=true)
casLoginConfirmView.(class)=org.springframework.web.servlet.view.JstlView
casLoginConfirmView.url=/WEB-INF/view/jsp/default/ui/casConfirmView.jsp
(当选择"警告"按钮时,系统显示的页面)
### Logged-in view (logged in, no service provided)
casLoginGenericSuccessView.(class)=org.springframework.web.servlet.view.JstlView
casLoginGenericSuccessView.url=/WEB-INF/view/jsp/default/ui/casGenericSuccess.jsp(成功登陆页面)
每个属性文件决定具体加载页面的名称。
五. CAS 安全性
TGC/PGT 安全性
TGC 也有自己的存活周期。下面是 CAS 的 applicationContext.xml 中,通过 TimeoutExpirationPolicy来设置 CAS TGC 存活周期的参数,参数默认是 120 分钟,在合适的范围内设置最小值,太短,会影响 SSO 体验,太长,会增加安全性风险。
<bean
id="grantingTicketExpirationPolicy" class="org.jasig.cas.ticket.support.TimeoutExpirationPolicy">
<!-- This argument is the time a ticket can exist before its considered expired. -->
<constructor-arg
index="0"
value="7200000" />//单位为:毫秒
</bean>
Service Ticket/Proxy Ticket 安全性
设用户拿到 Service Ticket 之后,他请求 helloservice 的过程又被中断了, Service Ticket 就被空置了,事实上,此时, Service Ticket 仍然有效。 CAS 规定 Service Ticket 只能存活一定的时间,然后 CAS Server 会让它失效。通过在 applicationContext.xml 中配置下面的参数,可以让 Service Ticket 在访问多少次或者多少秒内失效。
<!-- Expiration policies -->
<bean
id="serviceTicketExpirationPolicy"
class="org.jasig.cas.ticket.support.MultiTimeUseOrTimeoutExpirationPolicy">
<!-- This argument is the number of times that a ticket can be used before its considered expired. -->
<constructor-arg
index="0"
value="1" />
<!-- This argument is the time a ticket can exist before its considered expired. -->
<constructor-arg
index="1"
value="300000" />//单位:毫秒
</bean>
该参数在业务应用的条件范围内,越小越安全。