/**
* Copyright (c) 2005-2010 springside.org.cn
*
* Licensed under the Apache License, Version 2.0 (the "License");
*
* $Id: JCaptchaFilter.java 1213 2010-09-11 16:28:22Z calvinxiu $
*/
package org.springside.modules.security.jcaptcha;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.web.context.support.WebApplicationContextUtils;
import org.springside.modules.utils.web.ServletUtils;
import com.octo.captcha.service.CaptchaService;
import com.octo.captcha.service.CaptchaServiceException;
/**
* 集成JCaptcha验证码的Filter.
*
* 可通过配置与SpringSecurity相同的登录表单处理URL与身份验证失败URL,实现与SpringSecurity的集成.
* 另本filter主要演示与SpringSecurity的集成方式,用户可参考其他验证码方案的集成.
*
* 在web.xml中配置的参数包括:
* 1.failureUrl -- 身份验证失败后跳转的URL, 需与SpringSecurity中的配置保持一致, 无默认值必须配置.
* 2.filterProcessesUrl -- 登录表单处理URL, 需与SpringSecurity中的配置一致, 默认为"/j_spring_security_check".
* 3.captchaServiceId -- captchaService在Spring ApplicationContext中的bean id,默认为"captchaService".
* 4.captchaParamter -- 登录表单中验证码Input框的名称, 默认为"j_captcha".
* 5.autoPassValue -- 用于自动化功能测试的自动通过值, 默认该值不存在.
*
*
* 具体应用参考showcase示例的web.xml与login.jsp.
*
* @author calvin
*/
public class JCaptchaFilter implements Filter {
//web.xml中的参数名定义
public static final String PARAM_CAPTCHA_PARAMTER_NAME = "captchaParamterName";
public static final String PARAM_CAPTCHA_SERVICE_ID = "captchaServiceId";
public static final String PARAM_FILTER_PROCESSES_URL = "filterProcessesUrl";
public static final String PARAM_FAILURE_URL = "failureUrl";
public static final String PARAM_AUTO_PASS_VALUE = "autoPassValue";
//默认值定义
public static final String DEFAULT_FILTER_PROCESSES_URL = "/j_spring_security_check";
public static final String DEFAULT_CAPTCHA_SERVICE_ID = "captchaService";
public static final String DEFAULT_CAPTCHA_PARAMTER_NAME = "j_captcha";
private static Logger logger = LoggerFactory.getLogger(JCaptchaFilter.class);
private String failureUrl;
private String filterProcessesUrl = DEFAULT_FILTER_PROCESSES_URL;
private String captchaServiceId = DEFAULT_CAPTCHA_SERVICE_ID;
private String captchaParamterName = DEFAULT_CAPTCHA_PARAMTER_NAME;
private String autoPassValue;
private CaptchaService captchaService;
/**
* Filter回调初始化函数.
*/
public void init(final FilterConfig fConfig) throws ServletException {
initParameters(fConfig);
initCaptchaService(fConfig);
}
/**
* 初始化web.xml中定义的filter init-param.
*/
protected void initParameters(final FilterConfig fConfig) {
if (StringUtils.isBlank(fConfig.getInitParameter(PARAM_FAILURE_URL))) {
throw new IllegalArgumentException("CaptchaFilter缺少failureUrl参数");
}
failureUrl = fConfig.getInitParameter(PARAM_FAILURE_URL);
if (StringUtils.isNotBlank(fConfig.getInitParameter(PARAM_FILTER_PROCESSES_URL))) {
filterProcessesUrl = fConfig.getInitParameter(PARAM_FILTER_PROCESSES_URL);
}
if (StringUtils.isNotBlank(fConfig.getInitParameter(PARAM_CAPTCHA_SERVICE_ID))) {
captchaServiceId = fConfig.getInitParameter(PARAM_CAPTCHA_SERVICE_ID);
}
if (StringUtils.isNotBlank(fConfig.getInitParameter(PARAM_CAPTCHA_PARAMTER_NAME))) {
captchaParamterName = fConfig.getInitParameter(PARAM_CAPTCHA_PARAMTER_NAME);
}
if (StringUtils.isNotBlank(fConfig.getInitParameter(PARAM_AUTO_PASS_VALUE))) {
autoPassValue = fConfig.getInitParameter(PARAM_AUTO_PASS_VALUE);
}
}
/**
* 从ApplicatonContext获取CaptchaService实例.
*/
protected void initCaptchaService(final FilterConfig fConfig) {
ApplicationContext context = WebApplicationContextUtils.getWebApplicationContext(fConfig.getServletContext());
captchaService = (CaptchaService) context.getBean(captchaServiceId);
}
/**
* Filter回调退出函数.
*/
public void destroy() {
}
/**
* Filter回调请求处理函数.
*/
public void doFilter(final ServletRequest theRequest, final ServletResponse theResponse, final FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) theRequest;
HttpServletResponse response = (HttpServletResponse) theResponse;
String servletPath = request.getServletPath();
//符合filterProcessesUrl为验证处理请求,其余为生成验证图片请求.
if (StringUtils.startsWith(servletPath, filterProcessesUrl)) {
boolean validated = validateCaptchaChallenge(request);
if (validated) {
chain.doFilter(request, response);
} else {
redirectFailureUrl(request, response);
}
} else {
genernateCaptchaImage(request, response);
}
}
/**
* 生成验证码图片.
*/
protected void genernateCaptchaImage(final HttpServletRequest request, final HttpServletResponse response)
throws IOException {
ServletUtils.setDisableCacheHeader(response);
response.setContentType("image/jpeg");
ServletOutputStream out = response.getOutputStream();
try {
String captchaId = request.getSession(true).getId();
BufferedImage challenge = (BufferedImage) captchaService.getChallengeForID(captchaId, request.getLocale());
ImageIO.write(challenge, "jpg", out);
out.flush();
} catch (CaptchaServiceException e) {
logger.error(e.getMessage(), e);
} finally {
out.close();
}
}
/**
* 验证验证码.
*/
protected boolean validateCaptchaChallenge(final HttpServletRequest request) {
try {
String captchaID = request.getSession().getId();
String challengeResponse = request.getParameter(captchaParamterName);
//自动通过值存在时,检验输入值是否等于自动通过值
if (StringUtils.isNotBlank(autoPassValue) && autoPassValue.equals(challengeResponse)) {
return true;
}
return captchaService.validateResponseForID(captchaID, challengeResponse);
} catch (CaptchaServiceException e) {
logger.error(e.getMessage(), e);
return false;
}
}
/**
* 跳转到失败页面.
*
* 可在子类进行扩展, 比如在session中放入SpringSecurity的Exception.
*/
protected void redirectFailureUrl(final HttpServletRequest request, final HttpServletResponse response)
throws IOException {
response.sendRedirect(request.getContextPath() + failureUrl);
}
}