前言
這是一個(gè)標(biāo)題黨。
CAS 是一個(gè)經(jīng)典的單點(diǎn)登錄方案,又有 開源版本 的支持,因此廣大提供統(tǒng)一身份認(rèn)證解決方案的供應(yīng)商鮮有不支持 CAS 的——至少投標(biāo)方案上是這樣的。盡管如此,實(shí)際對(duì)接的時(shí)候可能會(huì)遇到問題,又或者 CAS 不歸自己負(fù)責(zé),想做個(gè)測(cè)試又不太方便接入生產(chǎn)環(huán)境,總之這時(shí)候就特別想要自己部署一個(gè)測(cè)試的 CAS 來(lái)進(jìn)行驗(yàn)證。
是的,搭一個(gè) CAS 服務(wù)器 15 分鐘就夠了。
注意下文部署的 CAS 僅適合測(cè)試,不要拿這個(gè) CAS 直接用作生產(chǎn)環(huán)境哦。
cas-overlay
盡管 CAS 的功能極多且復(fù)雜,但是如果只考慮測(cè)試話,我們可以盡量簡(jiǎn)化他的配置。我們只引入ldap 和json-service-registry 模塊,并打包成 docker 以簡(jiǎn)化環(huán)境配置。
考慮到測(cè)試方便,我們把 cas 和 shibboleth-idp 安裝在同一臺(tái)服務(wù)器,因此使用了 httpd 來(lái)對(duì)兩者進(jìn)行代理。
本文假定已經(jīng)安裝好了 shibboleth-idp-3.4.6 ,并使用 httpd 方式代理發(fā)布。
- 首先拉取 apereo/cas-overlay-template ,由于 6.2.x 尚未正式發(fā)布,我切換到 6.1.x 分支。
git clone https://github.com/apereo/cas-overlay-template.git
cd cas-overlay-template/
git checkout 6.1
- 修改
build.gradle,在dependencies內(nèi)增加ldap和json-service-registry的編譯依賴
dependencies {
// Other CAS dependencies/modules may be listed here...
compile "org.apereo.cas:cas-server-support-json-service-registry:${casServerVersion}"
compile "org.apereo.cas:cas-server-support-ldap:${casServerVersion}"
}
- 安裝 docker,詳見 Get Docker Engine - Community for CentOS
$ sudo yum install -y yum-utils \
device-mapper-persistent-data \
lvm2
$ sudo yum-config-manager \
--add-repo \
https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum install docker-ce docker-ce-cli containerd.io
$ sudo systemctl start docker
- 修改
cas-overlay-template/etc/cas/config/cas.properties配置文件。由于我們使用httpd代理cas服務(wù),所以我們這里可以讓httpd卸載掉https,cas服務(wù)運(yùn)行在http上即可。同時(shí)加載json目錄的服務(wù)注冊(cè)配置。
最后一行表示cas所釋放的屬性,倒數(shù)第二行表示ldap內(nèi)屬性和cas的映射關(guān)系。例如如果用 AD 的話,那么這里可以配成cas.authn.ldap[0].principalAttributeList=employeeType:employeeType,sAMAccountName:uid,此時(shí)cas所釋放的屬性名依然是uid,由sAMAccountName映射產(chǎn)生。
server.port=8080
server.ssl.enabled=false
cas.server.tomcat.http.enabled=false
cas.server.name=https://idp.exmaple.org
cas.server.prefix=${cas.server.name}/cas
logging.config: file:/etc/cas/config/log4j2.xml
cas.serviceRegistry.initFromJson=false
cas.serviceRegistry.json.location=file:/etc/cas/services
cas.authn.ldap[0].type=AUTHENTICATED
cas.authn.ldap[0].ldapUrl=ldap://ldap.example.org:389
cas.authn.ldap[0].useSsl=false
cas.authn.ldap[0].baseDn=dc=example,dc=org
cas.authn.ldap[0].searchFilter=uid={user}
cas.authn.ldap[0].bindDn=cn=admin,dc=example,dc=org
cas.authn.ldap[0].bindCredential=password
cas.authn.ldap[0].principalAttributeList=employeeType:employeeType,uid:uid,sn:sn
cas.authn.attributeRepository.defaultAttributesToRelease=employeeType,uid,sn
- 在
cas-overlay-template/etc/cas/services/目錄內(nèi)新增idp-1001.json文件,注冊(cè)我們的idp服務(wù)
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https)://idp.example.org.*"
"name" : "idp",
"id" : 1001,
"evaluationOrder" : 10
}
- 執(zhí)行
./docker-build.sh生成docker鏡像。Dockerfile是cas-overlay-template/Dockerfile這個(gè)文件。實(shí)際上就是在容器里使用./gradlew clean build編譯CAS。如果在服務(wù)器上準(zhǔn)備好了java 11環(huán)境的話,直接執(zhí)行./gradlew clean build也是一樣的,有興趣的同學(xué)可以試試。 - 首次執(zhí)行可能會(huì)有一點(diǎn)慢,耐心等待我們的容器鏡像構(gòu)建完成。
Successfully built 6c1396544479
Successfully tagged org.apereo.cas/cas:6.1.4
Built CAS image successfully tagged as org.apereo.cas/cas:6.1.4
REPOSITORY TAG IMAGE ID CREATED SIZE
org.apereo.cas/cas 6.1.4 6c1396544479 Less than a second ago 247MB
由于我們和 idp 裝在一起,而 idp 的 tomcat 已經(jīng)占用了 8080 端口,所以將其映射到 8081 上。
$ docker run -d -p 8081:8080 --name="cas" org.apereo.cas/cas:6.1.4
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
8ed61668ad65 org.apereo.cas/cas:6.1.4 "java -server -nover…" 3 seconds ago Up 2 seconds 8443/tcp, 0.0.0.0:8081->8080/tcp cas
如果修改配置,則重新構(gòu)建 docker,再重新運(yùn)行構(gòu)建好的容器即可。由于底層已經(jīng)構(gòu)建過,此時(shí)只替換了配置文件,所以速度是很快的。然后停掉當(dāng)前容器,刪除之再重新拉起即可。
$ docker stop cas
cas
$ docker rm cas
cas
$ docker run -d -p 8081:8080 --name="cas" org.apereo.cas/cas:6.1.4
這些過程實(shí)際上也就是 cas-overlay-template/docker-run.sh 內(nèi)的內(nèi)容,大家可以根據(jù)實(shí)際情況修改后。直接執(zhí)行該腳本即可。
- 修改
httpd的配置,在idp.example.org的對(duì)應(yīng)的VirtualHost內(nèi),增加下述配置,然后重啟httpd服務(wù)。
ProxyPreserveHost On
RequestHeader set X-Forwarded-Proto https
RemoteIPHeader X-Forwarded-For
ProxyPass "/cas/" "http://localhost:8081/cas/"
- 好拉,訪問
https://idp.examle.org/cas/login,看看cas是不是已經(jīng)起來(lái)了?
對(duì)接 Shibboleth-IdP 3.4.6
我們使用 Unicon/shib-cas-authn3 插件來(lái)對(duì)接 IdP 和 CAS。由于 IdP 3.4.3 之后有一個(gè)內(nèi)部 API 變更,因此插件的配置有大幅調(diào)整,實(shí)際上變得更簡(jiǎn)單了。實(shí)測(cè)表明新版版的插件(3.3.0)還修復(fù)了一些老版本的 bug——比如一個(gè) CAS 屬性無(wú)法同時(shí)映射給兩個(gè) IdP 屬性的問題。建議大家盡量選擇升級(jí) IdP 到 3.4.6 后使用新版插件對(duì)接。
準(zhǔn)備工作
- 下載相關(guān)的 cas-client-core-3.6.0.jar 和 shib-cas-authenticator-3.3.0-oauth.jar文件備用。
- 下載 no-conversation-state.jsp 文件備用
以下假定 IdP 安裝在 /opt/shibboleth-idp/
- IdP 版本至少 3.4.6
安裝
- 把下載的
no-conversation-state.jsp放入/opt/shibboleth-idp/edit-webapp中 - 把下載的
cas-client-core-3.6.0.jar和shib-cas-authenticator-3.3.0.jar放入/opt/shibboleth-idp/edit-webapp/WEB-INF/lib中 - 將
/opt/shibboleth-idp/dist/webapp/WEB-INF/web.xml拷貝到/opt/shibboleth-idp/edit-webapp/WEB-INF/web.xml
cp /opt/shibboleth-idp/dist/webapp/WEB-INF/web.xml /opt/shibboleth-idp/edit-webapp/WEB-INF/web.xml
- 修改
/opt/shibboleth-idp/edit-webapp/WEB-INF/web.xml增加以下部分
...
<!-- Servlet for receiving a callback from an external CAS Server and continues the IdP login flow -->
<servlet>
<servlet-name>ShibCas Auth Servlet</servlet-name>
<servlet-class>net.unicon.idp.externalauth.ShibcasAuthServlet</servlet-class>
<load-on-startup>2</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>ShibCas Auth Servlet</servlet-name>
<url-pattern>/Authn/External/*</url-pattern>
</servlet-mapping>
...
- 修改 idp.properties 配置文件
idp.authn.flows = External
shibcas.casServerUrlPrefix = https://idp.example.org/cas
shibcas.casServerLoginUrl = ${shibcas.casServerUrlPrefix}/login
# idp 的地址
shibcas.serverName = https://idp.example.org
# 如果不支持 cas3.0 協(xié)議,這里修改為 cas20 并取消注釋
# shibcas.ticketValidatorName = cas30
運(yùn)行 /opt/shibboleth-idp/bin/build.sh 重新編譯 IdP ,然后重啟 IdP 即可
屬性映射
直接映射
AttributeDefinition 中的 xsi:type="SubjectDerivedAttribute" 為從插件中獲取屬性的配置,例如下面的示例表示將 cas 釋放的 sn 映射為 cn
<AttributeDefinition xsi:type="SubjectDerivedAttribute" id="cn" principalAttributeName="sn">
<AttributeEncoder xsi:type="SAML1String" name="urn:mace:dir:attribute-def:cn" encodeType="false" />
<AttributeEncoder xsi:type="SAML2String" name="urn:oid:2.5.4.3" friendlyName="cn" encodeType="false" />
</AttributeDefinition>
作為引用
如果 IdP 在屬性釋放時(shí)還需要進(jìn)行一些特殊轉(zhuǎn)換,即 xsi:type="ScriptedAttribute" 或者 xsi:type="Scoped" 等,那么可以先講屬性映射進(jìn)來(lái),再作為其他 AttributeDefinition 的 Dependency 的引入,例如下面這個(gè)示例:先將 employeeType 獲取到之后,標(biāo)注為employeetype,然后引入到AttributeDefinition xsi:type="ScriptedAttribute" 中進(jìn)行腳本計(jì)算。
<AttributeDefinition xsi:type="ScriptedAttribute" id="eduPersonScopedAffiliation">
<Dependency ref="employeetype" />
<Script><![CDATA[
var localpart = "";
if(employeetype.getValues().get(0)=="01") localpart = "staff";
else if(employeetype.getValues().get(0)=="02") localpart = "student";
else localpart = "other";
eduPersonScopedAffiliation.addValue(localpart + "@%{idp.scope}");
]]></Script>
<AttributeEncoder xsi:type="SAML1String" name="urn:mace:dir:attribute-def:eduPersonScopedAffiliation" encodeType="false" />
<AttributeEncoder xsi:type="SAML2String" name="urn:oid:1.3.6.1.4.1.5923.1.1.1.9" friendlyName="eduPersonScopedAffiliation" encodeType="false" />
</AttributeDefinition>
<AttributeDefinition xsi:type="SubjectDerivedAttribute" id="employeetype" principalAttributeName="employeeType"></AttributeDefinition>
以上
參考文獻(xiàn)
- CAS Enterprise Single Sign-On
- apereo/cas-overlay-template
- Get Docker Engine - Community for CentOS
- Unicon/shib-cas-authn3
- 上海教育認(rèn)證中心:IdP-CAS對(duì)接