如何在 Spring Security 中集成 JCaptcha
Posted
技术标签:
【中文标题】如何在 Spring Security 中集成 JCaptcha【英文标题】:How to Integrate JCaptcha in Spring Security 【发布时间】:2012-10-02 19:55:57 【问题描述】:在开始回答之前,我知道有 ReCaptcha,它更简单、更容易,但我不能使用它。生产服务器不在线。所以我们开始吧。
我在 maven Project 和 weblogic 上使用 Spring mvc 3 和 Spring Security 作为 Web 服务器(开发时的码头)。我会非常具体地介绍这一点。
在查看我的配置和文件之前,我想向您展示我的问题列表:
我在 JCaptcha 之前用相同的编码结构尝试了 ReCaptcha,效果很好。 logger.debug 根本不出现在 CaptchaCaptureFilter 类和/或 CaptchaVerifierFilter 类中(而它出现在 ArtajasaAuthenticationProvider 类中)。 我可以看到验证码图像,但无论答案是什么,它总是无效。在当前状态下,它在jetty和weblogic中都不起作用,但是如果我将自定义过滤器位置更改为下面的那个,它只能在jetty中起作用。
<custom-filter ref="captchaCaptureFilter" position="FIRST"/>
<custom-filter ref="captchaVerifierFilter" after="FIRST"/>
感谢您的观看,非常感谢您回答我的问题。 以下是详细信息。
JCaptcha 的存储库是这个:
<repository>
<id>sourceforge-releases</id>
<name>Sourceforge Releases</name>
<url>https://oss.sonatype.org/content/repositories/sourceforge-releases</url>
</repository>
<dependency>
<groupId>com.octo.captcha</groupId>
<artifactId>jcaptcha-integration-simple-servlet</artifactId>
<version>2.0-alpha-1</version>
</dependency>
这是我在 .xml 文件中所做的一些配置:
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml
/WEB-INF/spring/spring-security.xml
</param-value>
</context-param>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
<servlet>
<servlet-name>jcaptcha</servlet-name>
<servlet-class>com.octo.captcha.module.servlet.image.SimpleImageCaptchaServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>jcaptcha</servlet-name>
<url-pattern>/jcaptcha.jpg</url-pattern>
</servlet-mapping>
spring-security.xml
<http auto-config="true" use-expressions="true">
<intercept-url pattern="/resources/**" access="permitAll()" />
<intercept-url pattern="/jcaptcha.jpg" access="permitAll()" />
<intercept-url pattern="/**" access="isAuthenticated()" />
<form-login login-page="/session/login/" default-target-url="/"
authentication-failure-url="/session/loginfailed/" />
<logout logout-success-url="/session/logout/" />
<access-denied-handler error-page="/session/403/" />
<!--JCaptcha Filtering-->
<custom-filter ref="captchaCaptureFilter" before="FORM_LOGIN_FILTER"/>
<custom-filter ref="captchaVerifierFilter" after="FORM_LOGIN_FILTER"/>
<anonymous />
</http>
<!-- For capturing CAPTCHA fields -->
<beans:bean id="captchaCaptureFilter" class="com.util.CaptchaCaptureFilter" />
<!-- For verifying CAPTCHA fields -->
<!-- Private key is assigned by the JCaptcha service -->
<beans:bean id="captchaVerifierFilter" class="com.util.CaptchaVerifierFilter"
p:failureUrl="/session/loginfailed/"
p:captchaCaptureFilter-ref="captchaCaptureFilter"/>
<beans:bean id="customAuthFilter" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
<beans:property name="sessionAuthenticationStrategy" ref="sas"/>
<beans:property name="authenticationManager" ref="authenticationManager" />
<beans:property name="allowSessionCreation" value="true" />
</beans:bean>
<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
<beans:constructor-arg name="sessionRegistry" ref="sessionRegistry"/>
<beans:property name="maximumSessions" value="1" />
</beans:bean>
<beans:bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
<beans:bean id="userService" class="com.service.mybatis.UserManager" />
<beans:bean id="customAuthenticationProvider" class="com.util.ArtajasaAuthenticationProvider" />
<authentication-manager alias="authenticationManager">
<authentication-provider ref="customAuthenticationProvider" />
</authentication-manager>
<beans:bean id="accessDeniedHandler" class="com.util.ThouShaltNoPass">
<beans:property name="accessDeniedURL" value="/session/403/" />
</beans:bean>
这些是java类:
ArtajasaAuthenticationProvider.java
public class ArtajasaAuthenticationProvider implements AuthenticationProvider
@Autowired
private UserService userService;
private Logger logger = LoggerFactory.getLogger(ArtajasaAuthenticationProvider.class);
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException
String username = String.valueOf(authentication.getPrincipal());
String password = String.valueOf(authentication.getCredentials());
logger.debug("Checking authentication for user ", username);
if (StringUtils.isBlank(username)
|| StringUtils.isBlank(password))
throw new BadCredentialsException("No Username and/or Password Provided.");
else
Pengguna user = userService.select(username);
if (user == null)
throw new BadCredentialsException("Invalid Username and/or Password.");
if (user.getPassword().equals(new PasswordUtil().generateHash(password, user.getSalt())))
List<GrantedAuthority> authorityList = (List<GrantedAuthority>) userService.getAuthorities(user);
return new UsernamePasswordAuthenticationToken(username, password, authorityList);
else
throw new BadCredentialsException("Invalid Username and/or Password.");
@Override
public boolean supports(Class<?> authentication)
return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
CaptchaCaptureFilter.java
public class CaptchaCaptureFilter extends OncePerRequestFilter
protected Logger logger = Logger.getLogger(CaptchaCaptureFilter.class);
private String userCaptchaResponse;
private HttpServletRequest request;
@Override
public void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain chain) throws IOException, ServletException
logger.debug("Captcha capture filter");
// Assign values only when user has submitted a Captcha value.
// Without this condition the values will be reset due to redirection
// and CaptchaVerifierFilter will enter an infinite loop
if (req.getParameter("jcaptcha") != null)
request = req;
userCaptchaResponse = req.getParameter("jcaptcha");
logger.debug("userResponse: " + userCaptchaResponse);
// Proceed with the remaining filters
chain.doFilter(req, res);
public String getUserCaptchaResponse()
return userCaptchaResponse;
public void setUserCaptchaResponse(String userCaptchaResponse)
this.userCaptchaResponse = userCaptchaResponse;
public HttpServletRequest getRequest()
return request;
public void setRequest(HttpServletRequest request)
this.request = request;
CaptchaVerifierFilter.java
public class CaptchaVerifierFilter extends OncePerRequestFilter
protected Logger logger = Logger.getLogger(CaptchaVerifierFilter.class);
private String failureUrl;
private CaptchaCaptureFilter captchaCaptureFilter;
// Inspired by log output: AbstractAuthenticationProcessingFilter.java:unsuccessfulAuthentication:320)
// Delegating to authentication failure handlerorg.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler@15d4273
private SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
@Override
public void doFilterInternal(HttpServletRequest req, HttpServletResponse res,
FilterChain chain) throws IOException, ServletException
logger.debug("Captcha verifier filter");
logger.debug("userResponse: " + captchaCaptureFilter.getUserCaptchaResponse());
// Assign values only when user has submitted a Captcha value
if (captchaCaptureFilter.getUserCaptchaResponse() != null)
// Send HTTP request to validate user's Captcha
boolean captchaPassed = SimpleImageCaptchaServlet.validateResponse(captchaCaptureFilter.getRequest(), captchaCaptureFilter.getUserCaptchaResponse());
// Check if valid
if (!captchaPassed)
logger.debug("Captcha is invalid!");
// Redirect user to login page
failureHandler.setDefaultFailureUrl(failureUrl);
failureHandler.onAuthenticationFailure(req, res, new BadCredentialsException("Captcha invalid! " + captchaCaptureFilter.getRequest() + " " + captchaCaptureFilter.getUserCaptchaResponse()));
else
logger.debug("Captcha is valid!");
// Reset Captcha fields after processing
// If this method is skipped, everytime we access a page
// CaptchaVerifierFilter will infinitely send a request to the Google Captcha service!
resetCaptchaFields();
// Proceed with the remaining filters
chain.doFilter(req, res);
/**
* Reset Captcha fields
*/
public void resetCaptchaFields()
captchaCaptureFilter.setUserCaptchaResponse(null);
public String getFailureUrl()
return failureUrl;
public void setFailureUrl(String failureUrl)
this.failureUrl = failureUrl;
public CaptchaCaptureFilter getCaptchaCaptureFilter()
return captchaCaptureFilter;
public void setCaptchaCaptureFilter(CaptchaCaptureFilter captchaCaptureFilter)
this.captchaCaptureFilter = captchaCaptureFilter;
最后但同样重要的是,login.jsp
<%@ taglib prefix='c' uri='http://java.sun.com/jstl/core_rt' %>
<form id="login" name="f" action="<c:url value='/j_spring_security_check'/>" method="POST">
<div class="container">
<div class="content">
<div class="row">
<div class="login-form">
<h3>Login</h3>
<br />
<fieldset>
<div class="clearfix">
username: ecr
<input type="text" name='j_username' value='<c:if test="$not empty param.login_error"><c:out value="$SPRING_SECURITY_LAST_USERNAME"/></c:if>' placeholder="username@artajasa.co.id">
</div>
<div class="clearfix">
password: ecr123
<input type="password" name='j_password' placeholder="password">
</div>
<div class="clearfix">
<img src="../../jcaptcha.jpg" />
<br />
<input type="text" name="jcaptcha" placeholder="masukkan captcha" />
</div>
<br />
<button class="btn btn-primary" type="submit"><i class="icon-lock"></i> Sign in</button>
</fieldset>
</div>
</div>
</div>
<br />
<c:if test="$not empty error">
<div class="alert alert-error">
<button type="button" class="close" data-dismiss="alert"><i class="icon-remove"></i></button>
Login Failed, try again.<br />
<c:out value="$sessionScope['SPRING_SECURITY_LAST_EXCEPTION'].message"/>
</div>
</c:if>
</div>
【问题讨论】:
【参考方案1】:问题已解决!我找到了答案。所以我们毕竟不需要 CaptchaVerifierFilter。我在 AuthenticationProvider 中验证验证码。
这些是更改列表:
在 spring-security.xml 中,这个
<!--JCaptcha Filtering-->
<custom-filter ref="captchaCaptureFilter" before="FORM_LOGIN_FILTER"/>
<custom-filter ref="captchaVerifierFilter" after="FORM_LOGIN_FILTER"/>
成为这个人
<!--JCaptcha Filtering-->
<custom-filter ref="captchaCaptureFilter" before="FORM_LOGIN_FILTER"/>
删除
<!-- For verifying CAPTCHA fields -->
<!-- Private key is assigned by the JCaptcha service -->
<beans:bean id="captchaVerifierFilter" class="com.util.CaptchaVerifierFilter"
p:failureUrl="/session/loginfailed/"
p:captchaCaptureFilter-ref="captchaCaptureFilter"/>
并在此处验证验证码
<beans:bean id="customAuthenticationProvider" class="com.pusilkom.artajasa.ecr.backend.util.MyAuthenticationProvider"
p:captchaCaptureFilter-ref="captchaCaptureFilter"/>
【讨论】:
您能告诉我,验证提供者究竟在哪里进行验证码验证?【参考方案2】:我不确定这是否是正确的做法,但它非常适合我。 我创建了与您相同的类,但过滤器类代码的更改很少,security-context.xml 的更改也很小。
-
我在 CaptchaCaptureFilter 中验证验证码,并将验证结果存储在变量 iscaptchaPassed 中,该变量是 CaptchaCaptureFilter 中的一个属性。随着
iscaptchaPassed 我还将响应存储为验证码响应,以便稍后在 CaptchaVerifierFilter 中检查它是否为空。
公共类 CaptchaCaptureFilter 扩展 OncePerRequestFilter
private String captchaResponse;
private boolean iscaptchaPassed;
//setter 和 getter
@Override
public void doFilterInternal(HttpServletRequest req, HttpServletResponse res,FilterChain chain) throws IOException, ServletException
logger.info("Captcha capture filter");
String captcha_Response=req.getParameter("jcaptcha");
logger.info("response captcha captured : " +captcha_Response);
if(captcha_Response!=null)
iscaptchaPassed = SimpleImageCaptchaServlet.validateResponse(req, req.getParameter("jcaptcha"));
captchaResponse=captcha_Response;
logger.info("isCaptchaPassed value is "+iscaptchaPassed);
// Proceed with the remaining filters
chain.doFilter(req, res);
-
读取CaptchaVerifyFilter中注入的captchaCaptureFilter的值,如下处理。
公共类 CaptchaVerifierFilter 扩展 OncePerRequestFilter
protected Logger logger = LoggerFactory.getLogger(Filter.class);
private CaptchaCaptureFilter captchaCaptureFilter;
private String failureUrl;
//getters and setters**strong text**
private SimpleUrlAuthenticationFailureHandler failureHandler = new SimpleUrlAuthenticationFailureHandler();
@Override
public void doFilterInternal(HttpServletRequest req, HttpServletResponse res,FilterChain chain) throws IOException, ServletException
//logger.info("Captcha verifier filter");
boolean captchaPassed=captchaCaptureFilter.getIscaptchaPassed();
String captchaResponse=captchaCaptureFilter.getCaptchaResponse();
//logger.info("captcha captured :"+captchaResponse+" validation result of captcha : " +captchaPassed);
if(captchaResponse!=null)
if(captchaPassed)
logger.info("Captcha is valid!");
else
logger.info("Captcha is invalid!");
failureHandler.setDefaultFailureUrl(failureUrl);
failureHandler.onAuthenticationFailure(req, res, new BadCredentialsException("Captcha invalid!"));
resetCaptchaFields();
chain.doFilter(req, res);
/**
* Reset Captcha fields
*/
public void resetCaptchaFields()
captchaCaptureFilter.setCaptchaResponse(null);
captchaCaptureFilter.setIscaptchaPassed(false);;
-
在 security-context.xml 中更改以下内容。
security:custom-filter ref="captchaCaptureFilter" before="FIRST"
security:custom-filter ref="captchaVerifierFilter" after="FORM_LOGIN_FILTER"
我在验证验证码的所有过滤器之前添加 captchaCaptureFilter。 UserNameAndPasswordAuthFilter 之后使用验证结果,即 FORM_LOGIN_FILTER。
【讨论】:
【参考方案3】:我尝试了另一种验证 JCaptcha 的方法,即在 Spring Security Authentication 之前。首先,JCaptcha 将得到验证,如果正确,控制将转到 Spring Security Authentication。我也没有在 security-context.xml 中添加 CaptchaCaptureFilter。
以下是我尝试过的。它工作正常。
public String login()
this.captchaPassed = false;
// Check if captcha entered is correct. If yes then only proceed with
// Spring Security Authentication
this.captchaPassed = checkLoginCaptcha();
if (captchaPassed)
boolean success = authenticationService.login(userName, password);
if (success)
StringBuilder userNameBuilder = new StringBuilder();
userNameBuilder.append(userName);
FacesContext.getCurrentInstance().getExternalContext()
.getSessionMap()
.put("USER_NAME_PARAM", userNameBuilder.toString());
return ApplicationConstants.HOME_PAGE;
else
this.message = "Wrong Username or Password Entered. Please LOGIN again.";
this.userName = null;
this.password = null;
this.captchaString = null;
return ApplicationConstants.LOGIN_PAGE;
else
this.message = "Invalid captcha entered. Please LOGIN again.";
return ApplicationConstants.LOGIN_PAGE;
public boolean checkLoginCaptcha()
HttpServletRequest req = (HttpServletRequest) FacesContext
.getCurrentInstance().getExternalContext().getRequest();
String str = null;
boolean flag = false;
try
str = req.getParameter("loginForm:jcaptchaString");
if (str != null)
flag = SimpleImageCaptchaServlet.validateResponse(req, str);
catch (Exception e)
e.printStackTrace();
flag = false;
return flag;
【讨论】:
以上是关于如何在 Spring Security 中集成 JCaptcha的主要内容,如果未能解决你的问题,请参考以下文章
关于Boot应用中集成Spring Security你必须了解的那些事