一、簡(jiǎn)介
單點(diǎn)登錄(Single Sign On),簡(jiǎn)稱為 SSO,是目前比較流行的企業(yè)業(yè)務(wù)整合的解決方案之一。SSO的定義是在多個(gè)應(yīng)用系統(tǒng)中,用戶只需要登錄一次就可以訪問所有相互信任的應(yīng)用系統(tǒng)。
CAS 是一個(gè)開源的企業(yè)級(jí)單點(diǎn)登錄系統(tǒng),目前最新版本為 5.2.x。
CAS 包含兩個(gè)部分:CAS Server 和 CAS Client,它們之間獨(dú)立部署。CAS 客戶端攔截未認(rèn)證的用戶請(qǐng)求,并重定向至 CAS 服務(wù)端,由 CAS 服務(wù)端對(duì)用戶身份進(jìn)行統(tǒng)一認(rèn)證。
二、搭建服務(wù)端
對(duì)于本地搭建 CAS 服務(wù)端,官方提供了基于 Maven 和 Gradle 的 Overlay 構(gòu)建方式,本文用的是 CAS Maven WAR Overlay。
2.1 什么是 WAR Overlay?
Overlay 技術(shù)可以把多個(gè)項(xiàng)目 war 合并成為一個(gè)項(xiàng)目,如果項(xiàng)目存在同名文件,那么主項(xiàng)目中的文件將覆蓋掉其他項(xiàng)目的同名文件。
使用 Overlay 無需對(duì) CAS 源碼進(jìn)行編譯,也避免了對(duì) CAS 源碼進(jìn)行侵入性改造。
2.2 環(huán)境清單
- JDK 1.8
- Tomcat 8.0+
- IntelliJ IDEA 2017.2
2.3 Overlay 構(gòu)建
下載 CAS Maven WAR Overlay,修改 pom.xml ,設(shè)置 CAS 版本為 5.2.2。建議去除掉 pom.xml 文件中的 wrapper-maven-plugin 和無用的 profile 配置。
<properties>
<cas.version>5.2.2</cas.version>
</properties>
首次導(dǎo)入 IDEA,可以看到后臺(tái)正在下載官方 cas.war。

工程 overlays 目錄下的文件是由 maven 編譯后才產(chǎn)生的,可以在 pom.xml 中配置官方 cas.war 中的文件的那些文件可以排除,不要在 overlays 中生成:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<warName>cas</warName>
<failOnMissingWebXml>false</failOnMissingWebXml>
<recompressZippedFiles>false</recompressZippedFiles>
<archive>
<compress>false</compress>
<manifestFile>${manifestFileToUse}</manifestFile>
</archive>
<overlays>
<overlay>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-webapp${app.server}</artifactId>
<!--原有的服務(wù)不再初始化進(jìn)去-->
<excludes>
<exclude>WEB-INF/classes/services/*</exclude>
<exclude>WEB-INF/classes/application.*</exclude>
</excludes>
</overlay>
</overlays>
</configuration>
</plugin>
打開 Project Structure,可以觀察到該工程具有兩個(gè) Web Root,但是 src/main/webapp 目錄并不存在,需要進(jìn)行手動(dòng)創(chuàng)建。

拷貝 overlays 目錄下的 application.properties 配置文件至 resources 目錄,用于覆蓋 CAS WAR 中的同名文件。最終工程目錄結(jié)構(gòu)如下:

為工程配置 tomcat 8.0 并啟動(dòng),注意 CAS 5.2.x 不支持低于 tomcat 8.0 的版本。
看到控制臺(tái)打印 READY 表明啟動(dòng)成功。

訪問 http://localhost:8080/cas/login 進(jìn)入登錄界面。

其中Non-secure Connection提示需要配置 SSL,Static Authentication提示需要對(duì)用戶配置進(jìn)行修改,可以修改為 JDBC、REST 等方式。目前用戶配置寫死在 application.properties 配置文件中,用戶名為 casuser,密碼為 Mellon。
##
# CAS Authentication Credentials
#
cas.authn.accept.users=casuser::Mellon
2.4 Services配置
客戶端接入 CAS 首先需要在服務(wù)端進(jìn)行注冊(cè),否則客戶端訪問將提示“未認(rèn)證授權(quán)的服務(wù)”警告:

在 resources 文件夾下創(chuàng)建 services 文件夾進(jìn)行服務(wù)定義,該目錄中可包含多個(gè) JSON 文件,其命名必須滿足以下規(guī)則:
JSON fileName = serviceName + "-" + serviceNumericId + ".json"
創(chuàng)建 services/Localhost-10000003.json 文件,表示允許所有以 http://localhost 開頭的認(rèn)證請(qǐng)求:
{
"@class": "org.apereo.cas.services.RegexRegisteredService",
"serviceId": "^(http)://localhost.*",
"name": "本地服務(wù)",
"id": 10000003,
"description": "這是一個(gè)本地允許的服務(wù),通過localhost訪問都允許通過",
"evaluationOrder": 1
}
對(duì)其中屬性的說明如下,更多詳細(xì)內(nèi)容見官方文檔-Service-Management。
- @class:必須為org.apereo.cas.services.RegisteredService的實(shí)現(xiàn)類
- serviceId:對(duì)服務(wù)進(jìn)行描述的表達(dá)式,可用于匹配一個(gè)或多個(gè) URL 地址
- name: 服務(wù)名稱
- id:全局唯一標(biāo)志
- evaluationOrder:定義多個(gè)服務(wù)的執(zhí)行順序
最后,根據(jù)官方文檔-service-registry,還需修改 application.properties 文件告知 CAS 服務(wù)端從本地加載服務(wù)定義文件:
#開啟識(shí)別json文件,默認(rèn)false
cas.serviceRegistry.initFromJson=true
#自動(dòng)掃描服務(wù)配置,默認(rèn)開啟
#cas.serviceRegistry.watcherEnabled=true
#120秒掃描一遍
#cas.serviceRegistry.repeatInterval=120000
#延遲15秒開啟
#cas.serviceRegistry.startDelay=15000
#資源加載路徑
#cas.serviceRegistry.config.location=classpath:/services
啟動(dòng)時(shí)打印以下日志,說明服務(wù)注冊(cè)成功。
2018-03-18 23:36:08,660 INFO [org.apereo.cas.services.AbstractServicesManager] - <Loaded [0] service(s) from [InMemoryServiceRegistry].>
2018-03-18 23:36:08,876 INFO [org.apereo.cas.config.CasServiceRegistryInitializationConfiguration] - <Attempting to initialize the service registry [InMemoryServiceRegistry] from service definition resources found at [class path resource [services]]>
2018-03-18 23:36:08,877 WARN [org.apereo.cas.services.ServiceRegistryInitializer] - <Service registry [InMemoryServiceRegistry] will be auto-initialized from JSON service definitions. This behavior is only useful for testing purposes and MAY NOT be appropriate for production. Consider turning off this behavior via the setting [cas.serviceRegistry.initFromJson=false] and explicitly register definitions in the services registry.>
2018-03-18 23:36:09,283 INFO [org.apereo.cas.services.AbstractServicesManager] - <Loaded [3] service(s) from [InMemoryServiceRegistry].>
三、搭建客戶端
在官方文檔中提供了 CAS Java 客戶端樣例,即 cas-sample-java-webapp。
修改 pom.xml,配置 tomcat7-maven-plugin:
<!-- tomcat7 plugin -->
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.2</version>
<configuration>
<port>8181</port>
<uriEncoding>UTF-8</uriEncoding>
<server>tomcat7</server>
<path>/node1</path>
</configuration>
</plugin>
CAS Client 通過攔截器將未認(rèn)證的請(qǐng)求重定向到 CAS Server,這里對(duì) cas-sample-java-webapp 的 web.xml 文件進(jìn)行修改,將服務(wù)端、客戶端地址替換為實(shí)際測(cè)試的地址:
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<!--
<context-param>
<param-name>renew</param-name>
<param-value>true</param-value>
</context-param>
-->
<!--單點(diǎn)登出過濾器-->
<filter>
<filter-name>CAS Single Sign Out Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://localhost:8080/cas</param-value>
</init-param>
</filter>
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!--用來跳轉(zhuǎn)登錄-->
<filter>
<filter-name>CAS Authentication Filter</filter-name>
<!--<filter-class>org.jasig.cas.client.authentication.Saml11AuthenticationFilter</filter-class>-->
<filter-class>org.jasig.cas.client.authentication.AuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>http://localhost:8080/cas/login</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<!--這是客戶端的部署地址,認(rèn)證時(shí)會(huì)帶著這個(gè)地址,認(rèn)證成功后會(huì)跳轉(zhuǎn)到這個(gè)地址-->
<param-value>http://localhost:8181/node1</param-value>
</init-param>
</filter>
<!--Ticket校驗(yàn)過濾器-->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<!--<filter-class>org.jasig.cas.client.validation.Saml11TicketValidationFilter</filter-class>-->
<filter-class>org.jasig.cas.client.validation.Cas30ProxyReceivingTicketValidationFilter</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://localhost:8080/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:8181/node1</param-value>
</init-param>
<init-param>
<param-name>redirectAfterValidation</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>useSession</param-name>
<param-value>true</param-value>
</init-param>
<init-param>
<param-name>authn_method</param-name>
<param-value>mfa-duo</param-value>
</init-param>
</filter>
<!-- 該過濾器負(fù)責(zé)實(shí)現(xiàn)HttpServletRequest請(qǐng)求的包裹,比如允許開發(fā)者通過HttpServletRequest的getRemoteUser()方法獲得SSO登錄用戶的登錄名,可選配置-->
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<!-- 該過濾器使得開發(fā)者可以通過org.jasig.cas.client.util.AssertionHolder來獲取用戶的登錄名。 比如AssertionHolder.getAssertion().getPrincipal().getName()-->
<!--<filter>
<filter-name>CASAssertion Thread LocalFilter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CASAssertion Thread LocalFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>-->
<filter-mapping>
<filter-name>CAS Single Sign Out 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 Authentication 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>
<welcome-file-list>
<welcome-file>
index.jsp
</welcome-file>
</welcome-file-list>
</web-app>
此時(shí)訪問
http://localhost:8181/node1
會(huì)跳轉(zhuǎn)至
http://localhost:8080/cas/login?service=http%3A%2F%2Flocalhost%3A8181%2Fnode1%2F
輸入用戶信息,登錄成功,返回
http://localhost:8181/node1/;jsessionid=6628138DCAAA5BA3481CD4C9238FEBFF

利用相同的方法配置第二個(gè)客戶端,訪問地址為 http://localhost:8282/node2,可知在 node1 登錄成功的情況下,無需再次輸入用戶密碼即可訪問 node2 后臺(tái)頁(yè)面。
至此,開發(fā)環(huán)境搭建完畢。由于客戶端只是對(duì) web.xml 中的過濾器進(jìn)行配置,可以很方便地集成到各個(gè)業(yè)務(wù)系統(tǒng)中。