The repository for JCaptcha is this one:
<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>
Here are some configuration I made in .xml files:
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"/> <!-- REMOVED 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 --> <!-- REMOVED beans:bean id="captchaVerifierFilter" class="com.util.CaptchaVerifierFilter" p:failureUrl="/session/loginfailed/" p:captchaCaptureFilter-ref="captchaCaptureFilter"/--> <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.MyAuthenticationProvider" p:captchaCaptureFilter-ref="captchaCaptureFilter" /> <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>
And these are the java classes:
MyAuthenticationProvider.java
public class MyAuthenticationProvider implements AuthenticationProvider { @Autowired private UserService userService; private Logger logger = LoggerFactory.getLogger(ArtajasaAuthenticationProvider.class); private CaptchaCaptureFilter captchaCaptureFilter; @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); logger.debug("userResponse: {}", captchaCaptureFilter.getUserCaptchaResponse()); if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { throw new BadCredentialsException("No Username and/or Password Provided."); } else if(StringUtils.isBlank(captchaCaptureFilter.getUserCaptchaResponse())) { throw new BadCredentialsException("Captcha Response is Empty"); } else { // 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 valid!"); resetCaptchaFields(); 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."); } } else { logger.debug("Captcha is invalid!"); resetCaptchaFields(); throw new BadCredentialsException("Invalid Captcha."); } } } @Override public boolean supports(Class<?> authentication) { return (UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication)); } /** * Reset Captcha fields */ public void resetCaptchaFields() { captchaCaptureFilter.setUserCaptchaResponse(null); } public CaptchaCaptureFilter getCaptchaCaptureFilter() { return captchaCaptureFilter; } public void setCaptchaCaptureFilter(CaptchaCaptureFilter captchaCaptureFilter) { this.captchaCaptureFilter = captchaCaptureFilter; } }
CaptchaCaptureFilter.java
public class CaptchaCaptureFilter extends OncePerRequestFilter { private 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; } }
Last but not least, 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="[email protected]"> </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>
done!