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
以上即完成一個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));
}
}
}
測試效果

以上代碼可能不全,若有興趣可以點選下面的連接配接去通路:
下載下傳代碼嘗試:
其他版本可以到GitHub或者碼雲檢視
發現一些意外的事情可以考慮翻翻前面的部落格進行學習哦
作者聯系方式
如果技術的交流或者疑問可以聯系或者提出issue。
郵箱:[email protected]
QQ: 756884434 (請注明:SSO-CSDN)