CAS(Central Authentication Service):关于 SSO 的一套解决方案,即统一身份认证服务或中央身份服务器,由服务端和客户端组成,容易进行企业应用的集成。
CAS 原理
- 架构
- CAS 最基本的协议过程
- 访问服务: SSO 客户端发送请求访问应用系统提供的服务资源。
- 定向认证: SSO 客户端会重定向用户请求到 SSO 服务器。
- 用户认证:用户身份认证。
- 发放票据: SSO 服务器会产生一个随机的 Service Ticket 。
- 验证票据: SSO 服务器验证票据 Service Ticket 的合法性,验证通过后,允许客户端访问服务。
- 传输用户信息: SSO 服务器验证票据通过后,传输用户认证结果信息给客户端。
CAS Server 部署
-
构建 war 包
-
cas-overlay-template
项目下载地址:https://github.com/apereo/cas-overlay-template
,选择需要的版本下载到本地,新版本需要自己构建 war 包。git clone -b 分支名 https://github.com/apereo/cas-overlay-template.git
-
项目初次构建完成后,按照 README 中提示执行命令,在 build/libs/ 目录下可找到 cas.war。
-
如果想根据需要自己配置,可执行
./gradlew[.bat] explodeWar
命令将 war 包解压,将解压得到的 cas-resources 文件夹复制到 src/main/目录下,即可对相关的文件进行修改,主要配置文件src/main/resources/application.properties
,修改完之后需要再次 build ,使得最新的配置生效。
-
-
cas 需要使用 https 访问,故需要生成证书和密钥,此处使用 Java 自带的数据证书管理工具 keytool
-
生成密钥(别名、密钥、证书名最好保持一致)
keytool -genkey -alias castest -keyalg RSA -keystore 对应路径/keystore/castest
-
导出证书(此处将证书颁发给 castest)
keytool -export -trustcacerts -alias castest -file 对应路径/keystore/castest.cer -keystore 对应路径//keystore/castest
-
将证书导入到 JDK 证书库(默认密码:changeit)
keytool -import -trustcacerts -alias castest -file 对应路径/keystore/castest.cer -keystore "jdk路径/jdk-11/lib/security/cacerts"
-
配置 tomcat https 访问,在 tomcat 安装目录下找到 conf/server.xml,加入 https 配置,将生成的密钥及生成密钥时设置的密码配置到文件中。
<Connector port="8443" protocol="org.apache.coyote.http11.Http11AprProtocol" maxThreads="150" SSLEnabled="true" scheme="https" secure="true" clientAuth="false" sslProtocol="TLS" keystoreFile="D:\CAS\keystore\castest" keystorePass="123456"> </Connector>
-
在
C:\Windows\System32\drivers\etc
目录下找到 hosts 文件,添加配置将证书颁发的域名指向 127.0.0.1127.0.0.1 castest
-
-
将构建好的 war 包放到 Tomact 的 webapps 目录下,启动 tomcat 会自动解压部署该 war 包。
- 若正常启动,访问
https://castest:8443/cas/login
即可访问到 CAS 默认的登录页面。 - 采用默认的静态认证方式,用户名
casuser
,密码Mellon
。
- 若正常启动,访问
-
我们实际开发一般不会采用静态认证方式,可以根据需要配置数据库认证,这里使用 MySQL 数据库。
-
在
build.gradle
中引入相关依赖,注意自己的 MySQL 版本dependencies { // Other CAS dependencies/modules may be listed here... compile "org.apereo.cas:cas-server-webapp-init:${casServerVersion}" compile "org.apereo.cas:cas-server-support-json-service-registry:${casServerVersion}" // 添加数据库认证相关的包 compile "org.apereo.cas:cas-server-support-jdbc:${casServerVersion}" compile "org.apereo.cas:cas-server-support-jdbc-drivers:${casServerVersion}" compile "mysql:mysql-connector-java:5.7.9" // compile "org.jasig.cas:${casServerVersion}" }
-
在
src/main/resources/application.properties
中将之前的静态认证配置注释掉,添加数据库认证配置。- CAS 默认的密码加密方式为 MD5,如果数据库中密码没有加密,则需要手动生成对应的 MD5 值,才能验证通过,不过我使用在线工具生成 MD5 加密密码校验一直无法通过,查阅资料大致原因应该是 CAS 使用的 MD5 算法与我们使用的不同,可以自己实现一下
PasswordEncoder
,配置使用自己的实现去校验密码(这里未实现)。 - 不配置则使用明文方式,此处则使用明文方式。
## # CAS Authentication Credentials # #cas.authn.accept.users=casuser::Mellon #cas.authn.accept.name=Static Credentials cas.jdbc.showSql=true cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQL5Dialect cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/hibernate_demo01?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 cas.authn.jdbc.query[0].user=root cas.authn.jdbc.query[0].password=root cas.authn.jdbc.query[0].sql=select user_passwd from d_user where user_name=? # 数据库中的密码字段名称 cas.authn.jdbc.query[0].fieldPassword=user_passwd # com.mysql.cj.jdbc.Driver: mysql-connector-java 6 比 5 版本多了关于时区的设置 cas.authn.jdbc.query[0].driverClass=com.mysql.cj.jdbc.Driver #cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT #cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8 #cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5
- CAS 默认的密码加密方式为 MD5,如果数据库中密码没有加密,则需要手动生成对应的 MD5 值,才能验证通过,不过我使用在线工具生成 MD5 加密密码校验一直无法通过,查阅资料大致原因应该是 CAS 使用的 MD5 算法与我们使用的不同,可以自己实现一下
到这里我们启动 tomcat,就可以通过数据库中的用户名、密码登录 CAS Server 了。
-
-
在
src/main/resources/services/HTTPSandIMAPS-10000001.json
中添加 http 访问配置
{
"@class": "org.apereo.cas.services.RegexRegisteredService",
"serviceId": "^(http|https|imaps)://.*",
"name": "HTTP、HTTPS and IMAPS",
"id": 10000001,
"description": "This service definition authorizes all application urls that support HTTP、HTTPS and IMAPS protocols.",
"evaluationOrder": 10000
}
-
在
src/main/resources/application.properties
中添加 http 访问配置# 设置 http 访问 # 服务注册的 json 配置文件路径 cas.serviceRegistry.json.location=classpath:/services # 设置安全为 false cas.tgc.secure=false # *******tomcat 自动部署 cas.war时无法正确解析该设置******* # 开启识别 json 文件,默认为 false #cas.serviceRegistry.initFromJson=true
CAS Client 部署
-
此处客户端基于 SpringMVC 搭建,可以在一个项目下构建两个 Module,便于后续测试单点登录、登出,分别将两个模块部署到tomcat上,设置不同的端口如
http://localhost:18081/
、http://localhost:18082/
,同时启动可正常访问即可。 -
在 pom.xml 中添加 cas client 依赖
<!-- CAS客户端相关依赖--> <dependency> <groupId>org.jasig.cas.client</groupId> <artifactId>cas-client-core</artifactId> <version>3.6.2</version> </dependency>
-
在
src/main/webapp/WEB-INF/web.xml
中配置相关过滤器- 注意此处 CAS 服务端和客户端的访问地址不能使用 localhost,否则会报错,我理解是从 CAS Server 服务器可能
<!-- 负责用户认证 ,必须启用 --> <filter> <filter-name>CASFilter</filter-name> <filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class> <!-- cas server 的服务端 IP --> <init-param> <param-name>casServerLoginUrl</param-name> <param-value>https://castest:8443/cas/login</param-value> </init-param> <!-- cas client 的客户端 IP --> <init-param> <param-name>serverName</param-name> <param-value>http://castest:18081</param-value> </init-param> </filter> <filter-mapping> <filter-name>CASFilter</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> <!-- cas server 的服务端 IP --> <init-param> <param-name>casServerUrlPrefix</param-name> <param-value>https://castest:8443/cas</param-value> </init-param> <!-- cas client 的客户端 IP --> <init-param> <param-name>serverName</param-name> <param-value>http://castest:18081</param-value> </init-param> </filter> <filter-mapping> <filter-name>CAS Validation Filter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
<!-- 该过滤器负责实现HttpServletRequest请求的包裹,如允许获取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> <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>
-
配置好之后,重新启动 client,访问
http://localhost:18081/
此时会直接重定向到 CAS 的登录认证页面,输入正确的用户名密码才能访问到启动页面,此时再访问http://localhost:18082/
则会直接访问到启动页面,无需再进行登录,这就是单点登录。 -
单点登出
- 在
src/main/webapp/WEB-INF/web.xml
中配置相关过滤器及监听器
<!-- 用于实现单点登出的监听器和过滤器 --> <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>
- 编写登出方法,重定向到 CAS Server 的 logout 方法,可以在重定向 url 后面加一个后缀 service,表示登出之后回到 service 指定的页面。
@RequestMapping(value = "logout", method = {RequestMethod.GET, RequestMethod.POST}) public String logout (HttpSession session) { session.invalidate(); return "redirect:https://castest:8443/cas/logout?service=https://castest:8443/cas/login"; }
- 在 index.jsp 可添加登出按钮,获取当前登录用户等信息。
<%@page contentType="text/html; charset=utf-8" language="java" %>> <html> <body> <div style="text-align: right"> <a href="/logout">登出</a> </div> <h2>Hello World!</h2> <%=request.getRemoteUser()%> </body> </html>
- 重新启动 client,访问
http://localhost:18081/
,输入正确的用户名密码访问到启动页面,再访问http://localhost:18082/
之后点击登出,再回到http://localhost:18081/
的启动页面刷新界面,此时页面会变为 login 页面,即在 18082 登出那么 18081 也会登出,即单点登出。
- 在
到这里 CAS Client 就部署完成了。