Apereo CAS 爬坑

96
Jude95
0.2 2018.11.18 22:47* 字数 1557

心血来潮想给公司搭个单点登录系统,毕竟内网这么多系统,每个系统都一套账号密码,这样好low。CAS提供了一个统一认证服务,所有系统直接向CAS去请求登陆认证,而用户只在CAS登陆一次就够了,在大一点的公司里基本都有这种服务,叫SSO(Single sign-on)。
找了找开源的SSO,看到了CAS,感觉挺屌的,一直在更新,就他了。然后就开始爬坑了,记录一下,免得日后忘了又爬一次。

CAS-Overlay-Template

这个项目很大,还需要很多自定义配置,所以不可能做成开箱即用。CAS-Overlay-Template 就是一个模板项目,clone下来, 修改配置,然后是直接跑还是打包war或者打包docker都好说。
新版本的 CAS-Overlay-Template 已经改为Gradle编译,我喜欢Gradle。
clone下来后,直接 先运行
./build.sh copy
再运行
./build.sh run
就跑起来了。

编译配置

跑起来个屁。
默认是CAS 6.0版本,直接用Java11了,要跑6.0版本先去装个jdk11,配好环境。我踩坑的时候6.0还是RC版本,发现有bug就还是滚回5.3.5,配置这些东西在gradle.properties

# Versions
cas.version=5.3.5  // 可以改成6.0.0-rc3
springBootVersion=2.1.0.RELEASE

appServer=-tomcat

gradleVersion=4.10.2
tomcatVersion=9
tomcatFullVersion=9.0.12

group=你的组织名
sourceCompatibility=8   // 6.0以上就改成11
targetCompatibility=8    // 6.0以上就改成11

# Location of the downloaded CAS shell JAR
shellDir=build/libs

# use without "-slim" in tag name if you want tools like jstack, adds about 100MB to image size
baseDockerImage=openjdk:8-slim   // 6.0以上就改成11

同时,还有增加几个依赖,因为CAS完全是插件化设计

    compile "org.apereo.cas:cas-server-support-jdbc:${casServerVersion}"
    compile "org.apereo.cas:cas-server-support-jdbc-drivers:${casServerVersion}"
    compile "org.apereo.cas:cas-server-support-pm:${casServerVersion}"
    compile "org.apereo.cas:cas-server-support-pm-jdbc:${casServerVersion}"

然后下1个小时依赖就可以跑起来了,可喜可贺,可喜可贺。

配置目录

image.png

这个目录,里面包含了所有的配置,基本上改这里面的东西就够了。

这个配置目录是如何生效的?

之所以先运行 ./build.sh copy 是因为

function copy() {
    echo -e "Creating configuration directory under /etc/cas"
    mkdir -p /etc/cas/config

    echo -e "Copying configuration files from etc/cas to /etc/cas"
    cp -rfv ./etc/cas/* /etc/cas
}

他会把配置文件复制进系统配置目录,然后 Springboot 启动后会去读这个系统配置目录。
另外,注意一下 src/main/jib/docker/entrypoint.sh 里面是类似 Dockerfile

#!/bin/sh

#echo -e "\nChecking java..."
#java -version

#echo -e "\nCreating CAS configuration directories..."
mkdir -p /etc/cas/config
mkdir -p /etc/cas/services

#echo "Listing provided CAS docker artifacts..."
#ls -R docker/cas

#echo -e "\nMoving CAS configuration artifacts..."
mv docker/cas/thekeystore /etc/cas 2>/dev/null
mv docker/cas/config/*.* /etc/cas/config 2>/dev/null
mv docker/cas/services/*.* /etc/cas/services 2>/dev/null

#echo -e "\nListing CAS configuration under /etc/cas..."
#ls -R /etc/cas

echo -e "\nRunning CAS..."
exec java -Xms512m -Xmx2048M -XX:+TieredCompilation -XX:TieredStopAtLevel=1 -jar docker/cas/war/sso.war

打包镜像时,也会直接把配置目录复制进镜像的系统配置目录

build.sh

常用操作 build.sh 都已经写好了,常用的比如

  • ./build.sh copy 复制配置到系统目录
  • ./build.sh run 运行CAS
  • ./build.sh docker 打包镜像
  • ./build.sh debug Debug CAS

配置

然后几乎所有的服务配置都在 cas/config/cas.properties
一点一点的补充吧,把example.com换成自己的域名。
下面是 5.3.5的配置,5.2.x 的配置不一样,6.0.0也不一样,文档没写,我也不写🙂。
后文会介绍怎么去源码看当前版本的配置项

服务地址

cas.server.name=http://sso.example.com
cas.server.prefix=${cas.server.name}    //要使用根Path访问,比如 http://sso.example.com 就这样。默认值是`${cas.server.name}/cas`
server.context-path=      //对,就是空,这样就可以使用根Path访问了,默认值是 /cas
server.port=443    // HTTPS的端口,默认8443
cas.server.http.enabled=true    // 开启HTTP,默认只有HTTPS,而且我只知道怎么开HTTP,不知道怎么关HTTPS
cas.server.http.port=80  // 设置HTTP端口,默认8080

上面这几个配置挺操蛋的,我以为 cas.server.prefix 改了就可以 http://sso.example.com 直接访问了,结果还是要加上/cas才能访问,这个配置是假的,只是用于各种重定向url填充用。server.context-path 才是真正的 Path。

数据库认证

cas.authn.accept.users=        // 留空才会禁用 Static Authentication
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
cas.authn.jdbc.query[0].url=jdbc:mysql://xxxxxxxxxxxxxxxx?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
cas.authn.jdbc.query[0].user=xxxxxxx
cas.authn.jdbc.query[0].password=xxxxxxxxx
cas.authn.jdbc.query[0].sql=select * from user where name=?

// 这些配置懒得讲了,网上大部分文章讲的都没问题
cas.authn.jdbc.query[0].fieldPassword=password    
cas.authn.jdbc.query[0].fieldExpired=expired
cas.authn.jdbc.query[0].fieldDisabled=disabled
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

配置数据库认证很简单。去给数据库建个表。填一下字段名和Sql,就可以了。然后跑一下试试吧

密码管理-邮件

CAS支持密码管理功能,直接网页上点忘记密码,就给你邮箱发重置邮件,配置比较多,坑也很多

# 发送邮件,我用的腾讯企业邮
spring.mail.host=smtp.exmail.qq.com
spring.mail.port=465
spring.mail.username=xxxxxx@example.com
spring.mail.password=xxxxxxx

# 这是腾讯企业邮的配置,其他邮箱自己配,可能有些坑,但这个不管CAS的事
spring.mail.properties.mail.smtp.auth=true
spring.mail.properties.mail.smtp.socketFactory.port=465
spring.mail.properties.mail.smtp.socketFactory.class=javax.net.ssl.SSLSocketFactory
spring.mail.properties.mail.smtp.socketFactory.fallback=false
spring.mail.properties.mail.smtp.ssl=true

密码管理-数据库

改改自己的SQL就好了

cas.authn.pm.enabled=true    // 开启修改密码功能
cas.authn.pm.autoLogin=true  // 修改密码后自动登录
cas.authn.pm.jdbc.autocommit=true  // 加就是了
cas.authn.pm.jdbc.sqlFindEmail=SELECT email FROM user WHERE name=?
cas.authn.pm.jdbc.sqlChangePassword=UPDATE user SET password=? WHERE name=?
cas.authn.pm.jdbc.url=${cas.authn.jdbc.query[0].url}
cas.authn.pm.jdbc.user=${cas.authn.jdbc.query[0].user}
cas.authn.pm.jdbc.password=${cas.authn.jdbc.query[0].password}
cas.authn.pm.jdbc.dialect=${cas.authn.jdbc.query[0].dialect}
cas.authn.pm.jdbc.driverClass=${cas.authn.jdbc.query[0].driverClass}
# 密码修改加密规则,这个必须要和原始密码加密规则一致
cas.authn.pm.jdbc.passwordEncoder.type=${cas.authn.jdbc.query[0].passwordEncoder.type}
cas.authn.pm.jdbc.passwordEncoder.characterEncoding=${cas.authn.jdbc.query[0].passwordEncoder.characterEncoding}
cas.authn.pm.jdbc.passwordEncoder.encodingAlgorithm=${cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm}
cas.authn.pm.jdbc.passwordEncoder.secret=${cas.authn.jdbc.query[0].passwordEncoder.secret}

密码管理-业务配置

cas.authn.pm.reset.mail.from=${spring.mail.username}
cas.authn.pm.reset.mail.subject=[XXX公司 SSO系统] 重置密码
# 邮件内容,必须要有%s,因为会生成一个连接并且带了token,否则无法打开链接,当然这个链接也和cas.server.prefix有关系
cas.authn.pm.reset.mail.text=[季诺科技 SSO]打开以下链接重置您的密码,10分钟后失效: %s
cas.authn.pm.reset.expirationMinutes=10  // 邮件有效期
cas.authn.pm.reset.securityQuestionsEnabled=false    // 问题验证,我觉得这个操作很蠢,就关了
# 8-32位,必须包含大小写字母,数字,字符。你可以填其他密码检验正则
cas.authn.pm.policyPattern=^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[$@$!%*?&])[A-Za-z\\d$@$!%*?&]{8,32}

神坑

路由转发环境下的IP校验

到这里可能本地跑起来没问题了,修改密码都正常。一部署到线上就不能修改密码,从邮箱验证链接点过去,CAS每次都拒绝此次密码修改请求。为啥?
翻了大片的源码,终于找到了原因。邮箱里收到的链接参数是加了密的,取消加密的话就很容易看出问题。

http://sso.example.com/login?pswdrst={"jti":"ad06632d-dcee-43e0-bf0f-639150912b96","iss":"http://sso.example.com","aud":"http://sso.example.com","exp":1542541827,"iat":1542541227,"origin":"17*.17*.0.11","client":"1*.1*.97.138","sub":"jude"}

token里包含了请求邮件验证时的客户端IP,也不知道为啥,Nginx转发后,他拿到的IP就老是不对,点击链接访问时,IP对不上他就不认这次请求。
普通的直接部署的CAS系统,不加这个没问题。但我们的内网环境是有一台Nginx路由,所有请求都会经过他转发,就出问题。
差点去改源码了,好在无聊翻源码时找到,CAS原来留了一手来处理这个问题。

cas.audit.alternateClientAddrHeaderName=X-Forwarded-For

添加这个配置, CAS就会从Header里读X-Forwarded-For来确认客户端IP,这样就不存在转发后IP不对的问题了。

6.0版本的密码修改BUG

我也不清楚6.0版本加了找回用户名这个积累功能后,为啥找回密码页面他老是发请求到找回用户名的接口上去。导致找回密码和找回用户名2个功能都不能正常工作。源码好多,对JSP无力,没细查,改回5.3.5,BUG消失,放弃6.0。

找配置文档

我上面的配置有很多,官网文档写的很糟,又少又老还跳来跳去,还每个版本都要改配置项(5.2.x, 5.3.x, 6.0.x都很多不一样)。
网上大部分文档都是5.2版本的祖传配置,一开始真是恶心死我了。
还是得自己去翻源码,先找到 CasServerProperties 这个类,他在

image.png

这个类就是配置的映射,还有少数注释讲了很多官方文档没说的操作。
image.png

照着这个类来填配置就行了。

弃坑

好不容易终于跑起来这个坑B玩意了,但十分不建议使用CAS,CAS优势是开放,适合二次开发。
但十分难用......去用keycloak吧。配置简单,界面还好看。

Android技术
Gupao