SSO英文全称Single Sign On,单点登录。SSO是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。它包括可以将这次主要的登录映射到其他应用中用于同一个用户的登录的机制。它是目前比较流行的企业业务整合的解决方案之一。
SSO的实现机制是:
当用户第一次访问应用系统1的时候,因为还没有登录,会被引导到认证系统中进行登录;根据用户提供的登录信息,认证系统进行身份校验,如果通过校验,应该返回给用户一个认证的凭据--ticket;用户再访问别的应用的时候就会将这个ticket带上,作为自己认证的凭据,应用系统接受到请求之后会把ticket送到认证系统进行校验,检查ticket的合法性。如果通过校验,用户就可以在不用再次登录的情况下访问应用系统2和应用系统3了。
要实现SSO,需要以下主要的功能:
(1)系统共享
统一的认证系统是SSO的前提之一。认证系统的主要功能是将用户的登录信息和用户信息库相比较,对用户进行登录认证;认证成功后,认证系统应该生成统一的认证标志(ticket),返还给用户。另外,认证系统还应该对ticket进行校验,判断其有效性。
(2)信息识别
要实现SSO的功能,让用户只登录一次,就必须让应用系统能够识别已经登录过的用户。应用系统应该能对ticket进行识别和提取,通过与认证系统的通讯,能自动判断当前用户是否登录过,从而完成单点登录的功能。
单点登录在web项目中有着广泛运用。比如,用户在访问页面1的时候进行了登录,但是客户端的每个请求都是单独的连接,当客户再次访问页面2的时候,如何才能告诉Web服务器,客户刚才已经登录过了呢?浏览器和服务器之间有约定:通过使用cookie技术来维护应用的状态。Cookie是可以被Web服务器设置的字符串,并且可以保存在浏览器中。当浏览器访问了页面1时,web服务器设置了一个cookie,并将这个cookie和页面1一起返回给浏览器,浏览器接到cookie之后,就会保存起来,在它访问页面2的时候会把这个cookie也带上,Web服务器接到请求时也能读出cookie的值,根据cookie值的内容就可以判断和恢复一些用户的信息状态。Web-SSO完全可以利用Cookie技术来完成用户登录信息的保存,将浏览器中的Cookie和上文中的Ticket结合起来,完成SSO的功能。
为了完成一个简单的SSO的功能,需要两个部分的合作:
(1)统一的身份认证服务。
(2)修改Web应用,使得每个应用都通过这个统一的认证服务来进行身份校验。
目前,实现SSO的技术主要有:
(1)基于cookies实现。
(2) Broker-based(基于经纪人)。这种技术的特点是:有一个集中的认证和用户帐号管理的服务器,为认证提供一个公共和独立的"第三方"。Kerberos是由麻省理工大学发明的安全认证服务,当前版本V5,已经被UNIX和Windows作为默认的安全认证服务集成进操作系统。
(3) Agent-based(基于代理人)。在这种解决方案中,有一个自动地为不同的应用程序认证用户身份的代理程序。这个代理程序需要设计有不同的功能。比如,它可以使用口令表或加密密钥来自动地将认证的负担从用户移开。代理人被放在服务器上面,在服务器的认证系统和客户端认证方法之间充当一个"翻译"。例如SSH等。
SSH,全称Secure Shell,安全外壳协议,是建立在应用层基础上的安全协议。SSH 是目前较可靠,专为远程登录会话和其他网络服务提供安全性的协议。利用 SSH 协议可以有效防止远程管理过程中的信息泄露问题。
(4) Token-based。例如SecurID,WebID,现在被广泛使用的口令认证,比如FTP,邮件服务器的登录认证,这是一种简单易用的方式,实现一个口令在多种应用当中使用。
(5) 基于网关Agent and Broker-based。
(6) 基于安全断言标记语言(SAML)实现。SAML(Security Assertion Markup Language,安全断言标记语言)的出现大大简化了SSO,并被OASIS批准为SSO的执行标准。开源组织OpenSAML 实现了 SAML 规范。
(7)CAS由耶鲁大学开发的单点登录系统(SSO,single sign-on),应用广泛,具有独立于平台,易于理解,支持代理的功能。
接下来,我们先实现CAS的初步搭建,然后实现多个应用系统基于CAS进行单点登录。
一.初步搭建CAS
1.把CAS的server发布到一台独立的tomcat上。
从网上下载cas-server-4.0.0-release.zip,解压后,拷贝出cas-server-4.0.0modulescas-server-webapp-4.0.0.war,为了简化起见,将cas-server-webapp-4.0.0.war更名为cas-server.war,然后将这个war文件拷贝到到一个tomcat的webapps文件夹下。tomcat启动时,会自动解压war文件。
2.配置https。
由于CAS单点登录为了保障安全,因此必须基于https来搭建。
(1)鉴于https的要求,我们以win7系统为例,需要在C:WindowsSystem32driversetchosts中配置几个域名,如下:
127.0.0.1 dddr.money.com 127.0.0.1 dddr.store.com 127.0.0.1 dddr.cas.com
(2)生成ssl需要的证书。
①生成keystore: keytool -genkey -alias dddritszt -keyalg RSA -keysize 1024 -keypass 123456 -validity 365 -keystore C:UsersAdministratorDesktopcasssldddritszt.keystore -storepass 123456 ②转换keystore: keytool -export -alias dddritszt -keystore C:UsersAdministratorDesktopcasssldddritszt.keystore -file C:UsersAdministratorDesktopcasssldddritszt.crt -storepass 123456 ③信任证书【注意:JDK的java_home的路径中有中文或者空格都不行,比如我设置的java_home值为:E:Javajdk1.8.0_131】: keytool -import -keystore %JAVA_HOME%jrelibsecuritycacerts -file C:UsersAdministratorDesktopcasssldddritszt.crt -alias dddritszt 这一步按回车键后,会提示输入密码,请输入:changeit 由于要将证书导入jdk,因此输入的是jdk默认的秘钥。
(3)给tomcat配置https。在tomcat的conf/server.xml文件中添加下述配置:
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" keystoreFile="C:UsersAdministratorDesktopcasssldddritszt.keystore" keystorePass="123456" clientAuth="false" sslProtocol="TLS" URIEncoding="UTF-8"/>
到此就搭建好了CAS,接着开启tomcat服务器,在浏览器的地址栏中输入 https://dddr.cas.com:8443/cas-server/login,显示页面如下:
我在网上看到,有人说为了找出用户名和密码应该填什么,折腾了4个小时,在这里提醒大家,CAS默认,用户名是:casuser,密码是:Mellon。登录成功后,页面显示如下:
我们还可以通过CAS登出,在浏览器地址栏中修改url路径为 https://dddr.cas.com:8443/cas-server/logout,显示页面如下:
在演示完毕搭建CAS服务后,我们接下来进行多个应用系统在CAS上实现单点登录。
二.多个应用系统在CAS上实现单点登录。
1.再部署两个tomcat,http访问端口分别为17070和18080,这两个tomcat上将发布我们的测试案例。
(1)在idea中创建一个webApp项目,名称为“DDDR_CAS_Money”,在该项目的web/WEB-INF下新建一个lib文件夹,拷贝CAS客户端的一个jar包(此处为cas-client-core-3.3.3.jar)到该lib文件夹下,并将这个jar包配置为项目依赖。
注意,该项目的SDK(Software Development Kit,软件开发工具包),此处主要是指Java,要和添加证书信赖的Java路径一致。
另外,本项目所在的tomcat的conf/server.xml文件中,端口号不要和CAS服务器上的端口号冲突。我是这样配置的,亲测后,可以正常访问:
<Server port="8085" shutdown="SHUTDOWN"> ... <Connector port="18080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="18443" /> ... <Connector port="8009" protocol="AJP/1.3" redirectPort="18443" /> </Server>
该项目下的web.xml文件配置如下:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <!-- ======================== 单点登录开始 ======================== --> <!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置--> <listener> <listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class> </listener> <!-- 该过滤器用于实现单点登出功能,可选配置。 --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter> <filter-name>CAS Filter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>https://dddr.cas.com:8443/cas-server/login</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://dddr.money.com:18080</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责对Ticket的校验工作,必须启用它 --> <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://dddr.cas.com:8443/cas-server</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://dddr.money.com:18080</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Validation Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器负责实现HttpServletRequest请求的包裹, 比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。 --> <filter> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <filter-class> org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。 比如AssertionHolder.getAssertion().getPrincipal().getName()。 --> <filter> <filter-name>CAS Assertion Thread Local Filter</filter-name> <filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class> </filter> <filter-mapping> <filter-name>CAS Assertion Thread Local Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- ======================== 单点登录结束 ======================== --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>
该项目的index.jsp文件:
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>cas-money</title> </head> <body> 我是cas-money... </body> </html>
接着,在确保CAS服务器开启的情况下,在浏览器地址栏里输入 http://dddr.money.com:18080/index.jsp,会跳转到 https://dddr.cas.com:8443/cas-server/login登录页面,用户名和密码分别输入(用户名:casuser,密码:Mellon),登录成功后会跳转到http://dddr.money.com:18080/index.jsp的页面。
(2)仿照上面步骤,我们再建一个web案例,名称为DDDR_CAS_store。该项目下的web.xml也仿照上面的案例,注意ur要改为自己的。index.jsp为:
<%@ page import="org.jasig.cas.client.validation.Assertion" %> <%@ page import="org.jasig.cas.client.util.AssertionHolder" %> <%@ page import="java.security.Principal" %> <%@ page import="org.jasig.cas.client.util.AbstractCasFilter" %> <%@ page import="java.util.Map" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> </head> <body> 我是DDDR_CAS_store.... <hr> <dl> <dt>Your user name:</dt> <dd>1、通过getRemoteUser来获取信息: <%= request.getRemoteUser() == null ? "null" : request.getRemoteUser() %> </dd> <br> <dd>2、通过getUserPrincipal来获取信息: <%= request.getRemoteUser() == null ? "null" : request.getUserPrincipal() %> </dd> <br> <dd>3、通过Assertion来获取信息:<% Assertion ass = AssertionHolder.getAssertion(); Principal p = ass.getPrincipal(); String name = p.getName(); out.print(name); %></dd> <br> <dd>4、从session中获取Assertion对象,建议使用AssertionHolder: <% ass = (Assertion) session.getAttribute(AbstractCasFilter.CONST_CAS_ASSERTION); String nm = ass.getPrincipal().getName(); out.print(nm); %></dd> <br> <dd> <% Map m = ass.getPrincipal().getAttributes(); out.print(m.size()); %> </dd> </dl> </body> </html>
由于前面的应用DDDR_CAS_Money已经在CAS上登录,因此,在浏览器地址栏里输入 http://dddr.store.com:17070/index.jsp,会直接呈现下述页面:
现在,我们已经实现了多个应用通过CAS实现单点登录。接下来,我们定制化CAS页面,进行登录页与登录方式的自定义,使得页面呈现的效果更符合我们想要的效果。
三.定制化CAS页面风格
1.新建一个maven的web项目,名称为MyCAS_Server,将原先cas-server下的文件都拷贝到该项目的webapp文件夹下。
2.修改登录和登出页面的风格,然后在maven中执行package打包命令,将打出的war包替换CAS所在tomcat服务器下的cas-server即可。注意:要确保CAS的war包名称一致。
四.CAS数据库验证
我们上面进行的CAS验证,都是基于CAS自带的用户名和密码进行验证的,如果要对接我们的数据库信息,首先将cas-server-4.0.0-releasecas-server-4.0.0modulescas-server-support-jdbc-4.0.0.jar以及对于数据库的jar包加入到cas_server的lib目录下。接着,修改WebRootWEB-INFdeployerConfigContext.xm文件,将下面代码注释掉:
<bean id="primaryAuthenticationHandler" class="org.jasig.cas.authentication.AcceptUsersAuthenticationHandler"> <property name="users"> <map> <entry key="casuser" value="Mellon"/> </map> </property> </bean>
根据自己的数据库配置信息,添加如下代码,更新为JDBC验证方式:
<!-- 更新为JDBC验证方式 开始 --> <!-- 配置数据源 --> <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName"><value>com.mysql.jdbc.Driver</value></property> <property name="url"><value>jdbc:mysql://localhost:3306/itszt4?characterEncoding=utf8</value></property> <property name="username"><value>root</value></property> <property name="password"><value>2018</value></property> </bean> <bean id="primaryAuthenticationHandler" class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler"> <property name="dataSource" ref="dataSource"></property> <property name="sql" value="select userpwd from user where username=?"></property> <!-- 如果需要加密,则配置加密器 ,我这里不需要--> <!-- <property name="passwordEncoder" ref="MD5PasswordEncoder"></property> --> </bean> <!-- 添加MD5密码加密功能 --> <bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"> <constructor-arg index="0"> <value>MD5</value> </constructor-arg> </bean> <!-- 更新为JDBC验证方式 结束 -->
接下来再进行CAS验证,就改为匹配数据库表中的数据了。