CAS单点登录-登录校验码(十七)
本章教程用的cas版本为5.1.5
简介
在一些常规的老系统不得不加入固定的验证码,当然这是为了流控、暴力破解、降低数据库压力等等原因,那么接下来会讲解一些如何进行解决这些问题
流控/防爆:
这一层可以在监控系统中做,例如同一个ip高频率访问可以进行一些禁止策略处理,除了这个当然可以加验证码了,但传统的老系统往往是一开始就添加验证码,这样给用户的感觉不太友好了,毕竟并不是每个过来的都是机器人。例如可以添加一些策略,连续三次密码错误才加验证码,验证码也可以随机,图片验证,手机验证码,扫描校验,算法验证码等等
降低数据库压力:
用户密码,用户信息可以从缓存中获取,因为用户并不是经常改密码改用户信息,当未发生改变时都可以缓存中获取密码进行匹配,若每次都从数据库中获取密码进行匹配这肯定是对数据库增加了不少压力,当然了如果增加缓存层肯定是对系统增加了复杂度,这要看技术团队如何衡量这个事情了
本章计划
- 如何自定义Controller
- 输出校验码
- 登录前置验证码进行校验
温馨提示:
本章不包括:自定义校验码策略,流控策略
实战
自定义控制器(校验码)
在spring mvc中用到控制器就多了,可能传统的就是加
@Controller
或者继承或者实现Controller的接口这样然后让spring容器能够识别扫描到即可
那么在cas中依然是这样,简单看看以下代码
AbstractDelegateController已经加了@Controller
import org.apereo.cas.web.AbstractDelegateController;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
/**
* 验证码控制器
*
* @author Carl
* @date 2017/10/27
*/
public class CaptchaController extends AbstractDelegateController {
private ICaptchaWriter<String> captchaWriter;
private SessionCaptchaResultAware<String> aware;
public CaptchaController(ICaptchaWriter<String> captchaWriter, SessionCaptchaResultAware<String> aware) {
this.captchaWriter = captchaWriter;
this.aware = aware;
}
public SessionCaptchaResultAware<String> getAware() {
return aware;
}
public ICaptchaWriter<String> getCaptchaWriter() {
return captchaWriter;
}
@Override
public boolean canHandle(HttpServletRequest request, HttpServletResponse response) {
return true;
}
@GetMapping(value = CaptchaConstants.REQUEST_MAPPING, produces = "image/png")
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
//设置response头信息
//禁止缓存
response.setHeader("Cache-Control", "no-cache");
response.setContentType("image/png");
OutputStream outputStream = response.getOutputStream();
//存储验证码到session
String text = getAware().getAndStore(request.getSession());
getCaptchaWriter().write(text, outputStream);
return null;
}
}
import com.carl.sso.support.captcha.CaptchaController;
import com.carl.sso.support.captcha.ICaptchaWriter;
import com.carl.sso.support.captcha.SessionCaptchaResultAware;
import com.carl.sso.support.captcha.SessionCaptchaResultProvider;
import com.carl.sso.support.captcha.string.StringCaptchaResultAware;
/**
* Cage验证码控制器
*
* @author Carl
* @date 2017/10/27
*/
public class CageCaptchaController extends CaptchaController {
public CageCaptchaController(ICaptchaWriter<String> captchaWriter, SessionCaptchaResultAware<String> aware) {
super(captchaWriter, aware);
}
public CageCaptchaController() {
super(new CageStringCaptchaWriter(), new StringCaptchaResultAware(new SessionCaptchaResultProvider(), new CageStringTokenGenerator()));
}
public CageCaptchaController(SessionCaptchaResultProvider provider) {
super(new CageStringCaptchaWriter(), new StringCaptchaResultAware(provider, new CageStringTokenGenerator()));
}
}
注册:
import com.carl.sso.support.captcha.SessionCaptchaResultProvider;
import com.carl.sso.support.captcha.imp.cage.CageCaptchaController;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Carl
* @date 2017/10/28
* @since 1.5.0
*/
@Configuration("captchaConfiguration")
@EnableConfigurationProperties(CasConfigurationProperties.class)
public class CaptchaConfiguration {
//注册bean到spring容器
@Bean
@ConditionalOnMissingBean(name = "captchaController")
public CageCaptchaController captchaController() {
return new CageCaptchaController(captchaResultProvider());
}
@Bean
public SessionCaptchaResultProvider captchaResultProvider() {
return new SessionCaptchaResultProvider();
}
}
spring.factories
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.carl.sso.support.captcha.config.CaptchaConfiguration
以上即完成一个controller的自定义,当然了,这个比较简单不是本章的重点,但上面完成了一个简单的验证码的输出
登录前置验证码进行校验
需求:提交表单后进行校验码匹配,若失败跳转回登录页,那么以下代码我们模仿了官网的谷歌验证码模块
- 新增action
- 把action设置到提交表单流程
action代码为了方便测试,以下逻辑是在demo主题下触发,并且有系统参数进来才响应
import com.carl.sso.support.auth.UsernamePasswordSysCredential;
import com.carl.sso.support.captcha.ICaptchaResultProvider;
import org.apereo.cas.authentication.Credential;
import org.apereo.cas.web.support.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.binding.message.MessageBuilder;
import org.springframework.binding.message.MessageContext;
import org.springframework.webflow.action.AbstractAction;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
/**
* 登录校验码
*
* @author Carl
* @date 2017/11/18
*/
public class ValidateLoginCaptchaAction extends AbstractAction {
private static final Logger LOGGER = LoggerFactory.getLogger(ValidateLoginCaptchaAction.class);
//验证码存储器
private ICaptchaResultProvider<HttpSession, String> captchaResultProvider;
private static final String CODE = "captchaError";
public ValidateLoginCaptchaAction(ICaptchaResultProvider<HttpSession, String> captchaResultProvider) {
this.captchaResultProvider = captchaResultProvider;
}
/**
* 前端验证码
*/
public static final String CODE_PARAM = "validateCode";
@Override
protected Event doExecute(RequestContext context) throws Exception {
Credential credential = WebUtils.getCredential(context);
//系统信息不为空才检测校验码
if(credential instanceof UsernamePasswordSysCredential && ((UsernamePasswordSysCredential) credential).getSystem() != null) {
if (isEnable()) {
LOGGER.debug("开始校验登录校验码");
HttpServletRequest request = WebUtils.getHttpServletRequest();
HttpSession httpSession = request.getSession();
//校验码
String inCode = request.getParameter(CODE_PARAM);
//校验码失败跳转到登录页
if(!this.captchaResultProvider.validate(httpSession, inCode)) {
return getError(context);
}
}
}
return null;
}
/**
* 是否开启验证码
* @return
*/
private boolean isEnable() {
return true;
}
/**
* 跳转到错误页
* @param requestContext
* @return
*/
private Event getError(final RequestContext requestContext) {
final MessageContext messageContext = requestContext.getMessageContext();
messageContext.addMessage(new MessageBuilder().error().code(CODE).build());
return getEventFactorySupport().event(this, CODE);
}
}
注册:
@ConditionalOnMissingBean(name = "validateLoginCaptchaAction")
@Bean
@RefreshScope
public Action validateLoginCaptchaAction() {
ValidateLoginCaptchaAction validateCaptchaAction = new ValidateLoginCaptchaAction(captchaResultProvider);
return validateCaptchaAction;
}
流程设置:
package com.carl.sso.support.captcha.config;
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.web.flow.AbstractCasWebflowConfigurer;
import org.apereo.cas.web.flow.CasWebflowConstants;
import org.springframework.context.ApplicationContext;
import org.springframework.webflow.definition.registry.FlowDefinitionRegistry;
import org.springframework.webflow.engine.ActionState;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.execution.Action;
import org.springframework.webflow.engine.builder.support.FlowBuilderServices;
import java.util.ArrayList;
import java.util.List;
/**
* @author Carl
* @date 2017/10/30
*/
public class ValidateWebflowConfigurer extends AbstractCasWebflowConfigurer {
/**
* 校验码动作
*/
public static final String VALIDATE_CAPTCHA_ACTION = "validateCaptchaAction";
public ValidateWebflowConfigurer(FlowBuilderServices flowBuilderServices, FlowDefinitionRegistry loginFlowDefinitionRegistry, ApplicationContext applicationContext, CasConfigurationProperties casProperties) {
super(flowBuilderServices, loginFlowDefinitionRegistry);
}
@Override
protected void doInitialize() throws Exception {
createLoginValidateValidateFlow();
}
/**
* 登录校验流程
*/
private void createLoginValidateValidateFlow() {
final Flow flow = getLoginFlow();
if (flow != null) {
final ActionState state = (ActionState) flow.getState(CasWebflowConstants.TRANSITION_ID_REAL_SUBMIT);
final List<Action> currentActions = new ArrayList<>();
state.getActionList().forEach(currentActions::add);
currentActions.forEach(a -> state.getActionList().remove(a));
state.getActionList().add(createEvaluateAction("validateLoginCaptchaAction"));
currentActions.forEach(a -> state.getActionList().add(a));
state.getTransitionSet().add(createTransition("captchaError", CasWebflowConstants.STATE_ID_INIT_LOGIN_FORM));
}
}
}
测试效果
以上代码可能不全,若有兴趣可以点击下面的连接去访问:
发现一些意外的事情可以考虑翻翻前面的博客进行学习哦
作者联系方式
如果技术的交流或者疑问可以联系或者提出issue。
邮箱:huang.wenbin@foxmail.com
QQ: 756884434 (请注明:SSO-CSDN)