自定义验证很重要,因为我们后续的很多功能,都是基于自定义验证。
CAS服务器的org.apereo.cas.authentication.AuthenticationManager负责基于提供的凭证信息进行用户认证。与Spring Security很相似,实际的认证委托给了一个或多个实现了org.apereo.cas.authentication.AuthenticationHandler接口的处理类。在cas的认证过程中逐个执行authenticationHandlers中配置的认证管理,直到有一个成功为止。
CAS内置了一些AuthenticationHandler实现类,如下图所示,QueryDatabaseAuthenticationHandler中提供了基于jdbc的用户认
如果需要实现自定义登录,只需要实现org.apereo.cas.authentication.AuthenticationHandler接口即可。当然也可以利用已有的实现,比如创建一个继承自org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler的类。下面我们就来实现自定义验证。
自定义登入
pom.xml 添加如下依赖jar包
org.apereo.cas
cas-server-core-webflow
${cas.version}
org.apereo.cas
cas-server-core-authentication
${cas.version}
org.apereo.cas
cas-server-core-authentication-api
${cas.version}
org.apereo.cas
cas-server-core-webflow-api
${cas.version}
自定义验证类,继承AbstractPreAndPostProcessingAuthenticationHandler
package com.digipower.authentication.handler;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.security.auth.login.AccountNotFoundException;
import javax.security.auth.login.FailedLoginException;
import org.apache.commons.lang.StringUtils;
import org.apereo.cas.authentication.AuthenticationHandlerExecutionResult;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.MessageDescriptor;
import org.apereo.cas.authentication.PreventedException;
import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
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.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.digipower.encrypt.PasswordEncryption;
import com.digipower.exception.CaptchaException;
import com.digipower.verification.code.UsernamePasswordCaptchaCredential;
/**
* 自定义用户认证核心代码
* @author zzg
*
*/
public class UsernamePasswordCaptchaAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler {
public UsernamePasswordCaptchaAuthenticationHandler(String name, ServicesManager servicesManager,
PrincipalFactory principalFactory, Integer order) {
super(name, servicesManager, principalFactory, order);
// TODO Auto-generated constructor stub
}
@Override
protected AuthenticationHandlerExecutionResult doAuthentication(Credential credential)
throws GeneralSecurityException, PreventedException {
// TODO Auto-generated method stub
// 用户凭证
UsernamePasswordCaptchaCredential myCredential = (UsernamePasswordCaptchaCredential) credential;
String requestCaptcha = myCredential.getCaptcha();
ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
Object attribute = attributes.getRequest().getSession().getAttribute("captcha");
String realCaptcha = attribute == null ? null : attribute.toString();
if (StringUtils.isBlank(requestCaptcha) || !requestCaptcha.equalsIgnoreCase(realCaptcha)) {
throw new CaptchaException("验证码错误");
}
// 验证用户名和密码
DriverManagerDataSource d = new DriverManagerDataSource();
d.setDriverClassName("com.mysql.jdbc.Driver");
d.setUrl(
"jdbc:mysql://192.168.1.73:3306/boot-security?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true");
d.setUsername("root");
d.setPassword("digipower");
JdbcTemplate template = new JdbcTemplate();
template.setDataSource(d);
// 查询数据库加密的的密码
Map user = template.queryForMap("select pswd from u_user where nickname = ?",
myCredential.getUsername());
if (user == null) {
throw new AccountNotFoundException("用户名输入错误或用户名不存在");
}
// 返回多属性
Map map = new HashMap<>();
map.put("username", myCredential.getUsername());
// 密码加密验证(MD5 32位 大写)
PasswordEncryption passwordEncryption = new PasswordEncryption();
List warning = new ArrayList();
if (passwordEncryption.matches(myCredential.getPassword(), user.get("pswd").toString())) {
return createHandlerResult(myCredential, principalFactory.createPrincipal(myCredential.getUsername(), map),
warning);
}
throw new FailedLoginException("密码输入错误");
}
// 判断是否支持自定义用户登入凭证
@Override
public boolean supports(Credential credential) {
// TODO Auto-generated method stub
return credential instanceof UsernamePasswordCaptchaCredential;
}
}
绑定自定义认证流程和自定义凭证信息
package com.digipower.captcha.config;
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.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;
import com.digipower.authentication.handler.UsernamePasswordCaptchaAuthenticationHandler;
/**
* 自定义用户登入流程使用的自定义的用户凭证
* @author zzg
*
*/
@Configuration("usernamePasswordCaptchaConfig")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class UsernamePasswordCaptchaConfig implements AuthenticationEventExecutionPlanConfigurer{
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
/**
* 用户定义用户登入处理器
* @return
*/
@Bean
public AuthenticationHandler rememberMeUsernamePasswordCaptchaAuthenticationHandler() {
UsernamePasswordCaptchaAuthenticationHandler handler = new UsernamePasswordCaptchaAuthenticationHandler(
UsernamePasswordCaptchaAuthenticationHandler.class.getSimpleName(),
servicesManager,
new DefaultPrincipalFactory(),
9);
return handler;
}
/**
*
* Title: configureAuthenticationExecutionPlan
* Description: 用户自定义表单处理注册
* @param plan
* @see org.apereo.cas.authentication.AuthenticationEventExecutionPlanConfigurer#configureAuthenticationExecutionPlan(org.apereo.cas.authentication.AuthenticationEventExecutionPlan)
*/
@Override
public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
// TODO Auto-generated method stub
plan.registerAuthenticationHandler(rememberMeUsernamePasswordCaptchaAuthenticationHandler());
}
}
创建用户登入凭证,添加验证码属性
package com.digipower.verification.code;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apereo.cas.authentication.RememberMeUsernamePasswordCredential;
/**
* 自定义用户凭证,添加验证码属性
* @author zzg
*
*/
@SuppressWarnings("serial")
public class UsernamePasswordCaptchaCredential extends RememberMeUsernamePasswordCredential {
private String captcha;
public String getCaptcha() {
return captcha;
}
public void setCaptcha(String captcha) {
this.captcha = captcha;
}
@Override
public int hashCode() {
return new HashCodeBuilder()
.appendSuper(super.hashCode())
.append(this.captcha)
.toHashCode();
}
}
重新定义Apereo Cas5 服务端的登入流程
package com.digipower.webflow.action;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.CasWebflowConstants;
import org.apereo.cas.web.flow.configurer.DefaultLoginWebflowConfigurer;
import org.springframework.context.ApplicationContext;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.engine.builder.BinderConfiguration;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
import com.digipower.verification.code.UsernamePasswordCaptchaCredential;
/**
* 自定义系统登入设置自定义用户凭证
* @author zzg
*
*/
public class CaptchaWebflowConfigurer extends DefaultLoginWebflowConfigurer {
public CaptchaWebflowConfigurer(FlowBuilderServices flowBuilderServices,
FlowDefinitionRegistry flowDefinitionRegistry, ApplicationContext applicationContext,
CasConfigurationProperties casProperties) {
super(flowBuilderServices, flowDefinitionRegistry, applicationContext, casProperties);
}
@Override
protected void createRememberMeAuthnWebflowConfig(Flow flow) {
if (casProperties.getTicket().getTgt().getRememberMe().isEnabled()) {
createFlowVariable(flow, CasWebflowConstants.VAR_ID_CREDENTIAL, UsernamePasswordCaptchaCredential.class);
final ViewState state = getState(flow, CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM, ViewState.class);
final BinderConfiguration cfg = getViewStateBinderConfiguration(state);
cfg.addBinding(new BinderConfiguration.Binding("rememberMe", null, false));
cfg.addBinding(new BinderConfiguration.Binding("captcha", null, true));
} else {
createFlowVariable(flow, CasWebflowConstants.VAR_ID_CREDENTIAL, UsernamePasswordCaptchaCredential.class);
final ViewState state = getState(flow, CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM, ViewState.class);
final BinderConfiguration cfg = getViewStateBinderConfiguration(state);
// cfg.addBinding(new BinderConfiguration.Binding("rememberMe", null, false));
cfg.addBinding(new BinderConfiguration.Binding("captcha", null, true));
}
}
}
修改Apereo Cas 5 系统自带的登入流程定义webflow
在login-webflow.xml/viewLoginForm 添加如下代码:
源码修改如下:
将自定义登入流程添加如Apereo Cas 5服务器中webflow 中
package com.digipower.captcha.config;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.CasWebflowConfigurer;
import org.apereo.cas.web.flow.config.CasWebflowContextConfiguration;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
import com.digipower.webflow.action.CaptchaWebflowConfigurer;
/**
* 自定义登入流程配置入webflow
* @author zzg
*
*/
@Configuration("captchaWebflowConfigurer")
@EnableConfigurationProperties(CasConfigurationProperties.class)
@AutoConfigureBefore(value = CasWebflowContextConfiguration.class)
public class CaptchaConfigurer {
@Autowired
@Qualifier("loginFlowRegistry")
private FlowDefinitionRegistry loginFlowRegistry;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
private FlowBuilderServices flowBuilderServices;
@Bean("defaultLoginWebflowConfigurer")
public CasWebflowConfigurer defaultLoginWebflowConfigurer() {
CasWebflowConfigurer c = new CaptchaWebflowConfigurer(flowBuilderServices, loginFlowRegistry,
applicationContext, casProperties);
c.initialize();
return c;
}
}
补充相关的工具类:Kaptcha 图形验证码初始化,用户密码加密工具类,springBean 工具类和相关自定义异常
package com.digipower.kaptcha.config;
import javax.servlet.ServletException;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.google.code.kaptcha.servlet.KaptchaServlet;
@Configuration
public class KaptchaConfig {
@Bean
public ServletRegistrationBean servletRegistrationBean() throws ServletException {
ServletRegistrationBean servlet = new ServletRegistrationBean(new KaptchaServlet(), "/kaptcha.jpg");//加载路径
servlet.addInitParameter("kaptcha.border", "no"/* kborder */);// 无边框
servlet.addInitParameter("kaptcha.session.key", "captcha");// session key
servlet.addInitParameter("kaptcha.textproducer.font.color", "black");
servlet.addInitParameter("kaptcha.textproducer.font.size", "25");
servlet.addInitParameter("kaptcha.obscurificator.impl", "com.google.code.kaptcha.impl.WaterRipple");
servlet.addInitParameter("kaptcha.noise.impl", "com.google.code.kaptcha.impl.NoNoise");
servlet.addInitParameter("kaptcha.image.width", "90");
servlet.addInitParameter("kaptcha.image.height", "33");
servlet.addInitParameter("kaptcha.textproducer.char.length", "4");
servlet.addInitParameter("kaptcha.textproducer.char.space", "5");
servlet.addInitParameter("kaptcha.background.clear.from", "247,247,247"); // 和登录框背景颜色一致
servlet.addInitParameter("kaptcha.background.clear.to", "247,247,247");
return servlet;
}
}
package com.digipower.encrypt;
import org.apache.commons.codec.digest.DigestUtils;
import org.apereo.cas.authentication.handler.support.AbstractPreAndPostProcessingAuthenticationHandler;
import org.springframework.security.crypto.password.PasswordEncoder;
/**
* 自定义数据密码加密工具类
* @author zzg
*
*/
public class PasswordEncryption implements PasswordEncoder{
@Override
public String encode(CharSequence password) {
return DigestUtils.md5Hex(password.toString()).toUpperCase();
}
@Override
public boolean matches(CharSequence rawPassword, String encodePassword) {
// 判断密码是否存在
if (rawPassword == null) {
return false;
}
//通过md5加密后的密码
String pass = this.encode(rawPassword.toString());
//比较密码是否相等的问题
return pass.equals(encodePassword);
}
AbstractPreAndPostProcessingAuthenticationHandler handler;
}
package com.digipower.exception;
import javax.security.auth.login.AccountException;
@SuppressWarnings("serial")
public class CaptchaException extends AccountException {
public CaptchaException() {
super();
// TODO Auto-generated constructor stub
}
public CaptchaException(String msg) {
super(msg);
// TODO Auto-generated constructor stub
}
}
package com.digipower.util;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
@Component
public class SpringContextUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
SpringContextUtils.applicationContext = applicationContext;
}
public static Object getBean(String name) {
return applicationContext.getBean(name);
}
public static T getBean(String name, Class requiredType) {
return applicationContext.getBean(name, requiredType);
}
public static boolean containsBean(String name) {
return applicationContext.containsBean(name);
}
public static boolean isSingleton(String name) {
return applicationContext.isSingleton(name);
}
public static Class> getType(String name) {
return applicationContext.getType(name);
}
}
加载该配置类
在resources\META-INF\spring.factories中配置该类
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.digipower.captcha.config.CaptchaConfigurer,\
com.digipower.kaptcha.config.KaptchaConfig,\
com.digipower.captcha.config.UsernamePasswordCaptchaConfig,\
com.digipower.util.SpringContextUtils
系统登入页面修改,添加验证码(/templates/fragments/loginfrom.html),添加如下代码:
apereo cas5 自定义项目的整体结构:
pom.xml 文件
4.0.0
org.apereo.cas
cas-overlay
war
1.0
com.rimerosolutions.maven.plugins
wrapper-maven-plugin
0.0.5
true
MD5
org.springframework.boot
spring-boot-maven-plugin
${springboot.version}
${mainClassName}
true
${isExecutable}
WAR
repackage
org.apache.maven.plugins
maven-war-plugin
2.6
cas
false
false
false
${manifestFileToUse}
org.apereo.cas
cas-server-webapp${app.server}
org.apache.maven.plugins
maven-compiler-plugin
3.3
cas
8.0.13
5.3.9
1.5.18.RELEASE
-tomcat
org.springframework.boot.loader.WarLauncher
false
${project.build.directory}/war/work/org.apereo.cas/cas-server-webapp${app.server}/META-INF/MANIFEST.MF
1.8
1.8
UTF-8
sonatype-releases
http://oss.sonatype.org/content/repositories/releases/
false
true
sonatype-snapshots
https://oss.sonatype.org/content/repositories/snapshots/
true
false
shibboleth-releases
https://build.shibboleth.net/nexus/content/repositories/releases
true
default
org.apereo.cas
cas-server-webapp${app.server}
${cas.version}
war
runtime
org.apereo.cas
cas-server-support-jdbc
${cas.version}
org.apereo.cas
cas-server-support-jdbc-drivers
${cas.version}
mysql
mysql-connector-java
${mysql.driver.version}
org.apereo.cas
cas-server-support-redis-ticket-registry
${cas.version}
org.apereo.cas
cas-server-core-webflow
${cas.version}
org.apereo.cas
cas-server-core-authentication
${cas.version}
org.apereo.cas
cas-server-core-authentication-api
${cas.version}
org.apereo.cas
cas-server-core-webflow-api
${cas.version}
org.apereo.cas
cas-server-core-configuration
${cas.version}
com.github.penggle
kaptcha
2.3.2
org.apereo.cas
cas-server-support-audit-jdbc
${cas.version}
runtime
false
exec
org.apereo.cas.web.CasWebApplication
true
com.soebes.maven.plugins
echo-maven-plugin
0.3.0
prepare-package
echo
Executable profile to make the generated CAS web
application executable.
false
bootiful
-tomcat
false
org.apereo.cas
cas-server-webapp${app.server}
${cas.version}
war
runtime
false
pgp
com.github.s4u.plugins
pgpverify-maven-plugin
1.1.0
check
hkp://pool.sks-keyservers.net
${settings.localRepository}/pgpkeys-cache
test
true
false
效果展示: