Central Authentication Service

目录

一、前言

1.SSO

2.Sso一个实现案例的图示

3.sso和第三方登录区别

4.全局会话

5.CAS——Central Authentication Service(中央认证服务)

特点

版本

下载

请求方式

二、CAS5.3服务端搭建——放tomcat(不导入idea)

1.环境准备

2.HTTP方式

3.HTTPS方式——搭建本地HTTPS服务

生成秘钥库和证书并把证书导入jdk证书库(这里生成证书的参数设置有误导致在第五步客户端访问服务端时出现问题(这里没有影响),正确的步骤请看第五步!)

关键——修改tomcat的server.xml配置

修改\webapps\cas\WEB-INF\classes目录下的application.properties文件

测试访问(不需要配置hosts文件)

4.HTTPS方式回退到HTTP方式

5.配置cas数据源——用户信息从Mysql取

准备数据库和用户表

​编辑

下载依赖包导入到lib目录

修改application.properties文件

启动测试

配置密码加密校验

启动测试

三、CAS5.3服务端搭建——导入idea(不放tomcat)

1.环境准备

2.HTTP方式

3.HTTP方式——配置cas数据源——用户信息从Mysql取(有些步骤之前已完成)

准备数据库和用户表

​编辑​

添加pom依赖

修改application.properties文件

build.cmd run启动测试

配置密码加密校验

build.cmd run启动测试

4.HTTPS方式——搭建本地HTTPS服务(有些步骤之前已完成)

[之前讲放tomcat且采用HTTPS方式时已经配置完成]-​生成秘钥库和证书并把证书导入jdk证书库(不过之前生成证书的参数设置有误导致在第五步客户端访问服务端时出现问题(这里没有影响),正确的步骤请看第五步!)

修改application.properties,cas.tgc.secure设为true​编辑

修改application.properties,放开注释并修改值(不修改FileNotFoundException:)

[之前讲解HTTP方式时已经配置完成]-配置数据源和密码加密

测试访问,OK

[配置失败!--忽略这一步]——IDEA中配置独立tomcat代替build.cmd run启动

四、服务端添加自定义代码(导入idea、https方式)

1.自定义加密算法

新建加密工具类​编辑

修改数据库明文密码123456为经上面加密算法加密后的密文

修改application.properties配置

修改log4j2.xml配置打印自己代码中日志

测试 

2.其它

五、创建客户端并测试SSO

1.生成秘钥库和证书并把证书导入jdk证书库(上面配置有误,这里是正确配置)

定义好认证中心域名,下面配置需要保持一致(正是之前生成证书时存在的问题)

生成秘钥库

基于秘钥库导出证书

把证书导入到JDK证书库

2.更新服务端的application.properties和hosts文件

3.创建一个空的Spring Boot项目并引入依赖

4.启动类添加注解

5.添加首页Controller

6.添加首页模板

7.勾选Allow parallel run,启动3个实例

yml中每个实例配置

8. 单点登录测试

9.单点登出

创建登出控制器

页面添加登出按钮

测试

六、服务端界面修改

1.favicon.ico的修改 

放Tomcat时

导入Idea时

2.登录页面的修改

默认页面

放Tomcat时

导入Idea时

启动查看效果

七、重要补充

八、Debug

九、参考文章


一、前言

1.SSO

SSO单点登录是把若干子系统的登录模块抽离整合到专门的认证系统,统一进行登录管理,主要实现的是:用户访问单个子系统,该子系统检测到未登录就会重定向到认证中心进行登录,登录成功后,用户在以后一个时间段内在同一个客户端(浏览器)访问其它子系统就不需要再登录;同时某一子系统退出登录时,认证中心也退出登录。

2.Sso一个实现案例的图示

f99a4bf1883f4142a6b9b224a8d8b0ea.png

3.sso和第三方登录区别

单点登录应用于同一公司的若干系统之间。

4.全局会话

全局会话是若干客户端通过浏览器和单一认证中心之间建立并保持的会话,当某一客户端重定向访问认证中心进行首次登录操作时,浏览器地址栏会由客户端的域名变为认证中心的域名,相当于浏览器发起了一次访问认证中心的请求并建立起全局会话,此时全局会话的JSESSIONID会自动地被浏览器保存在认证中心域名下的cookie中。后续其它客户端再在此台浏览器重定向访问认证中心时,由于浏览器地址栏请求的域名都是认证中心的域名,浏览器会自动带上该域名的cookie访问认证中心,由于cookie中jsessionid相同的,所以是一个会话,称为全局会话,实现了session共享。

浏览器会携带哪些cookie放到请求头呢?

从url获取域名(不含port)tieba.baidu.com,获取本地cookie列表中所有域名设置为tieba.baidu.com的cookie  以及  本地cookie列表中所有域名设置为tieba.baidu.com的父域名baidu.com的cookie。

d717f39fad1742fd9770e37cd27a6b7f.png

5.CAS——Central Authentication Service(中央认证服务)

CAS是耶鲁大学发起的一个开源项目,旨在为 Web 应用系统提供一种可靠的单点登录方法。

ff7d8a83351f8ba889084f6e94d44224.png

第5步的验证只需要拿到票据后的第一次请求需要进行(除非创建的局部会话和cookie失效了)。

特点

1、开源的企业级单点登录解决方案。

2、CAS Server 为需要独立部署的 Web 应用(独占一台web容器)。

3、CAS Client 支持非常多的客户端(这里指单点登录系统中的各个 Web 应用),包括 Java, .Net, PHP, Perl, Apache, uPortal, Ruby 等。

4、CAS属于Apache 2.0许可证(Apache2.0 开源协议对使用,复制,修改,商用不做过多限制,但必须包含原著的License信息),允许代码修改,再发布(作为开源或商业软件)。

版本

6.0 及以上需要jdk11,如果你是jdk8,最高只能用5.3版本

5.3 以下版本的是[maven]工程,6.0以上改成[gradle]工程了

有web版本、maven版本、gradle版本三种,本文演示的5.3是maven版本

社区实践表明,建议CAS 部署在至少8GB双核 3.00Ghz 的处理器上。如果日志保存在该服务器上,则还需要足够的磁盘空间(最好是SSD)来存放日志

下载

界面下载

GitHub - apereo/cas-overlay-template at 5.3

5e55edd63b1443af82ef94084e6d5cca.png

GIT下载

可以下载master,然后再进行切换

git clone https://github.com/apereo/cas-overlay-template.git

也可以只下载5.3版本的分支

git clone -b 5.3 https://github.com/apereo/cas-overlay-template.git

请求方式

CAS默认使用的是HTTPS协议,如果使用HTTPS协议需要SSL安全证书(需向特定的机构申请和购买),也可以自己生成证书搭建本地https服务 。在开发测试阶段可修改配置使用HTTP协议。
如果不去除https认证而使用http请求会提示未认证授权的服务:

0705b9556c25480aa85a8a050304c986.png

二、CAS5.3服务端搭建——放tomcat(不导入idea)

1.环境准备

cas server必须独立部署在一台web容器中(独占一个tomcat服务器),准备一个tomcat放在E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS目录下。

在E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS目录保存有从github下载的cas压缩包cas-overlay-template-5.3.zip,解压缩并进入解压后的文件夹,进入到E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS\cas-overlay-template-5.3\cas-overlay-template-5.3中,在此处执行cmd命令mvn clean package(使用builde.cmd run也可以),会生成target文件夹,内部含有cas.war文件。

复制cas.war到tomcat的webapps文件夹下并解压,解压完成后删除原压缩包。 

d7462b01f4ec4692aa38e3a2f7f0e664.png

配置CAS日志地址

找到E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS\apache-tomcat-9.0.34-windows-x64\apache-tomcat-9.0.34\webapps\cas\WEB-INF\classes下的log4j2.xml

aa5c024779af4d509a32f2ca36542ff4.png

在这里可以修改日志存储目录等配置,windows下不修改会在当前盘符下自动新建此目录

60641c9f2eb2410980514a11d5fac8b5.png

2.HTTP方式

修改\webapps\cas\WEB-INF\classes\services目录下的HTTPSandIMAPS-10000001.json文件,把原来的serviceId内容改成如下

"serviceId" : "^(https|http|imaps)://.*",

修改\webapps\cas\WEB-INF\classes目录下的application.properties文件,最后追加

#霍其旺新添加的内容
#false忽略https安全协议使用HTTP协议;true使用https安全协议;
cas.tgc.secure=false
#是否开启json识别功能,默认为false
cas.serviceRegistry.initFromJson=true

默认用户名密码配置也在application.properties文件中(密码大小写敏感)

# 可以修改登录用户名密码,默认casuser::Mellon
cas.authn.accept.users=casuser::Mellon

启动tomcat,访问http://localhost:8080/cas/login,登陆成功:

3ef564ca049e4b778388b60df6abc434.png

3.HTTPS方式——搭建本地HTTPS服务

生成秘钥库和证书并把证书导入jdk证书库(这里生成证书的参数设置有误导致在第五步客户端访问服务端时出现问题(这里没有影响),正确的步骤请看第五步!)

在第五步客户端访问服务端时出现的问题第五步不再作记录,这里记录一下出现的问题:

客户端:36bd6786c5d3400180438a309ca38a34.png

页面:

0caf925d020d4889a03a0c255a4d9530.png

1.生成秘钥库

cmd执行

keytool -genkey -alias skl -keyalg RSA -keystore E:/2kc-projectSets/1AllCompany/01Gener/gener010-CAS/secretkey
#-alias指定密钥库别名为skl
#-keyalg指定加密算法
#最后的路径的末尾的secretkey是将要生成的密钥库文件

-alias       产生别名
-keystore    指定密钥库的名称(产生的各类信息将不在.keystore文件中)
-keyalg      指定密钥的算法 (如 RSA  DSA(如果不指定默认采用DSA))
-validity    指定创建的证书有效期多少天
-keysize     指定密钥长度
-storepass   指定密钥库的密码(获取keystore信息所需的密码)
-keypass     指定别名条目的密码(私钥的密码)
-dname       指定证书拥有者信息 例如:  "CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=单位的两字母国家代码"
-list        显示密钥库中的证书信息      
-v           显示密钥库中的证书详细信息
-export      将别名指定的证书导出到文件  
-file        参数指定导出到文件的文件名
-delete      删除密钥库中某条目         
-printcert   查看导出的证书信息          
-keypasswd   修改密钥库中指定条目口令   
-storepasswd 修改keystore口令     
-import      将已签名数字证书导入密钥库  

5fe2613501df4ad28287001b384a034b.png

在E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS下生成secretkey秘钥库:

6ed9aff317e04d598359d56152eb61c7.png

2.基于秘钥库生成证书

cmd执行

keytool -export -trustcacerts -alias skl -file E:/2kc-projectSets/1AllCompany/01Gener/gener010-CAS/secretkey.cer -keystore E:/2kc-projectSets/1AllCompany/01Gener/gener010-CAS/secretkey
#前面的路径是要导出证书的位置和证书名称
#后面的路径是之前密钥库的路径,别名和秘钥库别名skl保持一致

5ec30ee5ddcb4d3db6b61166503b2073.png

在E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS下生成secretkey.cer证书:

36ed4417dd7f4d6a945aec53bccc198d.png

3.把证书导入到JDK证书库

cmd执行

keytool -import -trustcacerts -alias skl -file E:/2kc-projectSets/1AllCompany/01Gener/gener010-CAS/secretkey.cer -keystore "E:/1kc-devtools/jdk/jre/lib/security/cacerts"
#别名和前面的别名skl保持一致即可
#前面路径是前面生成的证书的位置,后面是个人jdk中cacerts的所在目录

be41a0f7f2934cc5b89a5ad8423b63c6.png

补充:若证书别名skl已存在会不能重复添加,如何删除jdk证书库的旧证书?

keytool -delete -alias skl -keystore E:/1kc-devtools/jdk/jre/lib/security/cacerts
#skl是已存在证书的别名,E:/1kc-devtools/jdk/jre/lib/security/cacerts是个人jdk的cacerts路径
#输入口令:changeit,回车,ok。

关键——修改tomcat的server.xml配置

在96行添加(配置的端口为https默认端口443,访问时可以省略不写如同http的80端口一样),注意配置的值是先前的秘钥库相关信息

<Connector port="443" protocol="org.apache.coyote.http11.Http11NioProtocol"
	   maxThreads="150" SSLEnabled="true" scheme="https" secure="true"
	   clientAuth="false" sslProtocol="TLS"
	   keystoreFile="E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS\secretkey" 
	   keystorePass="123456" /><!--秘钥库位置--><!--秘钥库口令-->

注释默认的8080端口(否则https://127.0.0.1:443/cas/login可以访问到的同时,http://127.0.0.1:8080/cas/login也可以访问到)

0bac98f5b5aa423ebc8461cef01c82e9.png

修改\webapps\cas\WEB-INF\classes目录下的application.properties文件

#设置为true
#false忽略https安全协议使用HTTP协议;true使用https安全协议;
cas.tgc.secure=true

测试访问(不需要配置hosts文件)

省略443端口,访问https://loaclhost/cas7d967ccdf1744830baef2d641caef900.png

点击继续前往,可以正常访问

30cdb09085524241aa6ebf09bed9a7ed.png

通过浏览器可以查看自己本地生成证书时设置的信息

15c74d3b057547ff9cf43dc34f438094.png

自己做的SSL证书也就是自签名SSL证书,而CA证书就是由正规的CA机构颁发的,两者大不相同自签名SSL证书是免费的不安全的,不受任何浏览器信任的;

配置hosts文件后重启tomcat也是可以正常访问的(域名最好和自己的证书别名对应起来)

e580a3bccb9a4070963c99c37c130ab1.png访问https://skl.com/cas/login、https://wzy1/cas/login都可以。

4.HTTPS方式回退到HTTP方式

修改tomcat的server.xml,放开http-8080,注释https-443。

不需要变动\webapps\cas\WEB-INF\classes目录下的application.properties文件中的配置,测试发现即便设置为true,http方式依然可以访问到

#false忽略https安全协议使用HTTP协议;true使用https安全协议;
cas.tgc.secure=true

5.配置cas数据源——用户信息从Mysql取

准备数据库和用户表

CREATE DATABASE IF NOT EXISTS `db_sso`;
DEFAULT CHARACTER SET utf8;

USE `db_sso`;
DROP TABLE IF EXISTS `t_cas`;
CREATE TABLE `t_cas` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(30) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
	`expired` BIGINT DEFAULT 0 COMMENT '是否过期,1为过期,需修改密码',
  `disabled` BIGINT DEFAULT 0 COMMENT '是否禁用,1为禁用',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert  into `t_cas`(`id`,`username`,`password`) values (1,'system','123456');

select @@autocommit;
show variables like '%timeout%';

密码先明文存储 

2af1434984a846d6ac56f8a1ea08dce9.png

下载依赖包导入到lib目录

https://mvnrepository.com/下载下列jar包,

6bbc89558750425ba9fa0f9540b663cc.png

添加到E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS\apache-tomcat-9.0.34-windows-x64\apache-tomcat-9.0.34\webapps\cas\WEB-INF\lib中。

修改application.properties文件

注释掉写死的用户名和密码配置,加上jdbc数据源配置,连接准备好的db_sso数据库查询t_cas表

# 可以修改登录用户名密码,默认casuser::Mellon
# 注释掉写死的认证用户
#cas.authn.accept.users=casuser::Mellon
# 加上jdbc数据源配置
cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/db_sso?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8
cas.authn.jdbc.query[0].user=root
cas.authn.jdbc.query[0].password=root
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect
#数据库事务自动提交,默认true自动提交
cas.authn.jdbc.query[0].autocommit=true
#查询结果必须包含密码字段
cas.authn.jdbc.query[0].sql=select * from t_cas where username=?
#用户表密码字段(必须)
cas.authn.jdbc.query[0].fieldPassword=password
#指定是否过期字段,1为过期,需要修改密码
cas.authn.jdbc.query[0].fieldExpired=expired
#指定是否禁用字段,1为禁用
cas.authn.jdbc.query[0].fieldDisabled=disabled

启动测试

以用户表的system  123456登录,登录成功

fdfa931ced684da2bba8b92180703c8a.png

expired字段设置为1,过期

c20bcc4c955c494e966752dd2fbee5fc.png

disabled字段设置为1,禁用

973db87321644aa190efeec494f4a1e9.png

配置密码加密校验

修改t_cas表密码为密文

SELECT SHA(123456);#7c4a8d09ca3762af61e59520943dc26494f8941b

408fb7770c614f2fa7306740b2859f44.png

修改application.properties文件,填加以下加密配置:

# 加密策略,默认NONE不加密
#NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT
# 字符类型
cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
# 加密算法(常用单向加密算法:MD5、SHA、HMAC)
cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=SHA
# 加密盐
#cas.authn.jdbc.query[0].passwordEncoder.secret=
# 加密字符长度
#cas.authn.jdbc.query[0].passwordEncoder.strength=16

启动测试

输入用户名system,密码依然输123456,依然可以成功登录,加密配置成功

2fcfdbaf2a9d4efdb48d6dac9ba8a13f.png

三、CAS5.3服务端搭建——导入idea(不放tomcat)

1.环境准备

在cas-overlay-template-5.3.zip解压缩后的文件夹中打开cmd,将文件夹中的build.cmd文件拖入cmd窗口,然后空格+run,回车[也可以用mvn clean package命令,都可以在target下生成war包,不同的是前者还可以启动项目后者只能打包],就会编译了,编译过程可能报错,无须理会

426b6b6ca59146139724e5f0408da704.png

编译完成会生成target文件夹,target文件夹下会生成cas.war

8dbd6329edd44340a6e82d103b1ac0f4.png

导入项目到idea

be926f20972f4c2aa0d007b8e40e3a90.png

4a6c7526573149d9b3492e791679df6e.png

导入后项目的初始结构

0b7e24c42c0d41e8ae9c4e7f31c8f25d.png

导入后Project structure中各配置项默认效果,无须改动

da390eaac5694ac1870b02ba65516934.png

2eb3a732826f4ae9b6ba9bbda0e68a69.png

5fd21f3f7a6d4222974c9d325d024851.png468565569384445a87baf264fad8d3f7.png

46c8e17c0a884af9b3f2e8f19b936c8b.png

5274b066ab714c9baad62bba5c226118.png

81d567a6c29f46fb824ae1338ef9bd5e.png

c3be7893b6b24a81b164e0b7a83e9154.png

db3b76de413944e69bd48cab054665f8.png

195bf835409e48da9239a4d867dee63b.png

修改jdk版本:File->Settings 

8ae0963b754744b5bcdc4ba3064635e5.png

创建src-main-java和src-main-resources两个文件夹

9c9c6942cf0845e58076da9d5bfb460f.png

使target文件夹也显示在项目结构中

6ef572b41a53490fba8a4ab435761b12.png

target/cas/WEB-INF/classes下servies文件夹和application.properties文件和log4j2.xml复制到resources文件夹下

3aad5256b8894fabbc9a14cf3f070506.png ==》6f643c9b2ec644638ac7c2733d463765.png

2.HTTP方式

由于我这里不用证书(不使用https),修改application.properties,将这三行注释掉

不注释会报错java.io.FileNotFoundException: \etc\cas\thekeystore (系统找不到指定的文件。)

520bae6f1e9e406f87c5007fb27e6056.png

继续修改application.properties,追加配置

#false,忽略https安全协议使用HTTP协议
cas.tgc.secure=false
#是否开启json识别功能,默认为false
cas.serviceRegistry.initFromJson=true
#加载json资源-第一次使用此配置-暂不清楚具体作用
cas.serviceRegistry.json.location=classpath:/services
#自动扫描服务配置,默认开启
#cas.serviceRegistry.watcherEnabled=true
#120秒扫描一遍
#cas.serviceRegistry.repeatInterval=120000
#延迟15秒开启
#cas.serviceRegistry.startDelay=15000

添加支持http请求

5422386300c4464fab5b302a62509591.png

在idea的Terminal面板执行build.cmd  run重新编译,访问:http://localhost:8443/cas,注意是8443,且cas不能省略,f5b72324256240a8a8699f8d726f1b11.png,输入默认的用户名/密码:casuser/Mellon,登录成功

d5a23f25f1a54101b43961241b5b39e5.png

注意不要用build.cmd bootrun(好像是gradle中的命令)启动,否则会报错。

输入http://wzy2:8443/cas也可以访问,可见hosts配置文件既适用http,也适用https

3.HTTP方式——配置cas数据源——用户信息从Mysql取(有些步骤之前已完成)

准备数据库和用户表

CREATE DATABASE IF NOT EXISTS `db_sso`;
DEFAULT CHARACTER SET utf8;

USE `db_sso`;
DROP TABLE IF EXISTS `t_cas`;
CREATE TABLE `t_cas` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(30) DEFAULT NULL,
  `password` varchar(100) DEFAULT NULL,
	`expired` BIGINT DEFAULT 0 COMMENT '是否过期,1为过期,需修改密码',
  `disabled` BIGINT DEFAULT 0 COMMENT '是否禁用,1为禁用',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert  into `t_cas`(`id`,`username`,`password`) values (1,'system','123456');

select @@autocommit;
show variables like '%timeout%';

密码先明文存储 

2af1434984a846d6ac56f8a1ea08dce9.png

添加pom依赖

添加完Reimport一下(pom文件中有的自带配置会报红暂不用理会

    <dependency>
        <groupId>org.apereo.cas</groupId>
        <artifactId>cas-server-support-jdbc</artifactId>
        <version>5.3.2</version>
    </dependency>
    <!--这个包不用单独引入,上面的包已经包含了这个包-->
    <!--<dependency>-->
        <!--<groupId>org.apereo.cas</groupId>-->
        <!--<artifactId>cas-server-support-jdbc-authentication</artifactId>-->
        <!--<version>5.3.2</version>-->
    <!--</dependency>-->
    <dependency>
        <groupId>org.apereo.cas</groupId>
        <artifactId>cas-server-support-jdbc-drivers</artifactId>
        <version>5.3.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.44</version>
    </dependency>

注意添加到的位置

2ecafaf2a3724dc9aebe147c9e55a7aa.png

补充:不停止build.cmd run的执行,去maven clean时会报错c08900b84afc408a9c4c1c827bfad32c.png

修改application.properties文件

注释掉写死的用户名和密码配置,加上jdbc数据源配置,连接准备好的db_sso数据库查询t_cas表

# 可以修改登录用户名密码,默认casuser::Mellon
# 注释掉写死的认证用户
#cas.authn.accept.users=casuser::Mellon
# 加上jdbc数据源配置
cas.authn.jdbc.query[0].url=jdbc:mysql://localhost:3306/db_sso?autoReconnect=true&useSSL=false&useUnicode=true&characterEncoding=UTF-8
cas.authn.jdbc.query[0].user=root
cas.authn.jdbc.query[0].password=root
cas.authn.jdbc.query[0].driverClass=com.mysql.jdbc.Driver
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect
#数据库事务自动提交,默认true自动提交
cas.authn.jdbc.query[0].autocommit=true
#查询结果必须包含密码字段
cas.authn.jdbc.query[0].sql=select * from t_cas where username=?
#用户表密码字段(必须)
cas.authn.jdbc.query[0].fieldPassword=password
#指定是否过期字段,1为过期,需要修改密码
cas.authn.jdbc.query[0].fieldExpired=expired
#指定是否禁用字段,1为禁用
cas.authn.jdbc.query[0].fieldDisabled=disabled

build.cmd run启动测试

以用户表的system  123456登录,登录成功

fdfa931ced684da2bba8b92180703c8a.png

expired字段设置为1,过期

c20bcc4c955c494e966752dd2fbee5fc.png

disabled字段设置为1,禁用

973db87321644aa190efeec494f4a1e9.png

配置密码加密校验

修改t_cas表密码为密文

SELECT SHA(123456);#7c4a8d09ca3762af61e59520943dc26494f8941b

408fb7770c614f2fa7306740b2859f44.png

修改application.properties文件,填加以下加密配置:

# 加密策略,默认NONE不加密
#NONE|DEFAULT|STANDARD|BCRYPT|SCRYPT|PBKDF2
cas.authn.jdbc.query[0].passwordEncoder.type=DEFAULT
# 字符类型
cas.authn.jdbc.query[0].passwordEncoder.characterEncoding=UTF-8
# 加密算法(常用单向加密算法:MD5、SHA、HMAC)
cas.authn.jdbc.query[0].passwordEncoder.encodingAlgorithm=SHA
# 加密盐
#cas.authn.jdbc.query[0].passwordEncoder.secret=
# 加密字符长度
#cas.authn.jdbc.query[0].passwordEncoder.strength=16

build.cmd run启动测试

输入用户名system,密码依然输123456,依然可以成功登录,加密配置成功

2fcfdbaf2a9d4efdb48d6dac9ba8a13f.png

4.HTTPS方式——搭建本地HTTPS服务(有些步骤之前已完成)

[之前讲放tomcat且采用HTTPS方式时已经配置完成]-​生成秘钥库和证书并把证书导入jdk证书库(不过之前生成证书的参数设置有误导致在第五步客户端访问服务端时出现问题(这里没有影响),正确的步骤请看第五步!

在第五步客户端访问服务端时出现的问题第五步不再作记录,这里记录一下出现的问题:

客户端:36bd6786c5d3400180438a309ca38a34.png

页面:

0caf925d020d4889a03a0c255a4d9530.png

修改application.properties,cas.tgc.secure设为true
f2c96b7678174672902f2ec781191934.png

修改application.properties,放开注释并修改值(不修改FileNotFoundException:)

de32caa471924ee495fdd6e34a33c3ac.png修改为

#上面生成的秘钥库保存位置
server.ssl.key-store=file:E:/2kc-projectSets/1AllCompany/01Gener/gener010-CAS/secretkey
#上面输入保存的口令
server.ssl.key-store-password=123456
#上面输入保存的口令
server.ssl.key-password=123456

[之前讲解HTTP方式时已经配置完成]-配置数据源和密码加密

(略)

测试访问,OK

访问http://localhost:8443/cas,提示Bad Request

db24f0913ea1411b881effa6f3288922.png

访问https://localhost:8443/cas,正常且system/123456登录成功

87302f6b09e9445d8cba414fc7779c77.png

[配置失败!--忽略这一步]——IDEA中配置独立tomcat代替build.cmd run启动

在IDEA开发阶段仍采用build.cmd run启动——发布时打成war包放到tomcat(tomcat可在linux后台启动)的webapps文件夹即可。

打成war包命令:mvn clean package   或   build.cmd run

四、服务端添加自定义代码(导入idea、https方式)

1.自定义加密算法

新建加密工具类887e7c914c6942eabaea977239a8f331.png

package com.kaytune.cas.encode;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.crypto.password.PasswordEncoder;
import sun.misc.BASE64Encoder;

import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

/**
 * @beLongProjecet: cas-overlay
 * @beLongPackage: com.kaytune.cas.encode
 * @author: sizzled
 * @createTime: 2022/12/02 15:10
 * @description: 自定义加密工具类
 * @version: v1.0
 */
public class MyPasswordEncoder implements PasswordEncoder {
  private final Logger log = LoggerFactory.getLogger(MyPasswordEncoder.class);
  //盐
  private static String salt = "hqw";

  @Override/*encode方法用于自定义加密逻辑,在matches方法中调用*/
  public String encode(CharSequence charSequence) {
    // charSequence为用户输入的用户密码
    String encodeStr = charSequence.toString();
    String newPW = encodeStr + salt;
    MessageDigest md5 = null;
    String result = "";
    try {
      md5 = MessageDigest.getInstance("MD5");//NoSuchAlgorithmException
      BASE64Encoder base64Encoder = new BASE64Encoder();
      result = base64Encoder.encode(md5.digest(newPW.getBytes(StandardCharsets.UTF_8))).toLowerCase();
    } catch (Exception e) {
      e.printStackTrace();
    }
    return result;
  }

  @Override
  public boolean matches(CharSequence charSequence, String str) {
    // charSequence用户输入的密码,str字符串为数据库中密码字段值
    // 判断密码为空,直接返回false
    if (StringUtils.isBlank(charSequence)) {
      return false;
    }
    //【调用上面的encode对请求密码进行MD5处理】
    String pass = this.encode(charSequence);
    /*请求密码为:123456 ,数据库密码为:0svsnofwzoqpkskisgtsfa==,加密后的请求密码为:0svsnofwzoqpkskisgtsfa==*/
    log.info("请求密码为:{} ,数据库密码为:{},加密后的请求密码为:{}", charSequence, str, pass);
    //比较密码是否相等
    return pass.equals(str);
  }
}

修改数据库明文密码123456为经上面加密算法加密后的密文

e747975572b1437cb7162b0cdd2ac1c1.png

修改application.properties配置

6c2297891e0146b5aa4e9b9e6f2220fb.png

fe4b2b8f636c46a78e143154436e07ce.png

修改log4j2.xml配置打印自己代码中日志

由于上面的加密工具类使用了

private final Logger log = LoggerFactory.getLogger(MyPasswordEncoder.class);
log.info("请求密码为:{} ,数据库密码为:{},加密后的请求密码为:{}", charSequence, str, pass);

所以需要添加下面三个配置到log4j2.xml的指定位置,注意最后一个配置的name

<!--自定义日志-->
<RollingFile name="myLog" fileName="${baseDir}/myLog.log" append="true"
             filePattern="${baseDir}/myLog-%d{yyyy-MM-dd-HH}-%i.log">
    <PatternLayout pattern="%highlight{%d %p [%c] - &lt;%m&gt;}%n"/>
    <Policies>
        <OnStartupTriggeringPolicy />
        <SizeBasedTriggeringPolicy size="10 MB"/>
        <TimeBasedTriggeringPolicy />
    </Policies>
    <DefaultRolloverStrategy max="5" compressionLevel="9">
        <Delete basePath="${baseDir}" maxDepth="2">
            <IfFileName glob="*/*.log.gz" />
            <IfLastModified age="7d" />
        </Delete>
    </DefaultRolloverStrategy>
</RollingFile>

<!--自定义日志-->
<CasAppender name="casMyLog">
    <AppenderRef ref="myLog" />
</CasAppender>

<!--自定义日志-->
<AsyncLogger name="com.kaytune.cas.encode" level="info" additivity="false" includeLocation="true">
    <AppenderRef ref="casMyLog"/><!--打印到文件-->
    <AppenderRef ref="casConsole"/><!--打印到控制台-->
</AsyncLogger>

测试 

build.cmd run脚本启动程序,system/123456登录成功,system/123456789登录失败。

2.其它

(自定义登录验证、白名单黑名单等)

五、创建客户端并测试SSO

1.生成秘钥库和证书并把证书导入jdk证书库(上面配置有误,这里是正确配置)

定义好认证中心域名,下面配置需要保持一致(正是之前生成证书时存在的问题)

自定义为:casserver.com

生成秘钥库

cmd执行

keytool -genkey -alias casserver -keyalg RSA -keystore E:/2kc-projectSets/1AllCompany/01Gener/gener010-CAS/casserver.keystore
#-alias指定密钥库别名为casserver
#-keyalg指定加密算法
#末尾的casserver.keystore是将要生成的密钥库文件

-alias       产生别名
-keystore    指定密钥库的名称(产生的各类信息将不在.keystore文件中)
-keyalg      指定密钥的算法 (如 RSA  DSA(如果不指定默认采用DSA))
-validity    指定创建的证书有效期多少天
-keysize     指定密钥长度
-storepass   指定密钥库的密码(获取keystore信息所需的密码)
-keypass     指定别名条目的密码(私钥的密码)
-dname       指定证书拥有者信息 例如:  "CN=名字与姓氏,OU=组织单位名称,O=组织名称,L=城市或区域名称,ST=州或省份名称,C=单位的两字母国家代码"
-list        显示密钥库中的证书信息      
-v           显示密钥库中的证书详细信息
-export      将别名指定的证书导出到文件  
-file        参数指定导出到文件的文件名
-delete      删除密钥库中某条目         
-printcert   查看导出的证书信息          
-keypasswd   修改密钥库中指定条目口令   
-storepasswd 修改keystore口令     
-import      将已签名数字证书导入密钥库

458849aec9db486084e0677a9166c9a6.png

在E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS下生成casserver.keystore秘钥库:

d763216a1312436ab4ab9094de6f60d7.png

基于秘钥库导出证书

cmd执行

keytool -export -trustcacerts -alias casserver -file E:/2kc-projectSets/1AllCompany/01Gener/gener010-CAS/casserver.cer -keystore E:/2kc-projectSets/1AllCompany/01Gener/gener010-CAS/casserver.keystore
#前面的路径是要导出证书的位置和证书名称
#后面的路径是之前密钥库的路径,别名和秘钥库别名casserver保持一致

285b7a3323a947dea49881c7ea2c6e8f.png

 在E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS下生成了casserver.cer证书:

3fe1e4358acc47e9b8049d7acd3679bb.png

把证书导入到JDK证书库

cmd执行

keytool -import -trustcacerts -alias casserver -file E:/2kc-projectSets/1AllCompany/01Gener/gener010-CAS/casserver.cer -keystore "E:/1kc-devtools/jdk/jre/lib/security/cacerts"
#别名和前面的别名casserver保持一致即可
#前面路径是前面生成的证书的位置,后面是个人jdk中cacerts的所在目录

ac69aa8aa6d74ebdbbffed06d89bd77d.png

补充:若证书别名casserver已存在会不能重复添加,如何删除jdk证书库的旧证书?

keytool -delete -alias casserver -keystore E:/1kc-devtools/jdk/jre/lib/security/cacerts
#casserver是已存在证书的别名,E:/1kc-devtools/jdk/jre/lib/security/cacerts是个人jdk的cacerts路径
#输入口令:changeit,回车,ok。

2.更新服务端的application.properties和hosts文件

1bc95ece536c4a32936cf894df069359.png

9d30318b71e148fd8ae4ed6861ec1737.png

注意:hosts文件不支持配置端口号。

3.创建一个空的Spring Boot项目并引入依赖

<!--构建cas客户端 start-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <scope>runtime</scope>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<!--<dependency>cas-client-autoconfig-support中包含的有
    <groupId>org.jasig.cas.client</groupId>
    <artifactId>cas-client-core</artifactId>
    <version>3.6.1</version>
</dependency>-->
<dependency>
    <groupId>net.unicon.cas</groupId>
    <artifactId>cas-client-autoconfig-support</artifactId><!--是核心功能,包含认证和校验的过滤器-->
    <version>2.3.0-GA</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
    <version>2.6.7</version>
</dependency>
<!--构建cas客户端 end-->

4.启动类添加注解

@EnableCasClient

5.添加首页Controller

package com.kaytune.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

/**
 * @beLongProjecet: cas-client
 * @beLongPackage: com.kaytune.controller
 * @author: sizzled
 * @createTime: 2022/12/04 21:11
 * @description:
 * @version: v1.0
 */
@Controller
public class IndexController {
  @RequestMapping("/index")
  public ModelAndView index(){
    ModelAndView model = new ModelAndView();
    model.setViewName("index");
    return model;
  }
}

6.添加首页模板

7ca3cc83d1da4702a593ff37e32a941f.png

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>cas_client</title>
</head>
<body>
欢迎:[<h3 th:text="${session._const_cas_assertion_.principal.name}"></h3>]</br>
cas_client
</body>
</html>

${session._const_cas_assertion_.principal.name} 登录用户名

d7f20fc2a6ef4a7da5b3256a7548b970.png

7.勾选Allow parallel run,启动3个实例

一个服务多个端口运行(Allow parallel run) - 周文豪 - 博客园

a6f24082627d4c2b945f4bbb7aaac5fa.png

yml中每个实例配置

f4bc9e6999a24ff79902ed1966c768f0.png

否则报错:

36bd6786c5d3400180438a309ca38a34.png

# 实例1
cas:
  server-url-prefix: https://casserver.com:8443/cas
  server-login-url: https://casserver.com:8443/cas/login
  client-host-url: http://localhost:8881
  validation-type: cas3
server:
  port: 8881

## 实例2
#cas:
#  server-url-prefix: https://casserver.com:8443/cas
#  server-login-url: https://casserver.com:8443/cas/login
#  client-host-url: http://localhost:8882/
#  validation-type: cas3
#server:
#  port: 8882

## 实例3
#cas:
#  server-url-prefix: https://casserver.com:8443/cas
#  server-login-url: https://casserver.com:8443/cas/login
#  client-host-url: http://localhost:8883/
#  validation-type: cas3
#server:
#  port: 8883

8. 单点登录测试

启动服务端和三个客户端

访问客户端1:http://localhost:8881/index

跳转登录界面,输入system/123456

37cd1bf5adae44ee990b3cfcaed99a6c.png

 点击登录按钮,登录成功

ff517e1c3fd54e19b4f3022b89f6e8be.png

访问客户端2:http://localhost:8882/index,不用登录

访问客户端3:http://localhost:8883/index,不用登录

访问服务端:https://casserver.com:8443/cas,不用登录,点击登出再次访问客户端需要重新登录

9.单点登出

创建登出控制器

注销本地会话,重定向到服务端的/cas/logout接口统一登出

e04568bd0e3e406fb1889cda1e63d0ac.png

package com.kaytune.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

/**
 * @beLongProjecet: cas-client
 * @beLongPackage: com.kaytune.controller
 * @author: sizzled
 * @createTime: 2022/12/06 14:33
 * @description: 登出
 * @version: v1.0
 */
@Controller
public class LogoutController {
  @RequestMapping("logout")
  public String logout(HttpSession httpSession){
    //注销本地会话
    httpSession.invalidate();
    //重定向
    return "redirect:https://casserver.com:8443/cas/logout";
  }
}

页面添加登出按钮

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>cas_client</title>
</head>
<body>
欢迎:[<h3 th:text="${session._const_cas_assertion_.principal.name}"></h3>]</br>
cas_client
<hr><a href="/logout">统一退出登录</a>
</body>
</html>

测试

启动服务端和三个客户端,分别访问三个客户端http://localhost:8881、http://localhost:8882、http://localhost:8883

f0dc13e4b950436ca2f439985f54b379.png

在http://localhost:8881页面点击退出

4261a508d8a7445a8ca254f7e94595ed.png

刷新http://localhost:8882、http://localhost:8883页面需要重新登录,测试成功

六、服务端界面修改

1.favicon.ico的修改 

放Tomcat时

E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS\apache-tomcat-9.0.34-windows-x64\apache-tomcat-9.0.34\webapps\cas\WEB-INF\classes\static下:备份默认的放入新的

5e27a4bef80541c9ad3d775d41988c8c.png7e1c32b5a618460688564a45597d01e0.png

导入Idea时

新建static文件夹,放入favicon.ico,退出脚本运行,执行mvn clean重新编译,脚本启动

dbe18452f607499fb1fd86d85b2d47d2.png2e93c735152547359383e382e7f51c45.png

2.登录页面的修改

默认页面

6ab353737787485cba012241bc6c5031.png

放Tomcat时

11_CAS Server界面修改_哔哩哔哩_bilibili

在E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS\apache-tomcat-9.0.34-windows-x64\apache-tomcat-9.0.34\webapps\cas\WEB-INF\classes\templates下修改,为了便于修改,在idea中打开E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS\apache-tomcat-9.0.34-windows-x64\apache-tomcat-9.0.34\webapps\cas\WEB-INF\classes

85bcd5e50d9a43eea3bfbc297cc0fa89.png

修改templates\layout.html:使登录页面和登出成功页面去掉默认的头部和尾部

df13866eb5d14637a70361dd464edced.png

修改templates\casLoginView.html:使登录页面去掉右侧的内容

fb484119d10e491182b44d68c41d01e2.png

修改templates\fragments\loginsidebar.html:更改表单下登出按钮所在的提示信息

2a98e164c24b432c908c2295aca8032e.png

可以直接注释上面的自己添加下面的代码,也可以不添加下面的代码在classes\messages_zh_CN.properties中修改screen.welcome.security的属性值

#screen.welcome.security=\u51FA\u4E8E\u5B89\u5168\u8003\u8651\uFF0C\u4E00\u65E6\u60A8\u8BBF\u95EE\u8FC7\u90A3\u4E9B\u9700\u8981\u60A8\u63D0\u4F9B\u51ED\u8BC1\u4FE1\u606F\u7684\u5E94\u7528\u65F6\uFF0C\u8BF7\u64CD\u4F5C\u5B8C\u6210\u4E4B\u540E<a href="logout">\u767B\u51FA</a>\u5E76\u5173\u95ED\u6D4F\u89C8\u5668\u3002
screen.welcome.security=\u51fa\u4e8e\u5b89\u5168\u8003\u8651\uff0c\u005b\u52a0\u4e0a\u4e00\u6bb5\u5c41\u8bdd\u005d\uff0c\u4e00\u65e6\u60a8\u8bbf\u95ee\u8fc7\u90a3\u4e9b\u9700\u8981\u60a8\u63d0\u4f9b\u51ed\u8bc1\u4fe1\u606f\u7684\u5e94\u7528\u65f6\uff0c\u8bf7\u64cd\u4f5c\u5b8c\u6210\u4e4b\u540e\u003c\u0061\u0020\u0068\u0072\u0065\u0066\u003d\u0022\u006c\u006f\u0067\u006f\u0075\u0074\u0022\u003e\u767b\u51fa\u003c\u002f\u0061\u003e\u5e76\u5173\u95ed\u6d4f\u89c8\u5668\u3002screen.welcome.instructions=\u8bf7\u8f93\u5165\u60a8\u7684\u7528\u6237\u540d\u548c\u5bc6\u7801.

把上面默认的属性值unicode编码转为中文,修改成想要的中文,再把中文转为unicode编码

在登录页表单的右边设置图片

往static\images中放入一张图片,在static\css\cas.css中添加

#notices .cs {
  height: 600px;
  width: 540px;
  background-image: url("../images/美罗大厦.jpg");
}

在templates\casLoginView.html中添加

6053ba79fd7b4a999553589d5a2a8693.png

导入Idea时

把上面演示的放Tomcat时登录页面的修改,在idea中打开的E:\2kc-projectSets\1AllCompany\01Gener\gener010-CAS\apache-tomcat-9.0.34-windows-x64\apache-tomcat-9.0.34\webapps\cas\WEB-INF\classes文件夹下的已经改好的相关文件夹粘贴到导入Idea方式的工程即可

f2dc8a3c69554071ba045c050a7b7703.png

启动查看效果

4acfbabe89a449eeb0c7fa9b2703575f.png

49da3192e3464aa09f8e0c0b25154cfa.png

七、重要补充

  1. cas服务端的build目录没啥用不用提交到代码库中,这个文件夹就算删了每次运行都会自动重新生成。
  2. 自定义的log4j2.xml文件就放在resources目录下,移动并覆盖etc目录下的同名文件的话会导致自定义的日志配置无效。
  3. 测试发现etc目录可以删除对功能无影响;测试发现maven目录也可以删除对功能无影响。测试发现overlays目录也可以删除对功能无影响。最后的目录结构是这样功能也正常:
  4. 下面两个pom默认自带的地方可以注释掉。
  5. 要想在cas服务端新建controller和拦截器并使之生效。需要一些配置,第一步复制overlays\org.apereo.cas.cas-server-webapp-tomcat-5.3.16\WEB-INF\classes\META-INF\spring.factories中的META-INF目录到resources目录下,在spring.factories中追加:第二步新建SpringConfig.java配置类用于扫描包文件注册到spring容器:
  6. 在cas5.3服务端配置拦截器:尽量采用implements WebMvcConfigurer方式,采用/*extends WebMvcConfigurationSupport*/方式容易导致静态资源被拦截且在配置静态资源不拦截时容易出错。
  7. cas5.3的源码已经引入了springboot不需要再引入,不需要再被含boot依赖的父模块管理了。
  8.   /*多属性返回,大坑:返回的时候,json里一定要跟自定义认证策略里的map值一致*/
      ,"attributeReleasePolicy" : {
        /*全部返回*/
        "@class": "org.apereo.cas.services.ReturnAllAttributeReleasePolicy",
    
        /*只返回指定属性*/
        /*"@class" : "org.apereo.cas.services.ReturnAllowedAttributeReleasePolicy",
        "allowedAttributes" : [ "java.util.ArrayList", [ "username", "password" ] ]*/
      }

    ————————————————
    版权声明:本文为CSDN博主「张童瑶」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/u014641168/article/details/120906539

  9. 在resources/services目录下的.json文件中,logoutType:分为FRONT_CHANNEL,BACK_CHANNEL,其中FRONT_CHANNEL:显式登出.cas直接发送http post请求到已认证服务; BACK_CHANNEL:隐式登出.cas发送异步ajax get请求到已认证服务。

八、Debug

九、参考文章

CAS单点登录_Please Sit Down的博客-CSDN博客_cas单点登录CAS单点登录(三)——多种认证方式 - wjj1013 - 博客园

搭建CAS服务(导入IDEA)_KGF886的博客-CSDN博客

自定义登录验证:

CAS+Springboot单点登录 - 腾讯云开发者社区-腾讯云

cas单点登录-自定义登录验证(四)_weixin_42073629的博客-CSDN博客_cas自定义登录验证

19.SpringSecurity集成CAS_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值