在上一节我们讲述了CAS中关于自定义认证登录策略,对CAS中关于自定义登录配置的方案,校验策略有了一定的了解,如果忘记了可以去复习一下——————CAS单点登录(四)——自定义认证登录策略。这节本来该介绍自定义表单信息的知识,但是考虑到使用自定义表单知识涉及到Service配置方面的知识,所以这一节介绍一下在CAS中Service配置及管理。
首先我们要明白CAS中的Service的概念是什么,我们在第一节就讲解了在CAS系统中,主要分为三部分,User、Web应用、SSO认证中心。User就是我们普通用户,Web应用就是需要接入SSO认证中心的应用也就是这里的Service,而SSO认证中心就是CAS服务端。
简单来说就是CAS分为服务端和客户端,而Service就是指具体的多个客户端(CAS Clients)。
而这里的服务管理(Service Management)就是CAS服务管理工具允许CAS服务器管理员声明和配置哪些服务(Service,CAS客户端)可以在哪些方面使用CAS。服务管理工具的核心组件是服务注册表,它存储一个或多个注册服务。
接下来先介绍我们的第一个知识点——Service配置!
一、Service配置
我们刚刚提及到在CAS中,服务管理工具中的服务注册表当中存储着一个或多个注册服务,而这些Service中包含着各个行为的元数据,通过配置这些数据我们可以控制这些Service的行为。
主要行为包括一些几点:
- 授权服务 - 控制哪些服务可以参与CAS SSO会话。
- 强制身份验证 - 为强制身份验证提供管理控制。
- 属性发布 - 为服务提供用户详细信息以进行授权和个性化。
- 代理控制 - 通过授予/拒绝代理身份验证功能进一步限制授权服务。
- 主题控制 - 定义用于特定服务的备用CAS主题。
在Service中配置属性主要包括以下这些信息:
上图介绍了一些在Service中常用的配置项,对于各个配置属性的含义可以参考具体文档,服务配置。
在这些配置中,比较常使用的主要是:
服务访问策略——(accessStrategy),具体可以查看:服务策略配置。
服务属性配置——(properties),具体查看:服务属性配置。
服务到期政策——(expirationPolicy),具体查看:服务到期配置。
对CAS元数据的配置信息有了大致的了解后,我们需要配置其存储方式,就像我们前面介绍的多种认证方式一样,用户信息提供了多种方式,这里的Service存储方式也是提供了多种的解决方案。
推荐使用JSON、YAML、MongoDb、Redis、JPA这几种方式来存储使用,这里也将使用这几种方式来介绍。
1、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文件夹下面新建web-10000001.json,具体内容如下:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|imaps|http)://.*",
"name" : "web",
"id" : 10000001,
"evaluationOrder" : 10
}
注意: 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服务中,默认是提供了默认的Service配置项,所以如果添加的Json配置没起作用,可以尝试注释掉默认启动Json,在pom.xml文件里面进行配置,如下:
然后在配置文件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
启动服务,我们可以发现注释掉war包下Json初始化后,默认只启动了一个Json,在控制台中我们可以发现启动日志:
如果没有注释话,默认会是加载了2个服务配置。
输入具体地址https://sso.anumbrella.net:8443/cas/login?service=http://localhost:9080/sample
,进行登录,输入用户名、密码登录成功!然后跳转到一个http://localhost:9080/sample?ticket=xxxxxxxxxxxxxx
的地址。
后面接入的就是指具体的服务地址。说明先前的配置是其作用的,因为配置了http通过服务认证。
但是我们要求的是模拟客户端登录,这里暂时没有准备客户端接入,后面文章会准备讲解,所以我们这里直接使用官方提供的Demo,更改接入地址即可。
这里官方准备的客户端Demo是Java版本的,地址为:cas-sample-java-webapp
这里我重新给客户端设置绑定一个client.anumbrella.net域名,同样在hosts文件下进行映射。
然后我重新导入了一个client.anumbrella.net的证书,如果忘记了操作,可以看一下CAS单点登录(二)——搭建基础服务。将证书导入JDK中,可以通过命令查看导入了哪些证书:
查看cacerts中的证书列表:
Windows:
keytool -list -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit
Unix:
keytool -list -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit
删除cacerts中指定名称的证书:
Windows:
keytool -delete -alias taobao -keystore "%JAVA_HOME%/jre/lib/security/cacerts" -storepass changeit
Unix:
keytool -delete -alias taobao -keystore $JAVA_HOME/jre/lib/security/cacerts -storepass changeit
进入客户端,配置webapp下面WEB-INF下面的web.xml文件。如下图所示:
https://sso.anumbrella.net:8443/cas/login
为CAS服务端的地址,而https://client.anumbrella.net:9443
为我们客户端待会要启动的地址,也就是我们所说的登录地址。这里我客户端是使用了https,如果不使用https,这里配置为http://localhost:9080也是可以的。
然后配置我们的证书,如果客户端没使用https,直接配置为http://localhost:9080,后面的操作可以不用配置了,前面的证书也不用导入。
配置好证书的地址和密匙即可。接着进入Demo项目的根目录,执行mvn clean package jetty:run-forked
命令,
在浏览器中输入https://client.anumbrella.net:9443/sample
,或者输入http://localhost:9080/sample
跳转到CAS登录地址,输入用户名和密码,登录成功!
我们继续使用先前提到的服务访问策略进行更改,更改Json配置如下:
{
"@class" : "org.apereo.cas.services.RegexRegisteredService",
"serviceId" : "^(https|imaps|http)://.*",
"name" : "web",
"id" : 10000001,
"evaluationOrder" : 10,
"accessStrategy" : {
"@class" : "org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy",
"enabled" : false,
"ssoEnabled" : false
}
}
禁止服务使用,这里就是我们前面提到的服务策略配置,再启动CAS服务端测试一下。
直接提示我们未认证授权的服务!!!,说明访问策略起作用了。
2、YAML
yaml配置与json配置基本一致,添加依赖:
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-yaml-service-registry</artifactId>
<version>${cas.version}</version>
</dependency>
在resources/services文件夹下面新建web-10000001.yml,具体内容如下:
--- !<org.apereo.cas.services.RegexRegisteredService>
serviceId: "^(https|imaps|http)://.*"
name: "web"
id: 10000001
description: "description"
evaluationOrder: 10
attributeReleasePolicy: !<org.apereo.cas.services.ReturnAllAttributeReleasePolicy> {}
accessStrategy: !<org.apereo.cas.services.DefaultRegisteredServiceAccessStrategy>
enabled: true
ssoEnabled: true
注意:
yaml文件名字规则为${name}-${id}.yml
,id必须为yaml文件内容id一致。其他与Json配置一样的。
在application.properties更改配置如下:
cas.serviceRegistry.initFromJson=false
cas.serviceRegistry.yaml.location=classpath:/services
启动服务,登录成功!!
3、MongoDb
同样的先添加依赖:
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-mongo-service-registry</artifactId>
<version>${cas.version}</version>
</dependency>
在application.properties更改配置如下:
##
# MongoDB配置
#
#ip地址
cas.serviceRegistry.mongo.host=localhost
#cas.serviceRegistry.mongo.clientUri=
cas.serviceRegistry.mongo.idleTimeout=30000
#数据库端口
cas.serviceRegistry.mongo.port=27017
cas.serviceRegistry.mongo.dropCollection=false
cas.serviceRegistry.mongo.socketKeepAlive=false
#
#密码
cas.serviceRegistry.mongo.password=admin
cas.serviceRegistry.mongo.collection=cas-service-registry
cas.serviceRegistry.mongo.databaseName=admin
cas.serviceRegistry.mongo.timeout=5000
#用户名
cas.serviceRegistry.mongo.userId=admin
cas.serviceRegistry.mongo.writeConcern=NORMAL
cas.serviceRegistry.mongo.replicaSet=
cas.serviceRegistry.mongo.sslEnabled=false
cas.serviceRegistry.mongo.conns.lifetime=60000
cas.serviceRegistry.mongo.conns.perHost=10
这里要注意一下:MongoDB默认需要开启认证才行,如果不开起认证,启动CAS会报错。
我使用的是docker启动的MongoDB,直接使用的是admin数据库,用户名和密码配置也是添加环境变量启动的。如果自己搭建的MongoDB数据库,可以参考一下下面的配置:
MongoDB用户配置(版本3.4以上)
#启动
>mongod.exe
#登录
>mongo
#切换数据库
>use admin
#新增管理员
>db.createUser({user: "admin",pwd: "admin",roles:[{role:"userAdminAnyDatabase", db: "admin" } ]})
#切换数据库
>use cas-mongo-database
# 新增用户
>db.createUser({user: "cas",pwd: "123456",roles: [ { role: "readWrite", db: "cas-mongo-database" }]})
#重启并开启认证
>mongod.exe --auth
启动服务后,发现服务提示这样的信息?如下情况:
这是为啥?因为我们没有定义可以认证的服务,这就需要我们使用服务管理(Service Management)添加服务数据,或者手动添加数据信息到数据库中,像Json、Yaml中文件配置的信息一样。所以暂时可以忽略不用管,后面我会讲解如何使用Service Management来添加。
4、Redis
Redis的使用与MongoDB的用法大致相同,这里就不详细分析了。同样添加依赖:
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-redis-service-registry</artifactId>
<version>${cas.version}</version>
</dependency>
添加配置信息:
##
# Redis配置
#
cas.serviceRegistry.redis.host=localhost
cas.serviceRegistry.redis.database=0
cas.serviceRegistry.redis.port=6380
cas.serviceRegistry.redis.password=
cas.serviceRegistry.redis.timeout=2000
cas.serviceRegistry.redis.useSsl=false
cas.serviceRegistry.redis.usePool=true
cas.serviceRegistry.redis.pool.max-active=20
cas.serviceRegistry.redis.pool.maxIdle=8
cas.serviceRegistry.redis.pool.minIdle=0
cas.serviceRegistry.redis.pool.maxActive=8
cas.serviceRegistry.redis.pool.maxWait=-1
cas.serviceRegistry.redis.pool.numTestsPerEvictionRun=0
cas.serviceRegistry.redis.pool.softMinEvictableIdleTimeMillis=0
cas.serviceRegistry.redis.pool.minEvictableIdleTimeMillis=0
cas.serviceRegistry.redis.pool.lifo=true
cas.serviceRegistry.redis.pool.fairness=false
5、Jpa
最后介绍一下Jpa这种方式,通过Java持久层API来实现数据保存到数据库,可以是Mysql、Oracle、SQL Service这种方式可能使用的比较多一些。
同样的先添加依赖:
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jpa-service-registry</artifactId>
<version>${cas.version}</version>
</dependency>
添加配置信息:
##
# Jpa配置
#
cas.serviceRegistry.jpa.user=root
cas.serviceRegistry.jpa.password=123
cas.serviceRegistry.jpa.driverClass=com.mysql.jdbc.Driver
cas.serviceRegistry.jpa.url=jdbc:mysql://127.0.0.1:3306/cas?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
cas.serviceRegistry.jpa.dialect=org.hibernate.dialect.MySQL5Dialect
cas.serviceRegistry.jpa.failFastTimeout=1
cas.serviceRegistry.jpa.healthQuery=
cas.serviceRegistry.jpa.isolateInternalQueries=false
cas.serviceRegistry.jpa.leakThreshold=10
cas.serviceRegistry.jpa.batchSize=1
#设置配置的服务,一直都有,不会给清除掉 , 第一次使用,需要配置为 create-drop
#create-drop 重启cas服务的时候,就会给干掉
#create 没有表就创建,有就不创建
#none 一直都有
#update 更新
cas.serviceRegistry.jpa.ddlAuto=update
cas.serviceRegistry.jpa.autocommit=true
cas.serviceRegistry.jpa.idleTimeout=5000
cas.serviceRegistry.jpa.pool.suspension=false
cas.serviceRegistry.jpa.pool.minSize=6
cas.serviceRegistry.jpa.pool.maxSize=18
cas.serviceRegistry.jpa.pool.maxWait=2000
cas.serviceRegistry.jpa.pool.timeoutMillis=1000
更详细的驱动信息可以参考文档:
https://apereo.github.io/cas/5.3.x/installation/JDBC-Drivers.html
启动服务后,还是和前面一样,提示CAS的服务记录是空的,没有定义服务。 希望通过CAS进行认证的应用程序必须在服务记录中明确定义。。。暂时可以忽略掉,马上我们就来配置服务管理。
然后我们去数据库发现,新增了一些表,这就是CAS将服务信息保存到数据库使用的表。
二、Service管理
在前面的服务配置中,我们知道了CAS需要先把服务的信息确定下来,无论你是写在Json、MongoDB、Redis还是数据库MySQL、Oracle等等中,如果在没有信息前,你是无法登陆认证的。
而Service管理相当于我们可以动态管理我们自己的服务信息,随时增加,随时删除。
1、服务管理Web应用程序
官方为我们提供了一个管理Web服务的简单Demo,我们可以直接使用,这个应用服务管理webapp不是CAS服务器的一部分,是单独的一个服务。
我们然后新建src/main/resources
文件夹,在下面新建文件application.properties,添加覆盖配置如下:
##
# CAS Thymeleaf Views
#
spring.thymeleaf.cache=false
spring.thymeleaf.mode=HTML
spring.thymeleaf.order=1
##
# Embedded CAS Tomcat Container
#
server.context-path=/cas-management
server.port=8081
server.ssl.key-store=calsspath:thekeystore
server.ssl.key-store-password=changeit
server.ssl.key-password=changeit
##
# Log4J Configuration
#
server.context-parameters.isLog4jAutoInitializationDisabled=true
# logging.config=file:/etc/cas/log4j2.xml
##
# CAS Server
#
cas.server.name=https://sso.anumbrella.net:8443
cas.server.prefix=${cas.server.name}/cas
##
# CAS Authentication Attributes
#
cas.authn.attributeRepository.stub.attributes.uid=uid
cas.authn.attributeRepository.stub.attributes.givenName=givenName
cas.authn.attributeRepository.stub.attributes.eppn=eppn
# cas-management服务地址
mgmt.serverName=https://client.anumbrella.net:8081
##
# CAS Web Application Config
#
server.session.timeout=1800
server.session.cookie.http-only=true
server.session.tracking-modes=COOKIE
##
# CAS Cloud Bus Configuration
# Please leave spring.cloud.bus.enabled set to false
#
spring.cloud.bus.enabled=false
#Indicates that systemPropertiesOverride can be used.
# Set to false to prevent users from changing the default accidentally. Default true.
spring.cloud.config.allow-override=true
# External properties should override system properties.
spring.cloud.config.override-system-properties=false
# When allowOverride is true, external properties should take lowest priority, and not override any
# existing property sources (including local config files).
spring.cloud.config.override-none=false
##
# Actuator Endpoint Security Defaults
#
endpoints.sensitive=true
endpoints.enabled=false
endpoints.actuator.enabled=
这里也是通过覆盖配置,使用Service Management,这里的cas-management其实也相当于一个客户端使用,只是它可以用来进行Service的管理配置。
同时新建user-details.properties文件,添加用户配置,如下:
casuser=notused,ROLE_ADMIN
anumbrella=notused,ROLE_ADMIN
这里我添加了anumbrella用户,默认只有casuser用户。
注意:cas-management启动需要https,所以我又在Tomcat中的server.xml中添加了一个端口,来启动cas-management,并使用client.anumbrella.net域名绑定。
还有一个细节注意,如果在同一电脑下启动CAS服务端,又启动cas-management,注意端口占用问题。如果出现该问题,启动一个应用时,把另一个端口注释掉,当启动时再打开。
启动cas-management应用后,发现并不能登录,又出现刚刚先前那个没有未认证授权的服务页面。这是因为cas-management也是一个客户端(client),所以没有在数据库中添加相应信息是不能登录的。(现在使用的是Jpa模式存储)
打开数据库的RegexRegisteredService表,按照我们先前Json一样的数据进行添加,把要求的必填信息填写进去就可以了,如下:
这里我就填写了了id、evaluation_order、name、serviceId这四个字段,分别为1,10,cas-management,^(https|imaps|http)://.*
。等大约2分钟后,配置扫描更改成功后,我们再刷新登录页面,输入密码登录成功!
这就是我们需要的cas-management,它提供了完善的service配置信息添加,点击旁边的添加按钮我们可以发现详细的service添加信息。
无论是基本的service信息,退出路由配置,服务策略配置等等都有。。
我们还可以以不同形式查看,比如以Json查看,这里的信息就是我们先前Json配置的一样,复制过去就可以使用。这里提供了更为详细的模板。
目前cas-management只是把信息存储在内存中,我们需要它指定为我们的数据库存储——Jpa方式,同前面的操作一样。添加依赖,添加配置,
<!-- 数据库jdbc驱动 -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jdbc-drivers</artifactId>
<version>${cas.version}</version>
</dependency>
<!-- Jpa Service Registry -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-support-jpa-service-registry</artifactId>
<version>${cas.version}</version>
</dependency>
添加配置:
##
# Jpa配置
#
cas.serviceRegistry.jpa.user=root
cas.serviceRegistry.jpa.password=123
cas.serviceRegistry.jpa.driverClass=com.mysql.jdbc.Driver
cas.serviceRegistry.jpa.url=jdbc:mysql://127.0.0.1:3306/cas?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
cas.serviceRegistry.jpa.dialect=org.hibernate.dialect.MySQL5Dialect
cas.serviceRegistry.jpa.failFastTimeout=1
cas.serviceRegistry.jpa.healthQuery=
cas.serviceRegistry.jpa.isolateInternalQueries=false
cas.serviceRegistry.jpa.leakThreshold=10
cas.serviceRegistry.jpa.batchSize=1
#设置配置的服务,一直都有,不会给清除掉 , 第一次使用,需要配置为 create-drop
#create-drop 重启cas服务的时候,就会给干掉
#create 没有表就创建,有就不创建
#none 一直都有
#update 更新
cas.serviceRegistry.jpa.ddlAuto=update
cas.serviceRegistry.jpa.autocommit=true
cas.serviceRegistry.jpa.idleTimeout=5000
cas.serviceRegistry.jpa.pool.suspension=false
cas.serviceRegistry.jpa.pool.minSize=6
cas.serviceRegistry.jpa.pool.maxSize=18
cas.serviceRegistry.jpa.pool.maxWait=2000
cas.serviceRegistry.jpa.pool.timeoutMillis=1000
然后我们重启服务,登录成功后,会发现先前我们在数据库中添加的服务信息。然后我们新增一个名为client-demo的服务,接着去数据库的RegexRegisteredService表查看,会发现完成新信息的添加!这里就实现了cas-management同步管理我们CAS系统Service的增删改查。
2、自定义接口管理服务
我们发现管理CAS每次都要启动一个cas-management,实在是麻烦,难道没简单方法吗?其实我们可以自己去实现数据的操作。
这里有两个思路,1、直接操作数据库,或者我们需要存储的文件,直接把需要的信息写进去。2、利用CAS提供的接口来实现操作。
这里讲解一下利用CAS提供的接口来实现操作,我们知道cas-management其实就是封装了一下界面,然后调用提供的接口,直接操作增删改查。
我们也可以在CAS服务添加一个Restful类似的接口,通过CAS中的ServicesManager来操作services。我们可以查看一下ServicesManager源码:
ServicesManager是一个接口,封装了我们增删改查的各种方法,简直nice!需要的就是它。
使用自定义接口需要依赖库:
<!-- Services Management -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-services-api</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>
接着我们自定义controller层如下:
package net.anumbrella.sso.controller;
import org.apereo.cas.services.RegexRegisteredService;
import org.apereo.cas.services.RegisteredService;
import org.apereo.cas.services.ReturnAllAttributeReleasePolicy;
import org.apereo.cas.services.ServicesManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.io.IOException;
import java.net.URL;
/**
* @author anumbrella
*/
@RestController
@RequestMapping("/services")
public class ServicesManagerController {
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
/**
* 添加cas客户端
* 增加了单点退出功能,cas退出默认使用隐式退出
* protocol 代表的是协议,比如: http或者https的协议
*/
@RequestMapping(value = "/addClient/{protocol}/{serviceId}/{id}", method = RequestMethod.GET)
public String addService(@PathVariable("serviceId") String serviceId, @PathVariable("protocol") String protocol
, @PathVariable("id") int id) throws IOException {
String url = protocol + "://" + serviceId;
RegisteredService svc = servicesManager.findServiceBy(url);
if (svc != null) {
return "0";
}
//serviceId,可以配置为正则匹配
String a = "^" + url + ".*";
RegexRegisteredService service = new RegexRegisteredService();
ReturnAllAttributeReleasePolicy re = new ReturnAllAttributeReleasePolicy();
service.setServiceId(a);
service.setId(id);
service.setAttributeReleasePolicy(re);
//将name统一设置为servicesId
service.setName(serviceId);
//单点登出
service.setLogoutUrl(new URL(url));
servicesManager.save(service, true);
servicesManager.load();
return "1";
}
/**
* 删除服务
*
* @param serviceId
* @return
*/
@RequestMapping(value = "/delete/{serviceId}", method = RequestMethod.GET)
public String delService(@PathVariable("serviceId") String serviceId) {
String res = "";
RegisteredService svc = servicesManager.findServiceBy(serviceId);
if (svc != null) {
try {
servicesManager.delete(svc);
} catch (Exception e) {
if (null == servicesManager.findServiceBy(serviceId)) {
res = "success";
servicesManager.load();
} else {
res = "failed";
}
}
}
return res;
}
}
最后在resources下的META-INF文件下的spring.factories注入spring boot的配置,
启动服务访问我们设置的路由,传入数据,如下:
最后我们去数据库可以发现我们添加的服务信息。
因此我们可以在Restful中进行服务的管理。
代码实例:Chapter4