authentication
①②③④⑤⑥⑦⑧⑨⑩⑪⑫⑬⑭⑮⑯⑰⑱⑲⑳✕✓✔✖
在整个认证链中,会有很多个认证。
英文名称 | 名称 | 说明 |
---|---|---|
All | 全部认证通过后才可以 | 例如必须在登录后,然后输入支付密码才通过。 |
Any | 其中一个通过后,就可以 | 最常用的登录方法,密码登录、短信登录、二维码登录 |
Global | 通过发放的验证票进行认证 | 与 JWT 的 Token 模式类似 |
Groovy | 自定义 Groovy 脚本来认证 | |
Not Prevented | 没有出现 PreventedException | |
Required | 只有指定的身份认证通过后才成功 | |
Rest | 访问一个 Rest 接口,进行认证 | |
Source Selection | 根据凭证源来选择认证策略 | 例如 Spring 中的 Token 类型 |
Unique Principal | 防止多次登录 | 开启后会增加系统负担 |
可以修改application.yml
来改变默认的登录用户名与密码
cas:authn:accept:users: user::aaa
这种认证方法是不被推荐的
CAS 默认提供 4 中方法
# 可以登录msyql中创建一个数据库cascd /home/fan/01-java/wukong-framework/wukong-mall/ref/docker/basic# 登录到mysqldocker-compose exec mysql mysql -uroot -prootmysql# 登录到容器docker-compose exec mysql /bin/bash
参考了 keycloak 创建的表
DROP TABLE IF EXISTS USER_ENTITY;create table USER_ENTITY(USER_ID bigint unsigned auto_increment comment '用户ID' ,EMAIL varchar(255) comment '邮箱' ,EMAIL_VERIFIED boolean default false not null comment '邮箱是否认证通过' ,ENABLED boolean default false not null comment '是否可用',FIRST_NAME varchar(255) comment '姓氏:现在没有用',LAST_NAME varchar(255) comment '名称:现在没有用',REALM_ID varchar(255) comment '领域编号:现在没有用',USERNAME varchar(255) comment '用户名',mobile_phone varchar(30) comment '手机号',EMAIL_VERIFIED_VERIFIED boolean default false not null comment '手机号是否认证通过',gmt_create datetime default CURRENT_TIMESTAMP comment '记录创建时间',gmt_modified datetime default CURRENT_TIMESTAMP comment '记录修改时间',PRIMARY KEY (USER_ID),-- 今后启用了realm_id后,再加唯一索引UNIQUE KEY `uq_USER_ENTITY_USERNAME` (USERNAME,REALM_ID),UNIQUE KEY `uq_USER_ENTITY_EMAIL` (EMAIL),UNIQUE KEY `uq_USER_ENTITY_mobile_phone` (mobile_phone)) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 COMMENT='用户表';-- +++++++++++++++++++++++++++++++++++++++++++++++++++DROP TABLE IF EXISTS CREDENTIAL;create table CREDENTIAL(CREDENTIAL_ID bigint unsigned auto_increment comment '凭证ID' ,USER_ID bigint unsigned comment '用户ID' ,TYPE varchar(255) comment '类型:password,' ,USER_LABEL varchar(255) comment '用户标签' ,SECRET_DATA varchar(512) comment '密码,这点与keycloak不一样' ,PRIORITY int comment '优先级' ,gmt_create datetime default CURRENT_TIMESTAMP comment '记录创建时间',gmt_modified datetime default CURRENT_TIMESTAMP comment '记录修改时间',PRIMARY KEY (CREDENTIAL_ID)) ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8 COMMENT='密码表';create index IDX_USER_CREDENTIALon CREDENTIAL (USER_ID);
添加模拟数据
delete from USER_ENTITY where USER_ID=10000;delete from CREDENTIAL where USER_ID=10000;INSERT INTO USER_ENTITY (USER_ID,USERNAME, ENABLED) VALUES (10000, 'user', 1);INSERT INTO CREDENTIAL (CREDENTIAL_ID, USER_ID, TYPE, SECRET_DATA) VALUES (10000, 10000, 'password', md5('aaa'));INSERT INTO USER_ENTITY (USER_ID,USERNAME, ENABLED) VALUES (10001, 'xiaoyu', 1);INSERT INTO CREDENTIAL (CREDENTIAL_ID, USER_ID, TYPE, SECRET_DATA) VALUES (10001, 10001, 'password', md5('aaa'));
主要是为了配置 Mysql 的驱动。具体做法如下
my.gradle
build.gradle
文件中的适当位置添加apply from: rootProject.file("gradle/my.gradle")
下面是my.gradle
文件的内容
dependencies {implementation "org.apereo.cas:cas-server-support-jdbc"testImplementation group: 'junit', name: 'junit', version: '4.12'}
网上有网友说要添加下面依赖是不对的。
// 这两个依赖不用添加,网上的例子有问题。当然加了也不会出现错误。implementation "org.apereo.cas:cas-server-support-jdbc-drivers"implementation "mysql:mysql-connector-java"
主要掉原先的内容
cas:authn:# 屏蔽默认的不能访问accept:enabled: falsejdbc:query:- driverClass: com.mysql.cj.jdbc.Driveruser: rootpassword: rootmysqlurl: jdbc:mysql://localhost:33061/cas?useSSL=false&serverTimezone=Asia/ShanghaifieldPassword: SECRET_DATApasswordEncoder:characterEncoding: UTF-8encodingAlgorithm: 'MD5'type: DEFAULTsql: select b.SECRET_DATA from USER_ENTITY a, CREDENTIAL b where a.USER_ID=b.USER_ID and b.TYPE='password' and a.ENABLED=1 and a.USERNAME=?
./gradlew debug
remote
,并点击 debug官方文档中有说明,但是将的不细致,其中:type
与encodingAlgorithm
的关系不明确。
如果了解加密的各种算法,就理解。这里必须指定 type,不同 type 下有不同的算法。
如果想了解详细的内容,可以看PasswordEncoderUtils
源代码。
passwordEncoder:#encodingAlgorithm: 'MD5'type: NONE
然后在数据库中输入明文的密码,就可以做实验了。
这个是 Spring 中的一个加密类型,但是不是 Spring 默认的,这个加密类型有三种算法,如果不指定,就出现错误。
passwordEncoder:encodingAlgorithm: PBKDF2WithHmacSHA256type: NONE
然后在数据库中输入密码ac35781caf7bba3a9373267c7e3cd69164209097d5bbb34577301b7f81cdb4a0be4dd633245c001b
。
然后在登录窗口使用123456
进行登录。
这个可以 Sping 默认的,这种算法的加密比较单一,所以可以不用设置encodingAlgorithm
passwordEncoder:#encodingAlgorithm: 'MD5'type: BCRYPT
然后在数据库中输入密码$2a$10$XJJqb2pPHD6EmJ6.pUuG5eYts8Kiyjrgh7NgZgMxnkXOb6sKpmaRC
。
然后在登录窗口使用aaa
进行登录。
Spring 是可以通过DelegatingPasswordEncoder
来实现多种密码模式的。但是这个函数没有不带参数的构造函数,所以不能在 CAS 的配置文件中设置,那么可以自定义一个 Java 类
passwordEncoder:#encodingAlgorithm: 'MD5'type: org.apereo.cas.wukong.crypto.password.MyEncoder
然后在数据库中输入下面不同的密码:
{bcrypt}$2a$10$XJJqb2pPHD6EmJ6.pUuG5eYts8Kiyjrgh7NgZgMxnkXOb6sKpmaRC
{noop}aaa
然后在登录窗口使用aaa
进行登录。作为一个专业的加密工具,可以推荐使用这个方法。下面给出具体的代码
/*** 可以支持多种加密模式,从Spring中改造过来*/public class MyEncoder implements PasswordEncoder {private PasswordEncoder passwordEncoder;public MyEncoder(){passwordEncoder= PasswordEncoderFactories.createDelegatingPasswordEncoder();}@Overridepublic String encode(CharSequence rawPassword) {return passwordEncoder.encode(rawPassword);}@Overridepublic boolean matches(CharSequence rawPassword, String encodedPassword) {return passwordEncoder.matches(rawPassword,encodedPassword);}@Overridepublic boolean upgradeEncoding(String prefixEncodedPassword) {return passwordEncoder.upgradeEncoding(prefixEncodedPassword);}}
一个新的做法,还没用过,可以尝试一下。
如何执行 groovy 代码呢,查一下源代码
if (type.endsWith(".groovy")) {LOGGER.trace("Creating Groovy-based password encoder at [{}]", type);val resource = applicationContext.getResource(type);return new GroovyPasswordEncoder(resource, applicationContext);}
这里定义了一个 groovy 文件,发现 idea 很好用,可以在任意地方设置断点。
package org.apereo.cas.wukong.crypto.passwordimport org.springframework.security.crypto.password.Pbkdf2PasswordEncoder;byte[] run(final Object... args) {String rawPassword = args[0]def generatedSalt = args[1]def logger = args[2]def casApplicationContext = args[3]logger.debug("Encoding password...")Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = new Pbkdf2PasswordEncoder();String pbk1 = pbkdf2PasswordEncoder.encode(rawPassword);return pbk1.getBytes();}Boolean matches(final Object... args) {def rawPassword = args[0]def encodedPassword = args[1]def logger = args[2]def casApplicationContext = args[3]logger.debug(rawPassword);logger.debug("Does match or not ?");Pbkdf2PasswordEncoder pbkdf2PasswordEncoder = new Pbkdf2PasswordEncoder();boolean ren =pbkdf2PasswordEncoder.matches(rawPassword,encodedPassword)return ren}
yml 文件中这么来配置
passwordEncoder:#encodingAlgorithm: 'MD5'type: file:///home/fan/01-java/cas/cas-overelay/src/main/java/org/apereo/cas/wukong/crypto/password/Script.groovy
这里使用PBKDF2
的密码算法,可以在上面代码中,找到一个加密的代码,然后实验一下就可以了。
但是总感觉这样不安全。
当然路径也可以放在 resource 文件下,例如
classpath:/resources/script/Script.groovy
总体任务可以分为以下几类:
这里可以参考一下:QueryDatabaseAuthenticationHandler
https://blog.csdn.net/anumbrella/category_7765386.html
implementation "org.apereo.cas:cas-server-core-authentication-api"
这里继承了一个类AbstractUsernamePasswordAuthenticationHandler
,当然也可以继承其他不用实现类。
这个类有两个重要方法
public class MyAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler{private UserDetailService userDetailService;protected MyAuthenticationHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order, UserDetailService userDetailService) {super(name, servicesManager, principalFactory, order);this.userDetailService=userDetailService;}@Overrideprotected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(UsernamePasswordCredential credential, String originalPassword) throws GeneralSecurityException, PreventedException {String username=credential.getUsername();String password=credential.getPassword();LOGGER.debug(username+":"+password);LOGGER.debug(originalPassword);if(userDetailService.loadByUsername(username)){//可自定义返回给客户端的多个属性信息HashMap<String, List<Object>> returnInfo = new HashMap<>();List<Object> theseElements = Lists.newArrayList("alpha", "beta", "gamma");returnInfo.put("theseElements", theseElements);final List<MessageDescriptor> list = new ArrayList<>();return createHandlerResult(credential,this.principalFactory.createPrincipal(username, returnInfo), list);}if(username.equals("user")){throw new PreventedException("a throws");}throw new FailedLoginException(username + " my customer exception");}}
后面演示了如果抛出异常:PreventedException
与FailedLoginException
定义一个类,用来注册认证程序
@Configuration("MyAuthenticationEventExecutionPlanConfiguration")@EnableConfigurationProperties(CasConfigurationProperties.class)public class MyAuthenticationEventExecutionPlanConfigurationimplements AuthenticationEventExecutionPlanConfigurer {@Autowiredprivate CasConfigurationProperties casProperties;@Autowired@Qualifier("servicesManager")private ServicesManager servicesManager;@Beanpublic AuthenticationHandler myAuthenticationHandler() {var handler = new MyAuthenticationHandler("MyAuthenticationEventExecutionPlanConfiguration",servicesManager,new DefaultPrincipalFactory(),1,new UserDetailService());return handler;}@Overridepublic void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) throws Exception {plan.registerAuthenticationHandler(myAuthenticationHandler());}}
可以添加多个
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.apereo.cas.config.CasOverlayOverrideConfiguration,org.apereo.cas.wukong.authentication.MyAuthenticationEventExecutionPlanConfiguration
输入用户名xiaoyu
,密码随便输入。
系统中默认有两个认证:
系统中只要有一个认证通过,就可以通过。
在调试的过程中发现没有输出 Sql 语句,并且启动项目时,会有一些警告。
WARN [org.apereo.cas.config.CasCoreServicesConfiguration] - <Runtime memory is used as the persistence storage for retrieving and persisting service definitions. Changes that are made to service definitions during runtime WILL be LOST when the CAS server is restarted. Ideally for production, you should choose a storage option (JSON, JDBC, MongoDb, etc) to track service definitions.>WARN [org.apereo.cas.config.CasCoreTicketsConfiguration] - <Runtime memory is used as the persistence storage for retrieving and managing tickets. Tickets that are issued during runtime will be LOST when the web server is restarted. This MAY impact SSO functionality.>WARN [org.apereo.cas.util.cipher.BaseStringCipherExecutor] - <Secret key for encryption is not defined for [Ticket-granting Cookie]; CAS will attempt to auto-generate the encryption key>WARN [org.apereo.cas.util.cipher.BaseStringCipherExecutor] - <Generated encryption key [Pnc9yJ_UTLrzYg7cdU2zIcSWGtPtZObSIQiLjzunswE] of size [256] for [Ticket-granting Cookie]. The generated key MUST be added to CAS settings under setting [cas.tgc.crypto.encryption.key].>WARN [org.apereo.cas.util.cipher.BaseStringCipherExecutor] - <Secret key for signing is not defined for [Ticket-granting Cookie]. CAS will attempt to auto-generate the signing key>WARN [org.apereo.cas.util.cipher.BaseStringCipherExecutor] - <Generated signing key [1Zf8v3M_oMRXc-vtoaZaSjzeRscYS1XiQClrJk37wPoi9hqQEJCcnY-N45TW2dCJln9AW_Eb9yKes7grxOW5jQ] of size [512] for [Ticket-granting Cookie]. The generated key MUST be added to CAS settings under setting [cas.tgc.crypto.signing.key].>WARN [org.apereo.cas.util.cipher.BaseBinaryCipherExecutor] - <Secret key for signing is not defined under [cas.webflow.crypto.signing.key]. CAS will attempt to auto-generate the signing key>WARN [org.apereo.cas.util.cipher.BaseBinaryCipherExecutor] - <Generated signing key [eW6CTqtoP03byXomSgZ6HbV3-50H2ObjQfdZ9AwtamryQPo4A8iYzyH2J5yCJmEvhCh-vLQErVGEMxiPKjZI4Q] of size [512]. The generated key MUST be added to CAS settings under setting [cas.webflow.crypto.signing.key].>WARN [org.apereo.cas.util.cipher.BaseBinaryCipherExecutor] - <Secret key for encryption is not defined under [cas.webflow.crypto.encryption.key]. CAS will attempt to auto-generate the encryption key>WARN [org.apereo.cas.util.cipher.BaseBinaryCipherExecutor] - <Generated encryption key [62ZPauktxRKUog0vP76QtQ] of size [16]. The generated key MUST be added to CAS settings under setting [cas.webflow.crypto.encryption.key].>