15 分钟部署一个 CAS 服务并对接 Shibboleth-I
前言
这是一个标题党。
CAS 是一个经典的单点登录方案,又有 开源版本 的支持,因此广大提供统一身份认证解决方案的供应商鲜有不支持 CAS 的——至少投标方案上是这样的。尽管如此,实际对接的时候可能会遇到问题,又或者 CAS 不归自己负责,想做个测试又不太方便接入生产环境,总之这时候就特别想要自己部署一个测试的 CAS 来进行验证。
是的,搭一个 CAS 服务器 15 分钟就够了。
注意下文部署的 CAS 仅适合测试,不要拿这个 CAS 直接用作生产环境哦。
cas-overlay
尽管 CAS 的功能极多且复杂,但是如果只考虑测试话,我们可以尽量简化他的配置。我们只引入ldap
和json-service-registry
模块,并打包成 docker
以简化环境配置。
考虑到测试方便,我们把 cas
和 shibboleth-idp
安装在同一台服务器,因此使用了 httpd
来对两者进行代理。
本文假定已经安装好了 shibboleth-idp-3.4.6
,并使用 httpd
方式代理发布。
- 首先拉取 apereo/cas-overlay-template ,由于 6.2.x 尚未正式发布,我切换到 6.1.x 分支。
git clone https://github.com/apereo/cas-overlay-template.git
cd cas-overlay-template/
git checkout 6.1
- 修改
build.gradle
,在dependencies
内增加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
服务,所以我们这里可以让httpd
卸载掉https
,cas
服务运行在http
上即可。同时加载json
目录的服务注册配置。
最后一行表示cas
所释放的属性,倒数第二行表示ldap
内属性和cas
的映射关系。例如如果用 AD 的话,那么这里可以配成cas.authn.ldap[0].principalAttributeList=employeeType:employeeType,sAMAccountName:uid
,此时cas
所释放的属性名依然是uid
,由sAMAccountName
映射产生。
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/
目录内新增idp-1001.json
文件,注册我们的idp
服务
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https)://idp.example.org.*"
"name" : "idp",
"id" : 1001,
"evaluationOrder" : 10
}
- 执行
./docker-build.sh
生成docker
镜像。Dockerfile
是cas-overlay-template/Dockerfile
这个文件。实际上就是在容器里使用./gradlew clean build
编译CAS
。如果在服务器上准备好了java 11
环境的话,直接执行./gradlew clean build
也是一样的,有兴趣的同学可以试试。 - 首次执行可能会有一点慢,耐心等待我们的容器镜像构建完成。
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
已经占用了 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
如果修改配置,则重新构建 docker
,再重新运行构建好的容器即可。由于底层已经构建过,此时只替换了配置文件,所以速度是很快的。然后停掉当前容器,删除之再重新拉起即可。
$ docker stop cas
cas
$ docker rm cas
cas
$ docker run -d -p 8081:8080 --name="cas" org.apereo.cas/cas:6.1.4
这些过程实际上也就是 cas-overlay-template/docker-run.sh
内的内容,大家可以根据实际情况修改后。直接执行该脚本即可。
- 修改
httpd
的配置,在idp.example.org
的对应的VirtualHost
内,增加下述配置,然后重启httpd
服务。
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
是不是已经起来了?
对接 Shibboleth-IdP 3.4.6
我们使用 Unicon/shib-cas-authn3 插件来对接 IdP 和 CAS。由于 IdP 3.4.3 之后有一个内部 API 变更,因此插件的配置有大幅调整,实际上变得更简单了。实测表明新版版的插件(3.3.0)还修复了一些老版本的 bug——比如一个 CAS 属性无法同时映射给两个 IdP 属性的问题。建议大家尽量选择升级 IdP 到 3.4.6 后使用新版插件对接。
准备工作
- 下载相关的 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 协议,这里修改为 cas20 并取消注释
# shibcas.ticketValidatorName = cas30
运行 /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
在属性释放时还需要进行一些特殊转换,即 xsi:type="ScriptedAttribute"
或者 xsi:type="Scoped"
等,那么可以先讲属性映射进来,再作为其他 AttributeDefinition
的 Dependency
的引入,例如下面这个示例:先将 employeeType
获取到之后,标注为employeetype
,然后引入到AttributeDefinition xsi:type="ScriptedAttribute"
中进行脚本计算。
<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>
以上
参考文献
- CAS Enterprise Single Sign-On
- apereo/cas-overlay-template
- Get Docker Engine - Community for CentOS
- Unicon/shib-cas-authn3
- 上海教育认证中心:IdP-CAS对接