CAS的全称Central Authentication Service即中央认证服务,是Web系统中企业单点登录(SSO)的一个解决方案
1 基础概念
1.1 CAS协议包含三部分
- 客户端浏览器
- CAS服务器
- 请求验证身份的应用程序
1.2 环境
1.3 CAS术语
TGC
TGT
ST
PGT
2 CAS服务部署
2.1 基础环境准备
CAS的官方文档
本文以Maven方式编译CAS服务端,overlay方式是指可以基于配置文件覆盖CAS原工程中的配置,达到自定义CAS服务目的。
将github中的工程克隆到本地
2.2 基础配置
本文基于自定义配置方案配置CAS服务端,使用CAS自带的Tomcat。
2.2.1 添加maven镜像
办法1:直接在cas的pom.xml文件中添加aliyun镜像
<repositories>
<repository>
<id>maven-ali</id>
<url>http://maven.aliyun.com/nexus/content/groups/public//</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
</repository>
</repositories>
办法2:在maven的settings.xml
<mirrors>
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
</mirrors>
2.2.2 配置证书
需求是基于https登陆CAS服务端,我们使用JDK自带的工具keytool在当前目录生成证书,sso.bob.net就是当前CAS服务端的地址,后续基于此地址访问。
密钥口令是:123456
➜ ca keytool -genkey -alias caskeystore -keypass 123456 -keyalg RSA -keystore thekeystore
输入密钥库口令:
再次输入新口令:
您的名字与姓氏是什么?
[Unknown]: sso.bob.net
您的组织单位名称是什么?
[Unknown]: bob
您的组织名称是什么?
[Unknown]: bob
您所在的城市或区域名称是什么?
[Unknown]: sz
您所在的省/市/自治区名称是什么?
[Unknown]: js
该单位的双字母国家/地区代码是什么?
[Unknown]: cn
CN=sso.bob.net, OU=bob, O=bob, L=sz, ST=js, C=cn是否正确?
[否]: y
2.2.3 导出数字证书
密钥口令是:123456
keytool -export -alias caskeystore -keystore thekeystore -rfc -file cas.crt
2.2.4 将数字证书导入到JDK下的JRE
导入JDK,输入的密码是changeit,
sudo keytool -import -alias caskeystore -keystore $JAVA_HOME/jre/lib/security/cacerts -file cas.crt -trustcacerts -storepass changeit
2.2.5 配置本地IP域名映射
windows系统需要编辑
C:\Windows\System32\drivers\etc\hosts
2.2.6 keytool相关命令
# 查看thekeystore证书信息
keytool -list -keystore thekeystore
# 查看证书列表
keytool -list -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit
# 删除指定证书
keytool -delete -alias taobao -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit
2.3 更改CAS配置
使用idea导入CAS工程,mvn clean package,在根目录下得到target目录,打开WEB-INF目录下的classes里面的application.properties文件。根目录新建src/main/resources
文件夹,同时将刚才的application.properties文件复制到该目录下。整个工程目录如下:
打开application.properties文件,我们可以发现在开头有配置信息如下:
server.ssl.key-store=file:/etc/cas/thekeystore server.ssl.key-store-password=changeit server.ssl.key-password=changeit
修改成
#SSL配置 server.ssl.enabled=true server.ssl.key-store=classpath:thekeystore server.ssl.key-store-password=123456 server.ssl.key-password=123456 server.ssl.keyAlias=caskeystore
其中server.ssl.key-store=classpath:thekeystore表示证书放在工程目录下,每次编译就使用此证书,方便迁移,本文就不在将证书放在本机的/etc/cas目录下。
在根目录下执行
# 清理工程
sh build.sh clean
# 打包
sh build.sh package
# 打包运行
sh build.sh run
# 直接运行war包
java -jar target/cas.war
CAS服务端启动失败,可以查看这里。
2.4 基于JDBC的认证配置
2.4.1 JDBC依赖
默认的用户认证方式是在application.properties中配置用户名以及密码,但是这个只是用于demo。本文需要使用JDBC方式验证登录用户,即将用户名以及密码存入数据库,基于JDBC查询数据库,验证用户密码是否可以通过。
<!--新增支持jdbc验证-->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc</artifactId>
<version>${cas.version}</version>
</dependency>
<!--若不想找驱动可以直接写下面的依赖即可,其中包括HSQLDB、Oracle、MYSQL、PostgreSQL、MariaDB、Microsoft SQL Server-->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc-drivers</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.driver.version}</version>
</dependency>
2.4.2 表信息
expired:过期字段,1为过期,需要修改密码。
disabled:不可用字段,1为不可用。
Mysql表结构信息如下:
CREATE TABLE `auth`.`user` (
`id` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
`username` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`password` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`expired` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`disabled` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`email` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
CREATE TABLE `auth`.`user_attr` (
`id` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NOT NULL,
`username` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`role` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
`company` varchar(255) CHARACTER SET latin1 COLLATE latin1_swedish_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
表数据如下:
user表:
user_arttr表:
2.4.3 认证
对于密码,常用加密算法如下:
- MD5
- SHA
- HMAC
加密类型包括:
Type | Description |
---|---|
NONE |
No password encoding (i.e. plain-text) takes place. |
DEFAULT |
Use the DefaultPasswordEncoder of CAS. For message-digest algorithms via characterEncoding and encodingAlgorithm . |
BCRYPT |
Use the BCryptPasswordEncoder based on the strength provided and an optional secret . |
SCRYPT |
Use the SCryptPasswordEncoder . |
PBKDF2 |
Use the Pbkdf2PasswordEncoder based on the strength provided and an optional secret . |
STANDARD |
Use the StandardPasswordEncoder based on the secret provided. |
GLIBC_CRYPT |
Use the GlibcCryptPasswordEncoder based on the encodingAlgorithm , strength provided and an optional secret . |
org.example.MyEncoder |
An implementation of PasswordEncoder of your own choosing. |
file:///path/to/script.groovy |
Path to a Groovy script charged with handling password encoding operations. |
如果需要密码无加密,调整passwordEncoder.type=NONE
现在我们更改application.properties配置,同时注释静态用户配置,具体更改如下:
##
# CAS Authentication Credentials
#
#cas.authn.accept.users=casuser::Mellon
#查询账号密码SQL,必须包含密码字段
cas.authn.jdbc.query[0].sql=select * from user where username=?
#指定上面的SQL查询字段名(必须)
cas.authn.jdbc.query[0].fieldPassword=password
#指定过期字段,1为过期,若过期不可用
cas.authn.jdbc.query[0].fieldExpired=expired
#为不可用字段段,1为不可用,需要修改密码
cas.authn.jdbc.query[0].fieldDisabled=disabled
#数据库连接
cas.authn.jdbc.query[0].url=jdbc:mysql://10.110.149.65:3306/auth?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
#数据库dialect配置
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect
#数据库用户名
cas.authn.jdbc.query[0].user=wes
#数据库用户密码
cas.authn.jdbc.query[0].password=xxss
#数据库事务自动提交
cas.authn.jdbc.query[0].autocommit=false
#数据库驱动
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
#超时配置
cas.authn.jdbc.query[0].idleTimeout=5000
#默认加密策略,通过encodingAlgorithm来指定算法,默认NONE不加密
# NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
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
# 加密盐
#cas.authn.jdbc.query[0].passwordEncoder.secret=
# 加密字符长度
#cas.authn.jdbc.query[0].passwordEncoder.strength=16
2.5 服务配置
2.5.1 基础概念
服务管理表示服务端注册各个应用/客户端,可以让这些应用/客户端使用指定的CAS服务。这些服务包括:
- 授权服务-控制那些客户端可以参与CAS SSO会话
- 强制身份验证 - 为强制身份验证提供管理控制
- 属性发布-为客户端提供用户信息详情以进行授权和个性化
2.5.2 基于JSON的服务存储方式
CAS默认初始化使用,注册表在应用程序上下文初始化时从JSON配置文件中读取服务定义,期望在配置的目录位置内找到JSON文件。
添加依赖关系
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-json-service-registry</artifactId>
<version>${cas.version}</version>
</dependency>
resources目录下心间services目录,在resources/services文件夹下面新建web-10000001.json,具体内容如下:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|imaps|http)://.*",
"name" : "web",
"id" : 10000001,
"evaluationOrder" : 10,
"attributeReleasePolicy": {
"@class": "org.apereo.cas.services.ReturnAllAttributeReleasePolicy"
}
}
注意: Json文件名字规则为${name}-${id}.json,id必须为Json文件内容Json一致。
Json文件解释:
@class:必须为org.apereo.cas.services.RegisteredService的实现类,对其他属性进行一个json反射对象,常用的有RegexRegisteredService,匹配策略为id的正则表达式
serviceId:唯一的服务id
name: 服务名称,会显示在默认登录页
id:全局唯一标志
description:服务描述,会显示在默认登录页
evaluationOrder: 匹配争取时的执行循序,最好是比1大的数字
CAS工程中target目录下有个services目录,存放了CAS默认的服务配置项,本文直接弃用这些配置,即执行服务端时不初始化默认的服务配置项。在pom.xml文件中做如下配置。
</configuration>
<dependentWarExcludes>
**/services/*.json
</dependentWarExcludes>
</configuration>
然后在配置文件application.properties下添加配置:
##
# Service Registry(服务注册)
#
# 开启识别Json文件,默认false
cas.serviceRegistry.initFromJson=true
#自动扫描服务配置,默认开启
#cas.serviceRegistry.watcherEnabled=true
#120秒扫描一遍
cas.serviceRegistry.schedule.repeatInterval=120000
#延迟15秒开启
# cas.serviceRegistry.schedule.startDelay=15000
# Json配置
cas.serviceRegistry.json.location=classpath:/services
启动CAS服务后,日志显示加载了一个服务配置项,如下图所示:
2.6 多属性返回配置
在业务系统的开发中往往少不了返回用户的一些属性,包括单属性以及多属性,例如用户的一些基本信息或者权限,部门角色等等,因为业务系统获取到这些信息可能需要做一些其他业务逻辑
本文基于JDBC方式配置CAS,实现多个属性返回。属性返回策略包括:
- Return All (所有配置返回的都返回)
- Deny All (配置拒绝的出现则报错)
- Return Allowed(只返回允许的主要属性)
- 自定义Filter(自定义过滤策略)
2.6.1 开启JSON服务配置
##
# Service Registry(服务注册)
#
# 开启识别Json文件,默认false
cas.serviceRegistry.initFromJson=true
#自动扫描服务配置,默认开启
cas.serviceRegistry.watcherEnabled=true
#120秒扫描一遍
cas.serviceRegistry.schedule.repeatInterval=120000
#延迟15秒开启
# cas.serviceRegistry.schedule.startDelay=15000
##
# Json配置
cas.serviceRegistry.json.location=classpath:/services
然后再在pom.xml文件中开启Json依赖包和属性返回依赖包
<!-- Json Service Registry -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-json-service-registry</artifactId>
<version>${cas.version}</version>
</dependency>
<!-- Authentication Attributes -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-authentication-attributes</artifactId>
<version>${cas.version}</version>
</dependency>
具体的JSON配置文件如下:
{
"@class": "org.apereo.cas.services.RegexRegisteredService",
"serviceId": "^(https|imaps|http)://.*",
"name": "t1",
"id": 1001,
"evaluationOrder": 10,
"attributeReleasePolicy": {
"@class": "org.apereo.cas.services.ReturnAllAttributeReleasePolicy"
}
}
说明:
- 将表中所有属性返回
"attributeReleasePolicy": {
"@class": "org.apereo.cas.services.ReturnAllAttributeReleasePolicy"
}
- 限制属性返回,即返回指定的属性
"attributeReleasePolicy" : {
"@class" : "org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy",
"allowedAttributes" : [ "java.util.ArrayList", [ "username", "mail" ] ]
}
2.6.2 单行属性配置
#单行属性
#cas.authn.attributeRepository.jdbc[0].attributes.username=username
#cas.authn.attributeRepository.jdbc[0].attributes.password=password
#cas.authn.attributeRepository.jdbc[0].attributes.expired=expired
#
#cas.authn.attributeRepository.jdbc[0].singleRow=true
#cas.authn.attributeRepository.jdbc[0].order=0
#cas.authn.attributeRepository.jdbc[0].requireAllAttributes=true
## cas.authn.attributeRepository.jdbc[0].caseCanonicalization=NONE|LOWER|UPPER
## cas.authn.attributeRepository.jdbc[0].queryType=OR|AND
#
#cas.authn.attributeRepository.jdbc[0].sql=SELECT * FROM user WHERE {0}
#cas.authn.attributeRepository.jdbc[0].username=username
#
##数据库连接
#cas.authn.attributeRepository.jdbc[0].url=jdbc:mysql://10.110.149.65:3306/auth?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
#
##数据库dialect配置
#cas.authn.attributeRepository.jdbc[0].dialect=org.hibernate.dialect.MySQLDialect
#
##数据库用户名
#cas.authn.attributeRepository.jdbc[0].user=wq
#
##数据库用户密码
#cas.authn.attributeRepository.jdbc[0].password=123
#
##数据库事务自动提交
#cas.authn.attributeRepository.jdbc[0].autocommit=false
#
##数据库驱动
#cas.authn.attributeRepository.jdbc[0].driverClass=com.mysql.jdbc.Driver
#
##超时配置
#cas.authn.attributeRepository.jdbc[0].idleTimeout=5000
#
#cas.authn.attributeRepository.jdbc[0].ddlAuto=none
#
#cas.authn.attributeRepository.jdbc[0].leakThreshold=10
#cas.authn.attributeRepository.jdbc[0].batchSize=1
#cas.authn.attributeRepository.jdbc[0].dataSourceProxy=false
启动CAS服务,查看属性返回情况。
CAS服务端返回信息:
WHO: hebj
WHAT: [result=Service Access Granted,service=http://localhost:5000/cas/login/?orig...,principal=SimplePrincipal(id=hebj, attributes={password=[e10adc3949ba59abbe56e057f20f883e], expired=[0], username=[hebj]}),requiredAttributes={}]
ACTION: SERVICE_ACCESS_ENFORCEMENT_TRIGGERED
APPLICATION: CAS
WHEN: Sun Feb 23 09:28:56 CST 2020
CLIENT IP ADDRESS: 127.0.0.1
SERVER IP ADDRESS: 127.0.0.1
CAS客户端返回信息:
{ 'cas:authenticationDate': '2020-02-23T09:28:56.485+08:00[Asia/Shanghai]',
'cas:authenticationMethod': 'QueryDatabaseAuthenticationHandler',
'cas:credentialType': 'UsernamePasswordCredential',
'cas:expired': '0',
'cas:isFromNewLogin': 'true',
'cas:longTermAuthenticationRequestTokenUsed': 'false',
'cas:password': 'e10adc3949ba59abbe56e057f20f883e',
'cas:successfulAuthenticationHandlers':
'QueryDatabaseAuthenticationHandler','cas:username': 'hebj'
}
2.6.3 多属性返回信息
#多行属性
cas.authn.attributeRepository.jdbc[1].attributes.admin=admin_multi
cas.authn.attributeRepository.jdbc[1].attributes.dev=dev_multi
cas.authn.attributeRepository.jdbc[1].attributes.user=user_multi
cas.authn.attributeRepository.jdbc[1].columnMappings.role=company
cas.authn.attributeRepository.jdbc[1].singleRow=false
cas.authn.attributeRepository.jdbc[1].order=1
cas.authn.attributeRepository.jdbc[1].requireAllAttributes=true
# cas.authn.attributeRepository.jdbc[1].caseCanonicalization=NONE|LOWER|UPPER
# cas.authn.attributeRepository.jdbc[1].queryType=OR|AND
cas.authn.attributeRepository.jdbc[1].sql=SELECT * FROM user_attr WHERE {0}
cas.authn.attributeRepository.jdbc[1].username=username
#数据库连接
cas.authn.attributeRepository.jdbc[1].url=jdbc:mysql://10.110.149.65:3306/auth?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
#数据库dialect配置
cas.authn.attributeRepository.jdbc[1].dialect=org.hibernate.dialect.MySQLDialect
#数据库用户名
cas.authn.attributeRepository.jdbc[1].user=e q
#数据库用户密码
cas.authn.attributeRepository.jdbc[1].password=123
#数据库事务自动提交
cas.authn.attributeRepository.jdbc[1].autocommit=false
#数据库驱动
cas.authn.attributeRepository.jdbc[1].driverClass=com.mysql.jdbc.Driver
#超时配置
cas.authn.attributeRepository.jdbc[1].idleTimeout=5000
cas.authn.attributeRepository.jdbc[1].ddlAuto=none
cas.authn.attributeRepository.jdbc[1].leakThreshold=10
cas.authn.attributeRepository.jdbc[1].batchSize=1
cas.authn.attributeRepository.jdbc[1].dataSourceProxy=false
重启CAS服务端,查看属性返回信息。
CAS服务端信息:
WHO: hebj
WHAT: [result=Service Access Granted,service=http://localhost:5000/cas/login/?orig...,principal=SimplePrincipal(id=hebj, attributes={admin_multi=1, user_multi=[2, 1]}),requiredAttributes={}]
ACTION: SERVICE_ACCESS_ENFORCEMENT_TRIGGERED
APPLICATION: CAS
WHEN: Sun Feb 23 09:49:17 CST 2020
CLIENT IP ADDRESS: 127.0.0.1
SERVER IP ADDRESS: 127.0.0.1
CAS客户端返回信息:
{
'cas:admin_multi': '1',
'cas:authenticationDate': '2020-02-23T09:49:17.900+08:00[Asia/Shanghai]',
'cas:authenticationMethod': 'QueryDatabaseAuthenticationHandler',
'cas:credentialType': 'UsernamePasswordCredential',
'cas:isFromNewLogin': 'true',
'cas:longTermAuthenticationRequestTokenUsed': 'false',
'cas:successfulAuthenticationHandlers':
'QueryDatabaseAuthenticationHandler',
'cas:user_multi': ['2', '1']
}
关于客户端接收多属性配置,参看这里。
参考:
1. 此博客写的非常详细,且基于MacOS环境做的本地配置,https://blog.csdn.net/Anumbrella/article/details/80821486