- 上一篇:CAS集群【1】-理论(cas架构、https、SSL、TLS、非对称加密、证书、CA、握手)、OpenSSL、cas(认证、指定加密策略)
- 下一篇: CAS集群【3】 - 自定义主题、Service配置及管理
参考: - CAS之5.2x版本自定义登录,多数据源登录-yellowcong
- CAS单点登录(四)——自定义认证登录策略
- CAS单点登录(五)——Service配置及管理
github:
自定义校验策略
# 继承 AbstractUsernamePasswordAuthenticationHandler
官方提供的认证策略通常是不够,这就需要我们能自定义认证校验策略
自定义策略主要通过现实更改CAS配置,通过AuthenticationHandler在CAS中设计和注册自定义身份验证策略,拦截数据源达到目的。
主要分为下面三个步骤:
- 设计自己的认证处理数据的程序
- 注册认证拦截器到CAS的认证引擎中
- 更改认证配置到CAS中
首先我们还是添加需要的依赖库:
<!-- Custom Authentication -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-authentication-api</artifactId>
<version>${cas.version}</version>
</dependency>
<!-- Custom Configuration -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-configuration-api</artifactId>
<version>${cas.version}</version>
</dependency>
如果我们认证的方式仅仅是传统的用户名和密码,实现AbstractUsernamePasswordAuthenticationHandler
这个抽象类就可以了,官方给的实例也是这个。 - Configuring-Custom-Authentication
接着我们自定义我们自己的实现类CustomAuthenticationHandler,如下
package cn.cas;
import cn.cas.utils.UserUtils;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.MessageDescriptor;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import org.springframework.util.StringUtils;
import javax.security.auth.login.AccountException;
import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
/**
* @author lawsssscat
*/
public class CustomerAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {
public CustomerAuthenticationHandler(
String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
super(name, servicesManager, principalFactory, order);
}
@Override
protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(
UsernamePasswordCredential credential, String originalPassword)
throws GeneralSecurityException, PreventedException {
String username = credential.getUsername();
String password = credential.getPassword();
if (StringUtils.isEmpty(username)) {
throw new AccountException("enter your username");
} else if (StringUtils.isEmpty(password)) {
throw new AccountException("enter your PIN");
}
System.out.println("username:" + username);
System.out.println("password:" + password);
User user = UserUtils.findUser(username);
System.out.println("user:" + user);
if (user == null) {
throw new AccountException("Sorry, username not found !");
}
System.out.println("database username:" + user.getUsername());
System.out.println("database password:" + user.getPassword());
if (!password.equals(user.getPassword())) {
throw new FailedLoginException("Sorry, password not correct !");
}
// 可自定义返回给客户端多个属性信息
HashMap<String, Object> info = new HashMap<>();
info.put("expired", user.getExpired());
// 不能为null,否则提交信息无法认证成功!
List<MessageDescriptor> warning = new ArrayList<>();
return createHandlerResult(credential, this.principalFactory.createPrincipal(username, info), warning);
}
}
其中涉及到的 实体类 User、连接数据库的工具类UserUtils 不是重点,就到 github 上看吧
https://github.com/LawssssCat/v-cas
由于版本原因(官方是上一个版本),这里给出的与官方实例不同在两个地方
- 其一,返回的为
AuthenticationHandlerExecutionResult
而不是HandlerResult
,其实源码是一样的,在新版本重新命名了而已。 - 第二点,createHandlerResult传入的warings不能为null,不然程序运行后提交信息始终无法认证成功!!!
代码主要通过拦截传入的 Credential
,获取用户名和密码,然后再自定义返回给客户端的用户信息。
这里便可以通过代码方式自定义返回给客户端多个不同属性信息。
接着我们注入配置信息,继承 AuthenticationEventExecutionPlanConfigurer
。
package cn.cas;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlan;
import org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer;
import org.apereo.cas.authentication.AuthenticationHandler;
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.services.ServicesManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author lawsssscat
*/
@Configuration("CustomAuthenticationConfiguration") // 此需要添加,否则bean会被注册两次(虽然不影响正常运行)
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomAuthenticationConfiguration implements AuthenticationEventExecutionPlanConfigurer {
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
// 验证器交给Spring管理
@Bean
public AuthenticationHandler customerAuthenticationHandler() {
System.out.println("initializating AuthenticationHandler@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
String name = CustomerAuthenticationHandler.class.getName();
ServicesManager servicesManager = this.servicesManager;
PrincipalFactory principalFactory = new DefaultPrincipalFactory();
// 定义为优先优先使用
Integer order = 1;
return new CustomerAuthenticationHandler(name,servicesManager, principalFactory, order) ;
}
// 注册自定义验证器
@Override
public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
plan.registerAuthenticationHandler(customerAuthenticationHandler());
}
}
最后我们我们在src/main/resources目录下新建META-INF目录,同时在下面新建spring.factories文件,将配置指定为我们自己新建的信息。
org.springframework.boot.autoconfigure.EnableAutoConfiguration=cn.cas.CustomAuthenticationConfiguration
-- auto-generated definition
create table sp_manager
(
mg_id int auto_increment comment '主键id'
primary key,
mg_name varchar(32) not null comment '名称',
mg_pwd char(32) not null comment '密码',
mg_salt char(36) not null comment 'salt',
mg_time int unsigned not null comment '注册时间',
role_id tinyint(11) default 0 not null comment '角色id',
mg_mobile varchar(32) null,
mg_email varchar(64) null,
mg_expired tinyint(2) default 0 null comment '0:表示启用 1:表示过期',
mg_disabled tinyint(2) default 0 null comment '0:表示启用 1:表示禁用'
)
comment '管理员表';
启动应用,输入用户名(linken)和密码(123456),查看控制台我们打印的信息,可以发现我们从登陆页面提交的数据以及从数据库中查询到的数据,匹配信息,登录认证成功!!
# 继承 AbstractPreAndPostProcessingAuthenticationHandler
上面的继承其实有问题,我提交的信息不止用户名和密码,那该如何自定义认证?
这里就要我们继承 AbstractPreAndPostProcessingAuthenticationHandler
这个接口
(其实上面的AbstractUsernamePasswordAuthenticationHandler就是继承实现的这个类,它只是用于简单的用户名和密码的校验。)
所以我们要自定义实现 AbstractPreAndPostProcessingAuthenticationHandler
接可以了。
比如这里我新建 CustomerAuthenticationHandler2
类,如下:
package cn.cas.authentication.handler;
import cn.cas.model.User;
import cn.cas.utils.UserUtils;
import lombok.NonNull;
import lombok.extern.log4j.Log4j2;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.springframework.util.StringUtils;
import javax.security.auth.login.AccountException;
import javax.security.auth.login.FailedLoginException;
import java.security.GeneralSecurityException;
/**
* @author lawsssscat
*/
@Log4j2
public class CustomerAuthenticationHandler2 extends AbstractPreAndPostProcessingAuthenticationHandler {
public CustomerAuthenticationHandler2(String name, ServicesManager servicesManager, PrincipalFactory principalFactory, Integer order) {
super(name, servicesManager, principalFactory, order);
}
@Override
public boolean supports(Credential credential) {
// 判断传递过来的Credential是否是自己能处理的类型
return credential instanceof UsernamePasswordCredential;
}
@Override
protected AuthenticationHandlerExecutionResult doAuthentication(
Credential credential)
throws GeneralSecurityException, PreventedException {
UsernamePasswordCredential usernamePasswordCredential = (UsernamePasswordCredential) credential;
String username = usernamePasswordCredential.getUsername();
String password = usernamePasswordCredential.getPassword();
if (StringUtils.isEmpty(username)) {
throw new AccountException("enter your username");
} else if (StringUtils.isEmpty(password)) {
throw new AccountException("enter your PIN");
}
log.info("log4j2 is running @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
System.out.println("username:" + username);
System.out.println("password:" + password);
User user = UserUtils.findUser(username);
System.out.println("user:" + user);
if (user == null) {
throw new AccountException("Sorry, username not found !");
}
if (!password.equals(user.getPassword())) {
throw new FailedLoginException("Sorry, password not correct!");
}
@NonNull Principal principal = this.principalFactory.createPrincipal(username);
return createHandlerResult(usernamePasswordCredential, principal);
}
}
这里我只是简单实现了用户名和密码的信息获取,当有更多信息提交时,在转换Credential时便可以拿到提交的信息。后面我会讲解,这里不明白没关系。
接着我们在CustomAuthenticationConfiguration中将CustomerAuthenticationHandler 更改为CustomerAuthenticationHandler2 。
// 验证器交给Spring管理
@Bean
public AuthenticationHandler customerAuthenticationHandler() {
System.out.println("initializating AuthenticationHandler@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@");
String name = CustomerAuthenticationHandler2.class.getName();
ServicesManager servicesManager = this.servicesManager;
PrincipalFactory principalFactory = new DefaultPrincipalFactory();
// 定义为优先优先使用
Integer order = 1;
return new CustomerAuthenticationHandler2(name,servicesManager, principalFactory, order) ;
}
启动应用,可以发现跟先前能达到相同效果。
github: