- CAS简介
- CAS协议
- First Access(首次访问web服务)
- Second Access To Same Application(后续访问同一个登录过的web服务)
- First Access To Second Application(首次访问其他的web服务)
- 两种session
- 快速启动CAS server
- SpringSecurity集成CAS
- 后续...
CAS(Central Authentication Service)是一个
- 开源的
- 企业级的
- 支持多语言(Java, .Net, Python, PHP...)的
- SSO(single sign-on, 单点登录)
- authentication(认证)
- authorization(授权)
解决方案,
- 适用于web平台,
- 其核心是由Java和Spring实现,
- CAS支持多种协议: CAS, SAML v1, SAML v2, WS-Federation,OAuth2, OpenID, OpenID Connect, REST,
- 支持插件式(Pluggable)的认证:LDAP, Database, X.509, SPNEGO, JAAS, JWT, RADIUS, MongoDb, …,
- 支持MFA(multifactor authentication)、代理认证(delegated authentication)、实时监控、统一日志管理与输出…
CAS系统包含两个核心组件,即CAS server和CAS client,
且CAS server和CAS clients是一对多的关系,即一个CAS server可以对应多个CAS client。
CAS Server即认证的服务端,负责认证用户,授予用户访问权(即向CAS client颁发tickets),
CAS clients即认证的客户端,需要用户认证的具体web服务,
即指代应用(任何支持CAS协议的应用 或者 集成client软件包的应用,亦称为CAS applications, CAS services),
又指代软件包(即特定编程语言支持的软件包,通过认证协议如CAS, SAML, OAuth与CAS Server进行交互),
通常没有特殊说明,CAS client即指的软件包,
例如: Java CAS Client, .NET CAS Client,Pycas, …
关于CAS server和client的具体职责及交互,可参考CAS 3.0.3协议时序图,
整个时序图即描绘了一个完整的SSO过程。
注:
Protected App:受保护的应用,即集成CAS client软件包的web服务,需要用户认证通过后,携带用户凭证才可访问该应用。
关于CAS协议时序图的具体说明如下:
First Access(首次访问web服务)(1)用户通过浏览器访问web服务(Protected App);
(2)web服务判断用户是否登录,若未登录(没有session cookie或者session不存在,此处的session为web服务对应的session),则重定向到CAS Server登录界面且携带query参数service(需要URL encoded),该service参数对应CAS Server认证成功后重定向回web服务的url;
302 Location: https://cas.example.com/cas/login?service=https://app.example.com
(3)浏览器重定向到CAS Server登录界面,即请求CAS Server,此时CAS Server检测该请求是否包含SSO Session(CASTGC),若不存在TGC,则返回具体的登录表单form;
TGC(Ticket Granting cookie):CAS server用于存储用户session id的cookie,实际对应CASTGC
TGT(Ticket Granting Ticket):代表CAS用户session id,存储在浏览器的TGC cookie中
ST (Service Ticket):在CAS Server登录成功后,通过url(即service参数对应的url)中的GET参数ticket传递给具体CAS client,后续CAS client需验证该ST并获取到对应的用户信息
(4)用户在CAS登录表单填写正确的用户名密码后,点击提交按钮后将登录请求发送到CAS Server,CAS Server验证用户信息无误后,生成当前用户session并写入CASTGC到浏览器cookie中,并重定向请求回web服务,且重定向url中包含ticket参数
Set-cookie: CASTGC=TGT-2345678 302 Location: https://app.example.com/?ticket=ST-12345678
(5)浏览器重定向到web服务,即请求web服务(Protected App),web服务需要请求CAS Server来验证url参数ticket(即ST),CAS Server返回一个XML响应体,该XML中包含是否验证成功、被认证的用户信息、可选属性。
# 浏览器重定向到web服务 https://app.example.com/?ticket=ST-12345678 # web服务调用CAS server验证ticket https://cas.example.com/servicevalidate?service=https://app.example.com&ticket=ST-12345678
(6)验证通过后,web服务可根据CAS验证结果生成当前web服务自身的用户session及cookie,并使浏览器重定向到web服务的具体path(省去ticket参数,避免长时间暴露在浏览器地址栏中)
Set-cookie: JSESSIONID=ABC1234567 302 Location: https://app.example.comSecond Access To Same Application(后续访问同一个登录过的web服务)
(7)之后再向web服务在发送请求时,即会携带自身的cookie,web服务后端仅需验证session cookie的合法性即可,验证通过后则返回具体请求内容。
cookie: JSESSIONID=ABC1234567 GET https://app.example.com/First Access To Second Application(首次访问其他的web服务)
(8)用户通过浏览器访问另一个web服务2(Protected App # 2);
(9)web服务2判断用户是否登录,若未登录(没有session cookie或者session不存在,此处的session为web服务2对应的session),则重定向到CAS Server登录界面且携带query参数service(需要URL encoded),该service参数对应CAS Server认证成功后重定向回web服务2的url;
302 Location: https://cas.example.com/cas/login?service=https://app2.example.com
注: 其中(8)(9)同之前的(1)(2)
(10)浏览器重定向到CAS Server登录界面,即请求CAS Server,由于之前已在浏览器中登录过CAS Server,所以浏览器中CAS Server域名下已经有CASTGC cookie,故再次向CAS Server发起请求时便会携带该CASTGC cookie,此时CAS Server检测该请求发现已经包含SSO Session(CASTGC),即此时已为登录态,则直接重定向到web服务2且重定向url中包含ticket参数;
# 请求CAS server登录界面 cookie: CASTGC=TGT-2345678 GET https://cas.example.com/cas/login?service=https://app2.example.com # 重定向到web服务2 Set-cookie: CASTGC=TGT-2345678 302 Location: https://app2.example.com/?ticket=ST-3455678
(11)浏览器重定向到web服务2,即请求web服务2(Protected App #2),之后重复第(5)(6)(7)步,
即web服务2调用CAS server验证ticket,web服务2生成自身的用户session及cookie、重定向回web服务2,发送到web服务2的请求携带自身的cookie。
在整个过程中出现两种session:
(1)CAS Server的用户session,对应CAS server域名下的TGC,该TGC仅对CAS server可见,用来标记用户是否已在CAS server端进行过登录,即全局的登录状态,用于SSO,即由CAS server来统一管理用户登录状态(用户认证),在CAS server中登录成功,则接入该CAS server的CAS Clients均为登录成功,无需用户再次输入账户密码,直接跳转到登录回调URL(对应service参数);
(2)web服务的用户session,对应各自web服务域名下的session cookie,仅对各自的web服务可见,用来标记用户已在web服务中分别登录过,各自集成CAS client的web服务在ST验证通过后需要生成各自的session,即web服务需要维护各自的登录态session;
注: 示例中的cas 6.4.x需要依赖JDK 11,所以需要先安装openjdk11并且设置相关环境变量
(1)下载CAS Initializer并解压缩
https://casinit.herokuapp.com/starter.tgz?type=cas-overlay&baseDir=cas-overlay
注: 详细的可参考:cas/6.4.x - WAR Overlay Initializr
查看README.md内容,并按照内容提示在cas-overlay根目录下依次执行以下命令
(2)构建cas.war
# 构建cas.war(war包输出在build/libs/cas.war) # Use --refresh-dependencies to force-update SNAPSHOT versions gradlew clean build
(3)创建keystore(即https证书)
# 创建keystore, # 如下命令会生成该cas-server对应的https证书,生成的证书存储在根目录/etc/cas下 gradlew createKeystore
注:
在windows环境环境执行执行以上命令会报错
即在windows环境无法定位到根目录/,此处需修改gradle.properties中certDir属性:
... # certDir由原来的/etc/cas修改为D:/etc/cas # 在windows系统下根目录即对应盘符根目录,比如cas-overlay在D盘下,则根目录则为D:/,即D:/etc/cas certDir=D:/etc/cas serverKeystore=thekeystore exportedServerCert=cas.crt storeType=PKCS12 ...
查看build.gradle文件中
task createKeystore(group: "CAS", description: "Create CAS keystore") { ... }
发现该createKeystore命令其实对应为如下命令:
# Generating keystore for CAS with DN "CN=cas.example.org,OU=Example,OU=Org,C=US" keytool -genkeypair -alias cas ^ -keyalg RSA ^ -keypass changeit ^ -storepass changeit ^ -keystore D:/etc/cas/thekeystore ^ -dname CN=cas.example.org,OU=Example,OU=Org,C=US ^ -ext SAN=dns:example.org,dns:localhost,ip:127.0.0.1 ^ -storetype PKCS12 # Exporting cert from keystore... keytool -exportcert ^ -alias cas ^ -storepass changeit ^ -keystore D:/etc/cas/thekeystore ^ -file D:/etc/cas/cas.crt
同时为了后续的CAS client在通过https请求调用CAS server相关接口(如验证ticket)不发生SSL handshake错误,
需要将生成的D:/etc/cas/cas.crt导入到JDK/JRE中,通过keytool导入命令如下:
# 导入证书 keytool -import -keystore "D:/programs/dev/java/jdk-11/lib/security/cacerts" -file D:/etc/cas/cas.crt -alias cas # 删除证书(该命令仅在需要删除证书时执行,非必需执行) keytool -delete -alias cas -keystore "D:/programs/dev/java/jdk-11/lib/security/cacerts"
注:
导入后需重启系统后方可生效,
我本地装有jdk8和jdk11,keytool导入时是在jdk11上执行的,而后续client是执行在jdk8上,
所以client通过https调用CAS server时一直报https验证错误,
后续将client也执行在jdk11上即问题消失,此处需要注意执行keytool导入的jdk和CAS server、CAS client执行的jdk需要保持一致。
(4)拷贝配置
执行如下命令将cas-overlay/etc/cas/config下的配置文件拷贝到根目录下/etc/cas/config(在windows系统下根目录即对应盘符根目录,比如cas-overlay在D盘下,则根目录则为D:/,即D:/etc/cas/config),
在之后启动cas.war时会自动到/etc/cas/config下读取配置文件
gradlew copyCasConfiguration
(5)添加用户
修改之前拷贝的配置文件D:/etc/cas/config/cas.properties,添加如下内容:
# 添加账户(用户名为luo,密码为123456) cas.authn.accept.users=luo::123456
(6)注册CAS Service(即注册CAS Client应用信息)
修改之前拷贝的配置文件D:/etc/cas/config/cas.properties,添加如下内容使CAS Server从JSON文件中读取service注册信息:
# 从JSON文件读取service注册信息 cas.service-registry.core.init-from-json=true # service注册对应的JSON文件目录位置 # 可通过classpath:指定resource目录 # 本示例直接指定根目录下/etc/cas/services,即对应D:/etc/cas/services cas.service-registry.json.location=file:/etc/cas/services
创建service注册对应的JSON文件,建议文件名称遵循{name}-{id}.json,如下配置即对应casSecureApp-1.json
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
//servcieId通过正则表达式定义client应用的URL pattern,该URL模式应该匹配client的URL
//如"^(https|http)://.*"
"serviceId" : "http://localhost:8900/login/cas",
//client的名称
"name" : "casSecuredApp",
//client的唯一的ID
"id" : 1,
//登出类型
"logoutType" : "BACK_CHANNEL",
//登出回调
"logoutUrl" : "http://localhost:8900/exit/cas"
}
且使用JSON文件注册service需在build.gradle添加依赖如下:
implementation "org.apereo.cas:cas-server-support-json-service-registry"
(7)启动CAS server
# 通过gradle启动 gradlew run # 或者直接通过java命令启动 java -jar build/libs/cas.war
如下看到READY即为启动成功
(8)访问CAS server
启动成功后即可通过https://localhost:8443/cas进行访问,此时亦可发现之前生成的https证书已生效。
可通过之前第(6)步添加的用户进行登录,即luo/123456,登录成功后如下图
SpringSecurity集成CAS具体可参见示例代码:https://github.com/marqueeluo/cas-secured-app
CAS Server(即cas-overlay)具体可参见示例代码:https://github.com/marqueeluo/cas-overlay
其中cas-secured-app中的核心cas配置如下:
# CAS server基础URL cas.server.base-url=https://localhost:8443/cas # CAS server登录页面URL cas.server.login-url=https://localhost:8443/cas/login # CAS server登出URL cas.server.logout-url=https://localhost:8443/cas/logout # CAS client基础URL cas.client.base-url=http://localhost:8900 # CAS client service参数(需匹配CAS service注册中的serviceId) cas.client.service=http://localhost:8900/login/cas # CAS client在当前应用中登出cas的urlPath(请求该url则自动重定向到cas.server.logout-url) cas.client.logout-cas-path=/logout/cas # CAS client在当前应用中单点登出的回调urlPath cas.client.slo-callback-path=/exit/cas
后续有时间会进一步记录
- 关于SpringSecurity集成CAS,具体可参见:https://www.baeldung.com/spring-security-cas-sso
- CAS通过database管理account、service注册,
可参见:
https://apereo.github.io/cas/6.4.x/password_management/Password-Management-JDBC.html
https://apereo.github.io/cas/6.4.x/services/JPA-Service-Management.html - CAS集成OIDC协议,可参见:https://apereo.github.io/cas/6.4.x/protocol/OIDC-Protocol.html
- CAS Logout and Single Logout (SLO)
参考:
https://apereo.github.io/cas/6.4.x/planning/Architecture.html
https://apereo.github.io/cas/6.4.x/installation/WAR-Overlay-Initializr.html
Maven的overlay插件的用法【结合cas4.0.3】
Stackoverflow - Difference Between OAUTH, OpenID and OPENID Connect in very simple term?
https://www.baeldung.com/spring-security-cas-sso
CSDN - CAS服务器5.3搭建



