本文分为4个部分:
1.登录界面添加验证码
2.自定义登录对象,增加一个验证码字段
3.自定义cas的登录流程,完成自定义校验
4.返回自定错误信息
生成验证码,使用google的kaptcha,需引入jar的包如下
<dependency>
<groupId>com.github.pengglegroupId>
<artifactId>kaptchaartifactId>
<version>2.3.2version>
dependency>
<dependency>
<groupId>org.apereo.casgroupId>
<artifactId>cas-server-core-configurationartifactId>
<version>${cas.version}version>
dependency>
配置验证码,以下是我自己的配置,大家可根据自身需要进行修改,网上有很多kaptcha配置参数详解
package com.zee.custom.captcha.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(), "/images/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;
}
}
配置spring.factories 文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zee.custom.captcha.config.KaptchaConfig,
登录界面
<html>
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<script type="text/javascript" th:src="@{/themes/captcha/js/jquery.min.js}" >script>
<script type="text/javascript" th:src="@{/themes/captcha/js/code.js}" >script>
head>
<body>
<h2>默认的登录模板h2>
<div id="casLoginContent">
<form method="post" th:object="${credential}">
<div th:if="${#fields.hasErrors('*')}">
<span th:each="err : ${#fields.errors('*')}" th:utext="${err}"/>
div>
<h2 th:utext="#{screen.welcome.instructions}">h2>
<section class="row">
<label for="username" th:utext="#{screen.welcome.label.netid}"/>
<div th:unless="${openIdLocalId}">
<input class="required"
id="username"
size="25"
tabindex="1"
type="text"
th:disabled="${guaEnabled}"
th:field="*{username}"
th:accesskey="#{screen.welcome.label.netid.accesskey}"
autocomplete="off"/>
div>
section>
<section class="row">
<label for="password" th:utext="#{screen.welcome.label.password}"/>
<div>
<input class="required"
type="password"
id="password"
size="25"
tabindex="2"
th:accesskey="#{screen.welcome.label.password.accesskey}"
th:field="*{password}"
autocomplete="off"/>
div>
section>
<section>
<img id="captcha_img" th:src="@{/images/kaptcha.jpg}" onclick="changeCode()" style="width: 125px;"/>
<input type="text" th:field="*{captcha}" id="code"/>
<span id="code_str">span>
section>
<section>
<input type="hidden" name="execution" th:value="${flowExecutionKey}"/>
<input type="hidden" name="_eventId" value="submit"/>
<input type="hidden" name="geolocation"/>
<input class="btn btn-submit btn-block"
name="submit"
accesskey="l"
th:value="#{screen.welcome.button.login}"
tabindex="6"
type="submit"/>
section>
form>
div>
body>
html>
<script type="text/javascript">
function changeCode(){
//刷新验证码
$("#captcha_img").attr('src','/cas/images/kaptcha.jpg?id='+Math.random());
}
script>
自定义一个表单对象,需要继承默认的RememberMeUsernamePasswordCredential,添加一个验证码字段
package com.zee.custom.pojo;
import org.apereo.cas.authentication.RememberMeUsernamePasswordCredential;
public class CusLoginUserInfoEntity extends RememberMeUsernamePasswordCredential {
private static final long serialVersionUID = 1L;
private String captcha;
public String getCaptcha() {
return captcha;
}
public void setCaptcha(String captcha) {
this.captcha = captcha;
}
public static long getSerialversionuid() {
return serialVersionUID;
}
}
由于cas使用的是webflow所以我们还需要那自定义的登录对象绑定到页面上
package com.zee.custom.mongo.webflow;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.CasWebflowConstants;
import org.apereo.cas.web.flow.configurer.AbstractCasWebflowConfigurer;
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.zee.custom.pojo.CusLoginUserInfoEntity;
public class CustomWebflowConfigurer extends AbstractCasWebflowConfigurer {
/**
* 校验码动作
*/
public static final String VALIDATE_CAPTCHA_ACTION = "validateCaptchaAction";
public CustomWebflowConfigurer(FlowBuilderServices flowBuilderServices,
FlowDefinitionRegistry loginFlowDefinitionRegistry, ApplicationContext applicationContext,
CasConfigurationProperties casProperties) {
super(flowBuilderServices, loginFlowDefinitionRegistry, applicationContext, casProperties);
// TODO Auto-generated constructor stub
}
// 绑定验证码信息
protected void doInitialize() {
final Flow loginFlow = getLoginFlow();
// 重写绑定自定义credential
createFlowVariable(loginFlow, CasWebflowConstants.VAR_ID_CREDENTIAL, CusLoginUserInfoEntity.class);
// 获取登录页
final ViewState state = (ViewState) loginFlow.getState(CasWebflowConstants.STATE_ID_VIEW_LOGIN_FORM);
// 获取参数绑定对象
final BinderConfiguration cfg = getViewStateBinderConfiguration(state);
// 由于用户名以及密码已经绑定,所以只需对新加系统参数绑定即可
// 参数1 :字段名
// 参数2 :转换器
// 参数3 :是否必须的字段
cfg.addBinding(new BinderConfiguration.Binding("captcha", null, true));
}
}
然后把自定义的CustomWebflowConfigurer注册到spring容器中
package com.zee.custom.mongo.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.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.zee.custom.mongo.webflow.CustomWebflowConfigurer;
@Configuration
@EnableConfigurationProperties(CasConfigurationProperties.class)
@AutoConfigureBefore(value = CasWebflowContextConfiguration.class)
public class CustomWebFlowConfig {
@Autowired
private CasConfigurationProperties casProperties;
@Autowired
// @Qualifier("loginFlowRegistry")
private FlowDefinitionRegistry loginFlowRegistry;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private FlowBuilderServices flowBuilderServices;
// 注册自定义的webFlow
@Bean
public CasWebflowConfigurer customWebflowConfigurer() {
// 实例化自定义的表单配置类
final CustomWebflowConfigurer c = new CustomWebflowConfigurer(flowBuilderServices, loginFlowRegistry,
applicationContext, casProperties);
// 初期化
c.initialize();
// 返回对象
return c;
}
}
配置spring.factories 文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zee.custom.captcha.config.KaptchaConfig,\
com.zee.custom.mongo.config.CustomWebFlowConfig
新建一个类继承AbstractPreAndPostProcessingAuthenticationHandler 这个类,重写cas是默认登录流程,自定义登录参数的校验
import static com.mongodb.client.model.Filters.eq;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import javax.security.auth.login.AccountLockedException;
import javax.security.auth.login.AccountNotFoundException;
import javax.servlet.http.HttpServletRequest;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.authentication.HandlerResult;
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.PrincipalFactory;
import org.apereo.cas.services.ServicesManager;
import org.bson.Document;
import org.bson.conversions.Bson;
import org.pac4j.core.exception.MultipleAccountsFoundException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import com.zee.custom.mongo.exception.CusCaptchaException;
import com.zee.custom.mongo.utils.CryptoUtil;import com.zee.custom.pojo.CusLoginUserInfoEntity;
public class CustomMongoAuthenticationHandler extends AbstractPreAndPostProcessingAuthenticationHandler {
@Autowired
private MongoClient mongoClient;
public CustomMongoAuthenticationHandler(String name, ServicesManager servicesManager, PrincipalFactory principalFactory,
Integer order) {
super(name, servicesManager, principalFactory, order);
}
@Override
public boolean supports(Credential credential) {
// TODO Auto-generated method stub
System.out.println("被调用了");
return credential instanceof UsernamePasswordCredential;
}
/*
* 验证码,自定义验证, 验证用户名密码是否正确
*/
protected HandlerResult doAuthentication(Credential credential)
throws GeneralSecurityException, PreventedException {
final CusLoginUserInfoEntity myCredential = (CusLoginUserInfoEntity) credential;
// 通过SpringMvc封装的方法获得本次请求的request
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
.getRequest();
//获取存储在Session中的验证码
String captcha = request.getSession().getAttribute("captcha").toString();
if (!myCredential.getCaptcha().equals(captcha)) {
//此处抛出的是自定义异常,后面说明如何自定义异常,自定义返回错误信息
throw new CusCaptchaException("验证码错误");
}
System.out.println(request.getSession().getAttribute("captcha"));
//自定义其他校验
}
}
自定义的验证处理流程注册到spring容器中
package com.zee.custom.mongo.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.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.zee.custom.mongo.handler.CustomMongoAuthenticationHandler;
@Configuration("CustomAuthenticationHandlerConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CustomAuthenticationHandlerConfiguration implements AuthenticationEventExecutionPlanConfigurer {
@Autowired
@Qualifier("servicesManager")
private ServicesManager servicesManager;
// 注册验证器
@Bean
public AuthenticationHandler customAuthenticationHandler() {
return new CustomMongoAuthenticationHandler("customAuthenticationHandler", servicesManager,
new DefaultPrincipalFactory(), 1);
}
// 注册自定义认证器
public void configureAuthenticationExecutionPlan(AuthenticationEventExecutionPlan plan) {
// TODO Auto-generated method stub
plan.registerAuthenticationHandler(customAuthenticationHandler());
}
}
配置spring.factories 文件,完整的spring.factories 文件
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.zee.custom.mongo.config.CustomAuthenticationHandlerConfiguration,\
com.zee.custom.captcha.config.KaptchaConfig,\
com.zee.custom.mongo.config.CustomWebFlowConfig
当输入的验证码不正确时,我们需要自定义异常和错误信息
创建异常类,需要继承AccountExpiredException这个类
package com.zee.custom.mongo.exception;
import javax.security.auth.login.AccountExpiredException;
public class CusCaptchaException extends AccountExpiredException {
private static final long serialVersionUID = 1L;
public CusCaptchaException() {
super();
// TODO Auto-generated constructor stub
}
public CusCaptchaException(String msg) {
super(msg);
// TODO Auto-generated constructor stub
}
}
配置application.properties
#自定义异常,多个逗号隔开
cas.authn.exceptions.exceptions= com.zee.custom.mongo.exception.CusCaptchaException
把cas原来的messages_zh_CN.properties文件复制到src/mian/resources目录下面,并配置需要提示的信息
#自定义字段如果为必须输入,当为null时,提示信息格式为 字段名+required
captcha.required=必须输入验证码
#自定义异常信息格式为 authenticationFailure+异常类名
authenticationFailure.CusCaptchaException=验证码错误
参考文章:https://blog.csdn.net/yelllowcong/article/category/7347371