https://wiki.jasig.org/display/CASUM/Home
http://downloads.jasig.org/cas-clients/
CAS(Central Authentication Server)
SSO CAS 单点登录
目标:一个CAS服务,2个以上的独立项目
在CAS服务器上登录成功后,即可访问关联到该CAS服务上的子项目
在任意子项目上进行注销,再访问其它项目将需要重新登录
以下内容,基于cas-server-3.4.10 和 cas-client-3.2.1
====================================================================
第一步 制作密钥与证书
使用Java的keytool生成密钥,并将其存放到指定的密钥库中
Keytool将密钥(key)和证书(certificates)存在一个称为keystore的文件中
参数:
-alias 密钥的别名(唯一)
-keyalg 加密算法
-keystore 指定生成的keystore存放位置
-storepass 指定密钥库的口令
-dname 配置密钥发布者相关信息
CN 服务器域名,本地测试用localhost(或者修改host文件,127.0.0.1 sso.gc.com)
OU 组织单位名称
O 组织名称
L 城市或区域名称
ST 省/市/自治区名称
C 双字母国家/地区代码
-keypass 访问具体密钥的口令
-validity 密钥的有效期(默认90天)
【生成keystore】
keytool -genkey -alias gc -keyalg RSA -keystore e:/keys/mykeystore -storepass 111111 -dname "CN=sso.gc.com, OU=gc.com, O=gc.com, L=bj, ST=bj, C=cn" -keypass 111111 -validity 365
Keytool将密钥(key)和证书(certificates)存在一个称为keystore的文件中
参数:
-alias 密钥的别名(唯一)
-keyalg 加密算法
-keystore 指定生成的keystore存放位置
-storepass 指定密钥库的口令
-dname 配置密钥发布者相关信息
CN 服务器域名,本地测试用localhost(或者修改host文件,127.0.0.1 sso.gc.com)
OU 组织单位名称
O 组织名称
L 城市或区域名称
ST 省/市/自治区名称
C 双字母国家/地区代码
-keypass 访问具体密钥的口令
-validity 密钥的有效期(默认90天)
【生成keystore】
keytool -genkey -alias gc -keyalg RSA -keystore e:/keys/mykeystore -storepass 111111 -dname "CN=sso.gc.com, OU=gc.com, O=gc.com, L=bj, ST=bj, C=cn" -keypass 111111 -validity 365
注意:上面的命令中,CN指定的域名是通过host文件中配置的(127.0.0.1 sso.gc.com)
从keystore密钥库导出证书
从keystore密钥库导出证书
服务端(提供CAS服务,如Tomcat),在8443端口处配置keystore
客户端则需要将证书导入到JVM中
参数:
-alias 上一步所指定的密钥别名
-keystore 上一步指定的密钥库地址
-storepass 上一步指定的密钥库口令
-file 指定即将导出的证书的存放地址
【从keystore导出证书】
keytool -export -alias gc -keystore e:/keys/mykeystore -storepass 111111 -file e:/keys/gckey.crt
客户端拿到证书后,将证书导入到的JVM中
参数:
-alias 上一步所指定的密钥别名
-keystore 上一步指定的密钥库地址
-storepass 上一步指定的密钥库口令
-file 指定即将导出的证书的存放地址
【从keystore导出证书】
keytool -export -alias gc -keystore e:/keys/mykeystore -storepass 111111 -file e:/keys/gckey.crt
客户端拿到证书后,将证书导入到的JVM中
(客户端只需要JRE即可,所以客户端的证书库在JRE目录下!)
cacerts证书文件(The cacerts Certificates File)
该证书文件存在于java.home\jre\lib\security目录下,是Java系统的CA证书仓库
将上面生成的证书导入到客户端的JVM中
参数:
-trustcacerts 信任的证书
-alias 证书别名
-keystore 客户端keystore的地址
-storepass 客户端keystore口令
-file 被导入的证书的位置
【客户端:导入证书】
keytool -import -trustcacerts -alias gc -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit -file e:/keys/gckey.crt
cacerts证书文件(The cacerts Certificates File)
该证书文件存在于java.home\jre\lib\security目录下,是Java系统的CA证书仓库
将上面生成的证书导入到客户端的JVM中
参数:
-trustcacerts 信任的证书
-alias 证书别名
-keystore 客户端keystore的地址
-storepass 客户端keystore口令
-file 被导入的证书的位置
【客户端:导入证书】
keytool -import -trustcacerts -alias gc -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit -file e:/keys/gckey.crt
注意:-storepass 为changeit,该密码是java证书库的密码,不是前面设置的111111
查看证书库里面的证书信息
keytool -list -v -alias gc -keystore e:/keys/mykeystore -storepass 111111
查看客户端JVM中的证书信息
keytool -list -v -alias gc -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit
删除证书库里面的某个证书
keytool -delete -alias gc -keystore e:/keys/mykeystore -storepass 111111
删除客户端JVM中的证书信息
keytool -delete -alias gc -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit
查看证书库里面的证书信息
keytool -list -v -alias gc -keystore e:/keys/mykeystore -storepass 111111
查看客户端JVM中的证书信息
keytool -list -v -alias gc -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit
删除证书库里面的某个证书
keytool -delete -alias gc -keystore e:/keys/mykeystore -storepass 111111
删除客户端JVM中的证书信息
keytool -delete -alias gc -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit
====================================================================
第二步 配置CAS服务器
以Tomcat为例,放开8843端口,配置如下
<Connector port="8443" protocol="org.apache.coyote.http11.Http11Protocol" SSLEnabled="true" maxThreads="150" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" keystoreFile="e:/keys/mykeystore" keystorePass="111111"/>
并放开此配置
<Valve className="org.apache.catalina.authenticator.SingleSignOn" />
配置CAS中的cas-server-webapp模块
打开E:\learning\cas\cas\cas-server-3.4.10\cas-server-webapp\src\main\webapp\WEB-INF\deployerConfigContext.xml
替换认证方式,默认配置是用户名密码相同即认证通过
<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler" />
配置新的认证方案:根据登录账号查询数据库中存储的密码,进行用户验证
如果数据库中存储的密码通过加密存储的,如MD5加密,可通过配置加密器
修改为通过数据库查询用户名对应的密码进行认证 <bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler" property name="dataSource" ref="dataSource" property name="sql" value="select password from t_admin_user where login_name = ?" <!-- property name="passwordEncoder" ref="MD5PasswordEncoder" --> /> <!-- 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/gc</value></property> <property name="username"><value>root</value></property> <property name="password"><value>root</value></property> </bean> <!-- MD5加密后与数据库查询得到的密码进行比对 --> <bean id="MD5PasswordEncoder" class="org.jasig.cas.authentication.handler.DefaultPasswordEncoder"> <constructor-argindex="0"> <value>MD5</value> </constructor-arg> </bean>
由于需要访问数据库,所以需要向
cas-server-webapp项目中增加数据库驱动
通过Maven来导入需要的jar包
手动将cas-server-support-jdbc-3.4.10.jar发布到私服上
注意:-DrepositoryId配置的是maven中settings.xml中配置的仓库id,如果这里配错了,将无法deploy,发生401,ReasonPhrase: Unauthorized.
注意:-DrepositoryId配置的是maven中settings.xml中配置的仓库id,如果这里配错了,将无法deploy,发生401,ReasonPhrase: Unauthorized.
mvn deploy:deploy-file -DgroupId=org.jasig.cas -DartifactId=cas-server-support-jdbc -Dversion=3.4.10 -Dpackaging=jar -Dfile=E:\learning\cas\cas\cas-server-3.4.10\modules\cas-server-support-jdbc-3.4.10.jar -Durl=http://localhost:8081/nexus/content/repositories/releases/ -DrepositoryId=nexus-releases
为cas-server-webapp添加依赖,这样打好的war包中就包含了这2个jar,而不用每次都手动拷贝到WEB-INF/lib中
<!-- CAS服务访问数据库的接口 --> <dependency> <groupId>org.jasig.cas</groupId> <artifactId>cas-server-support-jdbc</artifactId> <version>3.4.10</version> </dependency> <!-- Mysql数据库连接器 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.28</version> </dependency>
以Mysql为例,创建数据库gc,新建t_admin_user表,插入一个用户
create table t_admin_user (
id bigint not null auto_increment,
email varchar(255),
login_name varchar(255) not null unique,
name varchar(255),
password varchar(255),
primary key (id)
) ENGINE=InnoDB;
insert into t_admin_user (email,login_name,name,password) values ('gc@gc.com','hqh','hqh','123');
使用Maven对cas-server-webapp进行打包
E:\learning\cas\cas\cas-server-3.4.10\cas-server-webapp>mvn package将war包拷贝到tomcat的webapps下
重启tomcat
访问:https://sso.gc.com:8443/cas/login
输入用户名和密码,CAS SERVER将从数据库中查询uasername对应的密码进行验证,如果一致,则认证通过
![](https://i-blog.csdnimg.cn/blog_migrate/6b47b8a776ec4095252764b787c027b5.jpeg)
点击继续浏览
![](https://i-blog.csdnimg.cn/blog_migrate/4863257308788dce5eed648de9bc8eb1.jpeg)
输入用户名和密码(数据库中存储的用户名hqh和密码123)
![](https://i-blog.csdnimg.cn/blog_migrate/6a2177d44ed48167cfcaf51ed3093a58.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/4863257308788dce5eed648de9bc8eb1.jpeg)
输入用户名和密码(数据库中存储的用户名hqh和密码123)
![](https://i-blog.csdnimg.cn/blog_migrate/6a2177d44ed48167cfcaf51ed3093a58.jpeg)
到此,服务端配置结束。
====================================================================
第三步 配置客户端
准备2个需要用户验证的项目,项目A,项目B
用户登录的基本做法:
使用filter对项目中所有内容进行过滤(除了访问LoginServlet)
在LoginServlet中,获取用户名和密码,如果验证成功,则将用户存入HttpSession中
否则,重定向到登录页面
现在通过CAS进行单点登录,项目A与项目B 就不需要进行登录验证了
验证都集中在CAS中进行,一旦在CAS验证通过,访问项目A与项目B将不需要登录了。
取消当前项目中对用户登录的验证,下面开始使用CAS客户端进行登录控制
在项目A与项目B中增加对cas-client-core的依赖
<dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>3.2.1</version> </dependency>
配置项目A(服务地址:http://localhost:8088/client)
打开项目A的web.xml
添加如下Filter
<!-- 单点登陆开始 --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> </filter> <filter> <filter-name>CAS Authentication Filter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>https://sso.gc.com:8443/cas/login</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://localhost:8088</param-value> </init-param> </filter> <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://sso.gc.com:8443/cas</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://localhost:8088</param-value> </init-param> <init-param> <param-name>redirectAfterValidation</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>tolerance</param-name> <param-value>5000</param-value> </init-param> </filter> <filter> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class> </filter> <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 Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Authentication Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Validation Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Assertion Thread Local Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 单点登陆结束 -->
配置项目B
(服务地址:http://localhost:8888/client)
打开项目A的web.xml
添加如下Filter
<!-- 单点登陆开始 --> <filter> <filter-name>CAS Single Sign Out Filter</filter-name> <filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class> </filter> <filter> <filter-name>CAS Authentication Filter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>https://sso.gc.com:8443/cas/login</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://localhost:8888</param-value> </init-param> </filter> <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://sso.gc.com:8443/cas</param-value> </init-param> <init-param> <param-name>serverName</param-name> <param-value>http://localhost:8888</param-value> </init-param> <init-param> <param-name>redirectAfterValidation</param-name> <param-value>true</param-value> </init-param> <init-param> <param-name>tolerance</param-name> <param-value>5000</param-value> </init-param> </filter> <filter> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class> </filter> <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 Single Sign Out Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Authentication Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Validation Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS HttpServletRequest Wrapper Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>CAS Assertion Thread Local Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <!-- 单点登陆结束 -->
重启项目A与项目B
均跳转到了CAS登录页面
![](https://i-blog.csdnimg.cn/blog_migrate/45b5374f0bfc2768b8ee149924938c65.jpeg)
选择一个页面进行登录,登录成功后,再访问另一个项目,将不需要登录了
![](https://i-blog.csdnimg.cn/blog_migrate/45b5374f0bfc2768b8ee149924938c65.jpeg)
选择一个页面进行登录,登录成功后,再访问另一个项目,将不需要登录了
如,访问
http://localhost:8088/client/main.jsp,在跳转到的页面上进行登录,成功后跳转到
![](https://i-blog.csdnimg.cn/blog_migrate/1419069c835bc3d3c6b7ad17ebf23cad.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/1419069c835bc3d3c6b7ad17ebf23cad.jpeg)
当项目A登录成功,再访问项目B 时
,不需要再登录了
![](https://i-blog.csdnimg.cn/blog_migrate/e6843b59a6dabf2a8d53a41b6b291bc9.jpeg)
![](https://i-blog.csdnimg.cn/blog_migrate/e6843b59a6dabf2a8d53a41b6b291bc9.jpeg)
注销功能
在任何一个项目注销后,CAS所关联的子项目都被注销
web.xml中加入LogoutServlet
<!-- 注销登录 --> <servlet> <servlet-name>logoutServlet</servlet-name> <servlet-class>com.gc.user.web.LogoutServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>logoutServlet</servlet-name> <url-pattern>/logoutServlet</url-pattern> </servlet-mapping>
增加一个LogoutServlet类
package com.gc.user.web;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LogoutServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doSingleSignOut(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
doPost(req, resp);
}
public void doSingleSignOut(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
response.sendRedirect("https://sso.gc.com:8443/cas/logout");
}
}
页面提供注销按钮或链接
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<div align="right">
<a href="/client/logoutServlet">注销</a>
</div>
<h1>8088端口,项目A的一个很重要的页面</h1>
</body>
</html>
CAS单点登录到此只能算走通了一个流程,学了一丁点的皮毛,我仍需努力啊!