# 前言

今天的笔记是介绍如何搭建一个简单的 CAS 服务认证中心。

相关链接

1. CAS 5.3 官网
https://apereo.github.io/cas/5.3.x/index.html
2. CAS 5.3 开始指南
https://apereo.github.io/cas/5.3.x/planning/Getting-Started.html
3. CAS 5.3 覆盖安装
https://apereo.github.io/cas/5.3.x/installation/Maven-Overlay-Installation.html

# 环境准备

选择 CAS 5.3 作为构建版本

  • JDK 1.8
  • Maven 3.5.3
  • CAS 5.3

根据官方文档部署说明,推荐我们使用 WAR Overlay method 的方法,利用覆盖机制来组合 CAS 原始工件和本地自定义方法来达到自定义 CAS 的要求。官方给了两种编译方式,一种是 Maven 、另一种是 Gradle ,这里使用 Maven 安装部署。

项目构建目录源目录
CAS Maven WAR Overlaytarget/cas.war!WEB-INF/classes/src/main/resources
CAS Gradle WAR Overlaycas/build/libs/cas.war!WEB-INF/classes/src/main/resources

建议使用 WAR Overlay 方法在本地构建和部署 CAS 。这种方法不需要采用者显式下载任何版本的 CAS,而是利用覆盖机制将 CAS 原始工件和本地定制结合起来,以进一步简化未来的升级和维护。

# 开始

要构建覆盖项目,您需要将构建目录中需要自定义的目录和文件复制到源目录。

注意:不要在上面提到的构建目录中进行更改。每次进行构建时,更改集都将被清除并设置回默认值。将覆盖的组件放在源目录或其他指示位置以避免意外。

# 搭建环境

1、访问 github 构建模板项目地址

2、构建模板项目选择 5.3 版本分支

3、下载该版本的 zip 压缩包

4、导入 IDE 编辑器并且初始化

初始目录名称 cas-overlay-template-5.3 修改为 platform-cas-server

5、创建项目 src、resource、test 等目录

6、拷贝依赖 ./overlays/org.apereo.cas.cas-server-webapp-tomcat-5.3.16/WEB-INF/classes 下的部分文件目录到项目中

目录清单

./resources
├── services
│   └── ...
├── static
│   └── ...
├── templates
│   └── ...
├── webflow
│   ├── login
│   │   └── login-webflow.xml
│   └── logout
│       └── logout-webflow.xml
├── apereo.properties
├── application.properties
├── application.yml
├── bootstrap.properties
├── cas-theme-default.properties
├── log4j2.xml
├── messages.properties...
├── truststore.jks
└── user-details.properties

7、修改 pom.xml 文件

CAS 依赖和其他相关依赖

完整 maven 依赖通过 pom.xml 获取。

<dependencies>
        <dependency>
            <groupId>org.apereo.cas</groupId>
            <artifactId>cas-server-core</artifactId>
            <version>${cas.version}</version>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-devtools</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.apache.logging.log4j</groupId>
                    <artifactId>log4j-slf4j-impl</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <!-- 省略... -->
    </dependencies>

# 执行启动

CAS 基础环境已经搭建完成,我们开始启动 CAS 。

1、Spring Boot 启动类

1.1、CAS Spring Boot 启动 banner 实现

org.apereo.cas.web.util 目录包下面新建 CasEmbeddedContainerUtils

public final class CasEmbeddedContainerUtils {
    /**
     * Property to dictate to the environment whether embedded container is running CAS.
     */
    public static final String EMBEDDED_CONTAINER_CONFIG_ACTIVE = "CasEmbeddedContainerConfigurationActive";
    private static final Logger LOGGER = LoggerFactory.getLogger(CasEmbeddedContainerUtils.class);
    private CasEmbeddedContainerUtils() {
    }
    /**
     * Gets runtime properties.
     *
     * @param embeddedContainerActive the embedded container active
     * @return the runtime properties
     */
    public static Map<String, Object> getRuntimeProperties(final Boolean embeddedContainerActive) {
        final Map<String, Object> properties = new LinkedHashMap<>();
        properties.put(EMBEDDED_CONTAINER_CONFIG_ACTIVE, embeddedContainerActive);
        return properties;
    }
    /**
     * Gets cas banner instance.
     *
     * @return the cas banner instance
     */
    public static Banner getCasBannerInstance() {
        final String packageName = CasEmbeddedContainerUtils.class.getPackage().getName();
        final Reflections reflections =
                new Reflections(new ConfigurationBuilder()
                        .filterInputsBy(new FilterBuilder().includePackage(packageName))
                        .setUrls(ClasspathHelper.forPackage(packageName))
                        .setScanners(new SubTypesScanner(true)));
        final Set<Class<? extends AbstractCasBanner>> subTypes = reflections.getSubTypesOf(AbstractCasBanner.class);
        subTypes.remove(DefaultCasBanner.class);
        if (subTypes.isEmpty()) {
            return new DefaultCasBanner();
        }
        try {
            final Class<? extends AbstractCasBanner> clz = subTypes.iterator().next();
            LOGGER.debug("Created banner [{}]", clz);
            return clz.newInstance();
        } catch (final Exception e) {
            LOGGER.error(e.getMessage(), e);
        }
        return new DefaultCasBanner();
    }
}

1.2、Web 应用上下文

org.apereo.cas.web.web 目录下新建 CasWebApplicationContext

public class CasWebApplicationContext extends AnnotationConfigEmbeddedWebApplicationContext {
    /**
     * {@inheritDoc}
     * Reset the value resolver on the inner {@link ScheduledAnnotationBeanPostProcessor}
     * so that we can parse durations. This is due to how {@link org.springframework.scheduling.annotation.SchedulingConfiguration}
     * creates the processor and does not provide a way for one to inject a value resolver.
     */
    @Override
    protected void onRefresh() {
        final ScheduledAnnotationBeanPostProcessor sch = (ScheduledAnnotationBeanPostProcessor)
                getBeanFactory().getBean(TaskManagementConfigUtils.SCHEDULED_ANNOTATION_PROCESSOR_BEAN_NAME, BeanPostProcessor.class);
        sch.setEmbeddedValueResolver(new CasEmbeddedValueResolver(this));
        super.onRefresh();
    }
    @Override
    public String toString() {
        return getClass().getSimpleName();
    }
}

1.3、Cas Web 应用程序入口方法
org.apereo.cas.web.web 目录下新建 CasWebApplication

/**
 * This is {@link CasWebApplication}.
 *
 * @author Misagh Moayyed
 * @since 5.0.0
 */
@EnableDiscoveryClient
@SpringBootApplication(exclude = {
        HibernateJpaAutoConfiguration.class,
        JerseyAutoConfiguration.class,
        GroovyTemplateAutoConfiguration.class,
        JmxAutoConfiguration.class,
        DataSourceAutoConfiguration.class,
        RedisAutoConfiguration.class,
        MongoAutoConfiguration.class,
        MongoDataAutoConfiguration.class,
        CassandraAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class,
//        MetricsDropwizardAutoConfiguration.class,
        RedisRepositoriesAutoConfiguration.class
})
@EnableConfigurationProperties(CasConfigurationProperties.class)
@EnableAsync
@EnableTransactionManagement(proxyTargetClass = true)
@EnableScheduling
@NoArgsConstructor
@Slf4j
@ComponentScan(basePackages={"org.jasig.cas"})
public class CasWebApplication {
    /**
     * Main entry point of the CAS web application.
     *
     * Cas Web 应用程序入口方法
     * @param args the args
     */
    public static void main(final String[] args) {
        final Map<String, Object> properties = CasEmbeddedContainerUtils.getRuntimeProperties(Boolean.TRUE);
        final Banner banner = CasEmbeddedContainerUtils.getCasBannerInstance();
        new SpringApplicationBuilder(CasWebApplication.class)
                .banner(banner)
                .web(true)
                .properties(properties)
                .logStartupInfo(true)
                .contextClass(CasWebApplicationContext.class)
                .run(args);
    }
}

2、8080 端口启动

修改配置文件 application.properties 中 cas 使用 8080 端口启动

# server.port=8443
server.port=8080

注释 SSL 相关配置项

# server.ssl.key-store=file:/etc/cas/thekeystore
# server.ssl.key-store-password=changeit
# server.ssl.key-password=changeit

直接运行 CasWebApplication 中的 main 方法进行启动

启动成功后可直接访问 http://localhost:8080/cas/login ,默认账号密码:casuser / Mellon

完成登录

这样我们就已经完成 CAS 基本服务搭建。

这里提示我们的登录不是安全的,由于没有使用 HTTPS 协议,下面我们开始配置 HTTPS 支持。

# 配置证书

开始配置 HTTPS 支持,制作证书。

# Windows 环境

Windows 环境下使用 JDK 自带的工具 keytool 生成证书

1、生成密钥库文件

管理员命令行切换进入 JDK bin 目录 C:\Program Files\Java\jdk1.8.0_121\bin 后执行以下命令

keytool -genkeypair -alias cas -keyalg RSA -keypass changeit -storepass changeit -keystore thekeystore -dname "CN=cas.example.org,OU=Example,OU=Org,C=AU" -ext SAN="dns:example.org,dns:localhost,ip:127.0.0.1"

在当前目录生成密钥库文件 thekeystore

2、查看生成秘钥库的文件内容

输入密钥库口令: changeit

3、导出数字证书

继续执行以下命令根据密钥库文件导出证书

keytool -export -alias cas -file cas.cer -keystore thekeystore -validity 3650

输入先前的密钥库口令 changeit ,然后在当前目录下生成具体的 cas.cer 数字证书。

4、将数字证书导入到 JDK 中,其中证书密码为 changeit

keytool -import -alias cas -keystore "C:\Program Files\Java\jdk1.8.0_121\jre\lib\security\cacerts" -file cas.cer -trustcacerts -storepass changeit

命令执行后输:是 信任证书

至此证书制作并且导入完成。

# Linux 环境

keytool 工具生成密钥文件

keytool -genkey -alias tomcat -keyalg RSA -keystore /etc/cas/thekeystore

输入口令

输入口令:changeit

查看生成秘钥库的文件内容

keytool -list -keystore /etc/cas/thekeystore

根据 keystore 生成 crt 文件

keytool -export -alias tomcat -file /etc/cas/tomcat.cer -keystore /etc/cas/thekeystore -validity 3650

将信任授权文件添加到 jdk 中

sudo keytool -import -keystore /usr/java/jdk1.8.0_121/jre/lib/security/cacerts -file /etc/cas/tomcat.cer -alias tomcat -storepass changeit

# 配置 DNS

# Windows 环境

CAS 服务端是部署在本地的,需要做一个本地映射,Windows 需要使用管理员进行修改。

C:\Windows\System32\drivers\etc\hosts

添加映射地址

127.0.0.1 cas.example.org

# Linux 环境

添加 cas.example.org 到 hosts 文件中

echo '127.0.0.1 cas.example.org' >> /etc/hosts

# 配置 Tomcat

编辑 Tomcat 目录 conf 下的 server.xml 文件(如果部署 Tomcat 下的情况),我们使用 Spring Boot 部署则暂时不用。

<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"
	maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
	clientAuth="false" sslProtocol="TLS" 
	keystoreFile="C:\Program Files\Java\jdk1.8.0_121\bin\thekeystore"
	keystorePass="changeit" />

# 配置 SSL 启动项

1、配置 8443 端口启动

配置文件 application.properties 中 使用 8443 端口启动

server.port=8443

启用 SSL 相关配置项,注意 key-store 填写上文中具体生成路径

server.ssl.key-store=C:\\Program Files\\Java\\jdk1.8.0_121\\bin\\thekeystore
server.ssl.key-store-password=changeit
server.ssl.key-password=changeit

或者将刚才上面生成的 thekeystore 复制到项目的 /etc/cas/ 目录下,那么使用如下配置

server.ssl.key-store=file:/etc/cas/thekeystore
server.ssl.key-store-password=changeit
server.ssl.key-password=changeit

现在我们启动项目就可以直接使用 /etc/cas/ 下面的证书,除此之外,我们还可以将证书放在 ./resources 目录下面,每次打包就可以直接使用该证书。

server.ssl.key-store=classpath:thekeystore

2、启动 CAS 运行

https://localhost:8443/cas/login

localhost 方法访问,此时已经没有 SSL 警告,可以查看证书状况。

# 配置认证方式

接着我们继续更改 CAS 的认证配置,在 application.properties 配置文件中找到 CAS 默认用户名和密码配置位置。

##
# CAS Authentication Credentials
#
cas.authn.accept.users=casuser::Mellon

默认的认证方式为静态文件认证,现在我们更改为 JDBC 认证方式,同时添加相关数据库驱动。

<dependencies>
        <!-- 新增支持 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>
    </dependencies>

如果指定使用 MySQL 数据库,这里不推荐这个用法,具体使用什么数据库引入具体的相关驱动即可,因为引入综合包的同时也引入了许多不必要的包。

<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
            <scope>runtime</scope>
        </dependency>

我们继续更改 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://127.0.0.1:3306/cas?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=root
# 数据库用户密码
cas.authn.jdbc.query[0].password=123
# 数据库事务自动提交
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 不加密
cas.authn.jdbc.query[0].passwordEncoder.type=NONE
# cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
# cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=MD5

由于使用 JDBC 认证方式,测试需要准备一个 MySQL 数据库。本地新建了一个数据库 cas ,然后新建用户表 user ,添加 id、username、password、expired、disabled 字段,同时添加用户名和密码,并给 expired、disabled 字段赋值为 0。

CREATE DATABASE IF NOT EXISTS cas DEFAULT CHARACTER SET utf8;
CREATE TABLE `user`  (
  `id` bigint(20) NOT NULL COMMENT '主键',
  `username` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `password` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `expired` tinyint(4) NULL DEFAULT NULL,
  `disabled` tinyint(4) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;
INSERT INTO `user` VALUES (1, 'pitt1997', '123456', 0, 0);
INSERT INTO `user` VALUES (2, 'valieva', '12345678', 0, 0);
SET FOREIGN_KEY_CHECKS = 1;

数据库初始化完成后重启 CAS 再次登录。

到此,CAS 基础服务搭建就介绍完毕。