自定义登录验证器
整合spring+Mybatis连接数据库进行密码认证
1.首先配置pom.xml文件
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-webapp${app.server}</artifactId>
<version>${cas.version}</version>
<type>war</type>
<scope>runtime</scope>
</dependency>
<!-- 校验ticket相关代码(方便追踪代码) -->
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-util</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-util-api</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-services-api</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-authentication</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-authentication-api</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>org.apereo.cas</groupId>
<artifactId>cas-server-core-authentication-attributes</artifactId>
<version>${cas.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.29</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
</exclusions>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
2.配置application.properties文件(这里只是部分内容)
##
# CAS Server Context Configuration
#
server.context-path=/
server.port=443
#指定cas.server.namecas.server.name
cas.server.name=https://cas.xxx.mtn
cas.server.prefix=${cas.server.name}/
##
# CAS Authentication Credentials
#
#cas.authn.accept.users=casuser::Mellon
#数据库连接
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/cas5.3.14?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false
jdbc.user=root
jdbc.password=123456
#mybatis数据源配置
spring.datasource.url=${jdbc.url}
spring.datasource.driver-class-name=${jdbc.driverClass}
spring.datasource.username=${jdbc.user}
spring.datasource.password=${jdbc.password}
mybatis.mapperLocations=classpath:mapper/*.xml
##默认加密策略,通过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
#登入成功后可以查看到的信息
cas.authn.jdbc.query[0].principalAttributeList=userAccount:userAccount,userName:userName,email:email
3.配置spring
(1)配置spring,为了让spring加载 gds.application.cas.model包下 标注了@Service @Component @Controller 等注解的Bean
/**
* 这个配置是空值,是为了让spring 加载 这个包下 标注了 @Service @Component @Controller 等注解的Bean
* 并需要在resource/META-INF/spring.factories 中配置
*
* @author dongqifei
*/
@Configuration
@ComponentScan("gds.application.cas.model")
public class SpringConfig {
}
spring.factories 中配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
gds.application.cas.config.spring.SpringConfig
(2)开发功能接口,就和平常开发spring+springMVC项目一样写就行,只要确保代码在上面扫描的包下面即可。下图是我自己建的包,仅供参考
(3)这里可以写一个简单的Controller测试接口是否可以正常连接数据库
4.自定义登录验证器
(1)编写登录验证器
package gds.application.cas.config.principal;
import com.google.common.collect.Multimap;
import gds.application.cas.authentication.adaptors.CustomDatabaseAuthenticationHandler;
import gds.application.cas.model.usercenter.service.UserService;
import org.apereo.cas.authentication.*;
import org.apereo.cas.authentication.principal.DefaultPrincipalFactory;
import org.apereo.cas.authentication.support.password.PasswordEncoderUtils;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.model.support.jdbc.QueryJdbcAuthenticationProperties;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.util.CollectionUtils;
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;
import java.util.List;
import java.util.Map;
@Configuration("customQueryDatabaseAuthenticationConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomDatabaseAuthenticationConfiguration implements AuthenticationEventExecutionPlanConfigurer {
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
@Autowired
private UserService userService;//注入自定义的接口
/**
* 将自定义验证器注册为Bean
* @return
*/
@Bean
public AuthenticationHandler customAuthenticationConfiguration() {
// 获取配置文件中配置的jdbc配置
final List<QueryJdbcAuthenticationProperties> jdbc = casProperties.getAuthn().getJdbc().getQuery();
QueryJdbcAuthenticationProperties jdbcProperties = jdbc.get(0);
// casProperties.getCustom() 可获取自定义配置的属性值
CustomDatabaseAuthenticationHandler handler = new CustomDatabaseAuthenticationHandler(
CustomDatabaseAuthenticationHandler.class.getSimpleName(),
servicesManager,
userService,
new DefaultPrincipalFactory(),
casProperties.getCustom(),
1);
final Multimap<String, Object> multiMapAttributes = CoreAuthenticationUtils.transformPrincipalAttributesListIntoMultiMap(jdbcProperties.getPrincipalAttributeList());
final Map<String, Object> attributes = CollectionUtils.wrap(multiMapAttributes);
handler.setPrincipalAttributeMap(attributes);
handler.setPasswordEncoder(PasswordEncoderUtils.newPasswordEncoder(jdbcProperties.getPasswordEncoder()));
return handler;
}
/**
* 注册验证器
* @param plan
*/
@Override
public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
plan.registerAuthenticationHandler(customAuthenticationConfiguration());
}
(2)编写抽象身份验证处理程序,方便后续集成其它认证方式,将公共的代码写在一个地方。可自定义idap认证等等
package gds.application.cas.authentication.adaptors;
import gds.application.cas.common.constants.CustomWebConstants;
import gds.application.cas.exception.AccountUnusedLockedException;
import gds.application.cas.exception.WeakPasswordWarnException;
import gds.application.cas.model.usercenter.service.UserService;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
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.configuration.model.support.custom.CasCustomProperties;
import org.apereo.cas.services.ServicesManager;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.security.auth.login.AccountNotFoundException;
import javax.servlet.http.HttpServletRequest;
import java.security.GeneralSecurityException;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Setter
@Getter
public abstract class AbstractCustomAuthenticationHandler extends AbstractUsernamePasswordAuthenticationHandler {
/**
* Mapping of LDAP attribute name to principal attribute name.
*/
protected Map<String, Object> principalAttributeMap = new HashMap<>();
public final UserService userService;
public final CasCustomProperties casCustomProperties;
public AbstractCustomAuthenticationHandler(String name, ServicesManager servicesManager, UserService userService, PrincipalFactory principalFactory, CasCustomProperties casCustomProperties, Integer order) {
super(name, servicesManager, principalFactory, order);
this.userService = userService;
this.casCustomProperties = casCustomProperties;
}
@Override
protected AuthenticationHandlerExecutionResult authenticateUsernamePasswordInternal(UsernamePasswordCredential credential, String originalPassword) throws GeneralSecurityException, PreventedException {
final UsernamePasswordCredential originalUserPass = (UsernamePasswordCredential) credential;
final UsernamePasswordCredential userPass = new UsernamePasswordCredential(originalUserPass.getUsername(), originalUserPass.getPassword());
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
//这里可以自定义在检验密码之前做一些操作
return beforeAuthenticationUsernamePasswordInternal(userPass, originalUserPass.getPassword());
}
protected abstract AuthenticationHandlerExecutionResult beforeAuthenticationUsernamePasswordInternal(UsernamePasswordCredential credential,
String originalPassword) throws GeneralSecurityException, PreventedException;
}
(3)编写身份验证处理程序
package gds.application.cas.authentication.adaptors;
import gds.application.cas.common.constants.CustomWebConstants;
import gds.application.cas.common.utils.ObjectUtils;
import gds.application.cas.exception.AccountUnusedLockedException;
import gds.application.cas.exception.MustChangePasswordException;
import gds.application.cas.exception.WeakPasswordWarnException;
import gds.application.cas.model.usercenter.service.UserService;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.UsernamePasswordCredential;
import org.apereo.cas.authentication.exceptions.AccountDisabledException;
import org.apereo.cas.authentication.principal.Principal;
import org.apereo.cas.authentication.principal.PrincipalFactory;
import org.apereo.cas.configuration.model.support.custom.CasCustomProperties;
import org.apereo.cas.services.ServicesManager;
import org.apereo.cas.util.CollectionUtils;
import org.ldaptive.LdapEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;
import java.security.GeneralSecurityException;
import java.util.*;
/**
* 自定义登录验证器
*
* @author dongqifei
*/
@Slf4j
@Setter
public class CustomDatabaseAuthenticationHandler extends AbstractCustomAuthenticationHandler {
private static final Logger LOGGER = LoggerFactory.getLogger(CustomDatabaseAuthenticationHandler.class);
private final String FIELDPASSWORD = "password";
public CustomDatabaseAuthenticationHandler(String name, ServicesManager servicesManager, UserService userService, PrincipalFactory principalFactory, CasCustomProperties casCustomProperties, Integer order) {
super(name, servicesManager, userService, principalFactory, casCustomProperties, order);
}
@Override
protected AuthenticationHandlerExecutionResult beforeAuthenticationUsernamePasswordInternal(UsernamePasswordCredential credential, String originalPassword) throws GeneralSecurityException {
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
final Map<String, Object> attributeMap = new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
final String username = credential.getUsername();
final String password = credential.getPassword();
try {
Map<String, Object> dbFields;
try{
dbFields = ObjectUtils.objectToMap(this.getUserService().selectByPrimaryKey(username));
}catch (Exception ex) {
throw new FailedLoginException("DTO转换Map异常");
}
final String dbPassword = (String) dbFields.get(this.FIELDPASSWORD);
if ((StringUtils.isNotBlank(originalPassword) && !matches(originalPassword, dbPassword))
|| (StringUtils.isBlank(originalPassword) && !StringUtils.equals(password, dbPassword))) {
throw new FailedLoginException("用户名或密码错误!");
}
// 封装用户扩展属性
final Principal principal = createPrincipal(username, attributeMap);
return createHandlerResult(credential, principal);
} catch (FailedLoginException e) {
LOGGER.error("登录失败[{}]",e.getMessage());
throw e;
}
}
/**
* 如果查询的用户信息包含主体属性,则创建具有属性的CAS主体。
* @param username
* @param dbFields
* @return
*/
protected Principal createPrincipal(final String username, final Map<String, Object> dbFields){
final Map<String, Object> attributeMap = collectPrincipalAttributes(dbFields);
return this.principalFactory.createPrincipal(username, attributeMap);
}
/**
* 封装用户扩展属性
*
* @param dbFields 数据源
*/
private Map<String, Object> collectPrincipalAttributes(final Map<String, Object> dbFields) {
final Map<String, Object> attributeMap = new LinkedHashMap<>(this.principalAttributeMap.size());
this.principalAttributeMap.forEach((key, names) -> {
final Object attribute = dbFields.get(key);
if (attribute != null) {
LOGGER.debug("Found attribute [{}] from the query results", key);
final Collection<String> attributeNames = (Collection<String>) names;
attributeNames.forEach(s -> {
LOGGER.debug("Principal attribute [{}] is virtually remapped/renamed to [{}]", key, s);
attributeMap.put(s, CollectionUtils.wrap(attribute.toString()));
});
} else {
LOGGER.warn("Requested attribute [{}] could not be found in the query results", key);
}
});
return attributeMap;
}
}
(4)spring.factories 中配置
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
gds.application.cas.config.spring.SpringConfig,\
gds.application.cas.config.principal.CustomDatabaseAuthenticationConfiguration
(5)至此自定义验证器完成。
如果CAS是https协议的话,需要生成证书;
生成证书可参考 https://blog.csdn.net/qq_21359467/article/details/102731032
系统中如果同时存在数据库认证和ldap认证方式,两种将都会被加载,只要有一种认证通过就行