Integrate JCaptcha With Spring Security Framework
Integrate JCaptcha With Spring Security Framework
JCaptcha with
Spring Security
Introduction
This document explains how to integrate Jcaptcha with Spring Security framework. Currently,
Jcaptcha verifier is written inside Spring Security's Authentication Manager. However, there is a
more optimized way in Jcaptcha is not a part of Spring Security's Authentication Manager which
which I am still exploring.
<repositories>
<repository>
<id>sourceforge-releases</id>
<name>Sourceforge Releases</name>
<url>https://oss.sonatype.org/content/repositories/source
forge-releases</url>
</repository>
</repositories>
<properties>
<org.richfaces.bom.version>4.3.3.Final</org.richfaces.bom.version>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.richfaces</groupId>
<artifactId>richfaces-bom</artifactId>
<version>${org.richfaces.bom.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.richfaces.ui</groupId>
<artifactId>richfaces-components-ui</artifactId>
</dependency>
<dependency>
<groupId>org.richfaces.core</groupId>
<artifactId>richfaces-core-impl</artifactId>
</dependency>
<dependency>
<groupId>javax.faces</groupId>
<artifactId>javax.faces-api</artifactId>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.faces</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.20</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
</dependency>
<dependency>
<groupId>net.sourceforge.jexcelapi</groupId>
<artifactId>jxl</artifactId>
<version>2.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>3.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>3.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>3.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>3.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>3.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>3.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-instrument-tomcat</artifactId>
<version>3.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>3.2.12.RELEASE</version>
</dependency>
<dependency>
<groupId>com.octo.captcha</groupId>
<artifactId>jcaptcha-integration-simpleservlet</artifactId>
<version>2.0-alpha-1</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>compile</scope>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-ext</artifactId>
<scope>compile</scope>
<version>1.6.4</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<scope>compile</scope>
<version>1.6.4</version>
</dependency>
</dependencies>
<build>
<resources>
<resource>
<filtering>true</filtering>
<directory>src/test/resources</directory>
<includes>
<include>**/*.properties</include>
</includes>
<excludes>
<exclude>**/*local.properties</exclude>
</excludes>
</resource>
<resource>
<directory>src\main\resources</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
<include>**/*.vm</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<additionalClasspathElements>
<additionalClasspathElement>src\main\webapp\WEBINF</additionalClasspathElement>
</additionalClasspathElements>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.0</version>
<configuration>
<server>devserver</server>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<webXml>src\main\webapp\WEB-INF\web.xml</webXml>
</configuration>
</plugin>
</plugins>
</build>
</project>
The code in larger fint size is what is added extra for Jcaptcha.
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>*.faces</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Faces Servlet</servlet-name>
<url-pattern>/faces/*</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>login.faces</welcome-file>
</welcome-file-list>
<session-config>
<session-timeout>30</session-timeout>
</session-config>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filterclass>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<servlet>
<servlet-name>jcaptcha</servlet-name>
<servletclass>com.octo.captcha.module.servlet.image.SimpleImageCa
ptchaServlet</servlet-class>
<load-on-startup>0</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>jcaptcha</servlet-name>
<url-pattern>/jcaptcha.jpg</url-pattern>
</servlet-mapping>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/security-context.xml,
/WEB-INF/captcha-context.xml</param-value>
</context-param>
<listener>
<listenerclass>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
Create captcha-context.xml
This file contains all the details about how Jcaptcha image is created at runtime.
<?xml version="1.0" encoding="UTF-8"?>
<!-- JCaptch Beans -->
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.1.xsd">
<bean id="captchaService"
class="com.octo.captcha.service.multitype.GenericManageableCaptchaService">
<constructor-arg index="0">
<ref bean="captchaEngine" />
</constructor-arg>
<constructor-arg type="int" index="1" value="180" /> <!-minGuarantedStorageDelayInSeconds -->
<constructor-arg type="int" index="2" value="180000" /> <!-maxCaptchaStoreSize -->
<constructor-arg type="int" index="3" value="75000" /> <!-captchaStoreLoadBeforeGarbageCollection -->
</bean>
<!-- There are several Engines pre-configured, but as we want to control
configuration, we have to use the GenericCaptchaEngine, which is built with
a list of captcha factories (factories are the real producer of captchas) -->
<bean id="captchaEngine"
class="com.octo.captcha.engine.GenericCaptchaEngine">
<constructor-arg index="0">
<list>
<ref bean="captchaFactory" />
</list>
</constructor-arg>
</bean>
<!-- CaptchaFactory needs A word generator, to create the text to read.
A wordToImage, to generate the captcha from the text. -->
<bean id="captchaFactory"
class="com.octo.captcha.image.gimpy.GimpyFactory">
<constructor-arg>
<ref bean="wordgen" />
</constructor-arg>
<constructor-arg>
<ref bean="wordtoimage" />
</constructor-arg>
</bean>
<!-- A WordGenerator creates a text to be read, it can be random, be a common
implementation take words from a list, and can make composition to create
a text easier to read for a human being. In the example the WordGenerator
needs a Dictionnary to get real words from. -->
<bean id="wordgen"
class="com.octo.captcha.component.word.wordgenerator.DictionaryWordGenerator">
<constructor-arg>
<ref bean="filedict" />
</constructor-arg>
</bean>
<!-- A Dictionary provides words, this one reads words from the one
provided by default, with almost 6000 english words. -->
<bean id="filedict"
class="com.octo.captcha.component.word.FileDictionary">
<constructor-arg index="0">
<value>toddlist</value>
</constructor-arg>
</bean>
<!-- WordToImage component is needed which is mainly created with three
others components A font generator, A background generator and A Text paster -->
<bean id="wordtoimage"
class="com.octo.captcha.component.image.wordtoimage.ComposedWordToImage">
<constructor-arg index="0">
<ref bean="fontGenRandom" />
</constructor-arg>
<constructor-arg index="1">
<ref bean="backGenUni" />
</constructor-arg>
<constructor-arg index="2">
<ref bean="simpleWhitePaster" />
</constructor-arg>
</bean>
<!-- A FontGenerator provide Fonts to a WordToImage -->
<bean id="fontGenRandom"
class="com.octo.captcha.component.image.fontgenerator.RandomFontGenerator">
<constructor-arg index="0">
<value>40</value>
</constructor-arg>
<constructor-arg index="1">
<value>50</value>
</constructor-arg>
<constructor-arg index="2">
<list>
<ref bean="fontArial" />
</list>
</constructor-arg>
</bean>
<bean id="fontArial" class="java.awt.Font">
<constructor-arg index="0">
<value>Arial</value>
</constructor-arg>
<constructor-arg index="1">
<value>0</value>
</constructor-arg>
<constructor-arg index="2">
<value>10</value>
</constructor-arg>
</bean>
<!-- The BackgrountGenerator component can be very simple like in the example,
single color, or more complex with real picture, or fancy computed shapes.
The first two arguments are always, the size (length and height) of the
resulting image -->
<bean id="backGenUni"
class="com.octo.captcha.component.image.backgroundgenerator.UniColorBackgroundG
enerator">
<constructor-arg index="0">
<value>225</value>
</constructor-arg>
<constructor-arg index="1">
<value>75</value>
</constructor-arg>
</bean>
<!-- The TextPaster pastes the text on the background. Commons arguments
for TextPaster are Minimal length of the text, Maximal length of the text
and A color generator component to create the text color -->
<bean id="simpleWhitePaster"
class="com.octo.captcha.component.image.textpaster.RandomTextPaster">
<constructor-arg type="java.lang.Integer" index="0">
<value>6</value>
</constructor-arg>
<constructor-arg type="java.lang.Integer" index="1">
<value>6</value>
</constructor-arg>
<constructor-arg type="java.awt.Color" index="2">
<ref bean="colorBlue" />
</constructor-arg>
</bean>
<bean id="colorBlue" class="java.awt.Color">
<constructor-arg index="0" type="int">
<value>0</value>
</constructor-arg>
<constructor-arg index="1" type="int">
<value>0</value>
</constructor-arg>
<constructor-arg index="2" type="int">
<value>255</value>
</constructor-arg>
</bean>
</beans>
<custom-filter ref="captchaCaptureFilter"
before="FORM_LOGIN_FILTER" />
<custom-filter after="CONCURRENT_SESSION_FILTER"
ref="concurrencyFilter" />
<session-management
session-authentication-strategy-ref="sas" />
<headers />
</http>
<beans:bean id="captchaCaptureFilter"
class="XXXX.captcha.CaptchaCaptureFilter" />
<beans:bean id="UserDetailsManager"
class="XXXX.security.UserDetailsManager" />
<beans:bean id="concurrencyFilter"
class="org.springframework.security.web.session.ConcurrentSessionFilter">
<beans:property name="sessionRegistry" ref="sessionRegistry" />
<beans:property name="expiredUrl" value="/sessionExpired" />
</beans:bean>
<beans:bean id="myAuthFilter"
class="org.springframework.security.web.authentication.UsernamePasswordAuthenti
cationFilter">
<beans:property name="sessionAuthenticationStrategy"
ref="sas" />
<beans:property name="authenticationManager"
ref="authenticationManager" />
</beans:bean>
<beans:bean id="sas"
class="org.springframework.security.web.authentication.session.CompositeSession
AuthenticationStrategy">
<beans:constructor-arg>
<beans:list>
<beans:bean
class="org.springframework.security.web.authentication.session.ConcurrentSessio
nControlAuthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry" />
<beans:property name="maximumSessions" value="1"
/>
<beans:property name="exceptionIfMaximumExceeded"
value="true" />
</beans:bean>
<beans:bean
class="org.springframework.security.web.authentication.session.SessionFixationP
rotectionStrategy">
</beans:bean>
<beans:bean
class="org.springframework.security.web.authentication.session.RegisterSessionA
uthenticationStrategy">
<beans:constructor-arg ref="sessionRegistry" />
</beans:bean>
</beans:list>
</beans:constructor-arg>
</beans:bean>
<beans:bean id="sessionRegistry"
class="org.springframework.security.core.session.SessionRegistryImpl" />
<!-- Select users and user_roles from database -->
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref="UserDetailsManager">
<password-encoder hash="plaintext">
</password-encoder>
</authentication-provider>
</authentication-manager>
</beans:beans>
</navigation-case>
</navigation-rule>
<navigation-rule>
<navigation-case>
<from-outcome>login</from-outcome>
<to-view-id>/login.xhtml</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
<navigation-rule>
<navigation-case>
<from-outcome>sessionExpired</from-outcome>
<to-view-id>/sessionExpired.xhtml</to-view-id>
<redirect />
</navigation-case>
</navigation-rule>
<application>
<elresolver>org.springframework.web.jsf.el.SpringBeanFacesELResolver</el-resolver>
</application>
</faces-config>
Please note that <application>....</application> tag with the mentioned details above is very
important for Spring security configuration to work.
javax.faces.application.FacesMessage;
javax.faces.application.FacesMessage.Severity;
javax.faces.context.FacesContext;
javax.faces.context.ExternalContext;
/**
* @return the activeLink
*/
public String getActiveLink() {
if (FacesContext.getCurrentInstance().getExternalContext()
.getSessionMap().containsKey("ACTIVE_LINK")) {
this.activeLink = (String) FacesContext.getCurrentInstance()
.getExternalContext().getSessionMap().get("ACTIVE_
LINK");
} else {
this.activeLink = "welcome";
}
return activeLink;
}
public void setActiveLink(String activeLink) {
FacesContext.getCurrentInstance().getExternalContext().getSessionMap()
.put("ACTIVE_LINK", activeLink);
this.activeLink = activeLink;
}
public String getRealFileStoragePath() {
return FacesContext.getCurrentInstance().getExternalContext()
.getRealPath("/");
}
public String navigate(String activeLinkStr) {
if (null != activeLinkStr) {
setActiveLink(activeLinkStr);
}
return getActiveLink();
}
public void addFacesMessage(Severity sev, String msg) {
FacesContext.getCurrentInstance().addMessage(null,
new FacesMessage(sev, msg, ""));
}
public String getUserNameParam() {
if (FacesContext.getCurrentInstance().getExternalContext()
.getSessionMap().containsKey("USER_NAME_PARAM")) {
this.userNameParam = (String)
FacesContext.getCurrentInstance()
.getExternalContext().getSessionMap()
.get("USER_NAME_PARAM");
}
return userNameParam;
}
public boolean isLoggedInUser() {
ExternalContext extCtxt = FacesContext.getCurrentInstance()
.getExternalContext();
String remoteUser = extCtxt.getRemoteUser();
if (remoteUser != null) {
return true;
}
else return false;
}
/**
* @param userNameParam the userNameParam to set
*/
public void setUserNameParam(String userNameParam) {
this.userNameParam = userNameParam;
}
return "login";
return authenticationService;
}
/**
* @param authenticationService
*
the authenticationService to set
*/
public void setAuthenticationService(
AuthenticationService authenticationService) {
this.authenticationService = authenticationService;
}
/**
* @return the captchaString
*/
public String getCaptchaString() {
return captchaString;
}
/**
* @param captchaString the captchaString to set
*/
public void setCaptchaString(String captchaString) {
this.captchaString = captchaString;
}
}
java.sql.Connection;
java.sql.DriverManager;
java.sql.ResultSet;
java.sql.Statement;
}
}
public Connection getConn() {
return conn;
}
public void setConn(Connection conn) {
this.conn = conn;
}
public Statement getStmt() {
return stmt;
}
public void setStmt(Statement stmt) {
this.stmt = stmt;
}
public DriverManager getDriverManager() {
return driverManager;
}
javax.servlet.FilterChain;
javax.servlet.ServletException;
javax.servlet.http.HttpServletRequest;
javax.servlet.http.HttpServletResponse;
import org.springframework.web.filter.OncePerRequestFilter;
public class CaptchaCaptureFilter extends OncePerRequestFilter {
private String userCaptchaResponse;
private HttpServletRequest request;
@Override
public void doFilterInternal(HttpServletRequest req,
HttpServletResponse res, FilterChain chain) throws
IOException,
ServletException {
// 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("loginForm:jcaptchaString") != null) {
request = req;
userCaptchaResponse =
req.getParameter("loginForm:jcaptchaString");
}
/**
* @return the userCaptchaResponse
*/
public String getUserCaptchaResponse() {
return userCaptchaResponse;
}
/**
* @param userCaptchaResponse
*
the userCaptchaResponse to set
*/
public void setUserCaptchaResponse(String userCaptchaResponse) {
this.userCaptchaResponse = userCaptchaResponse;
}
/**
* @return the request
*/
public HttpServletRequest getRequest() {
return request;
}
/**
* @param request
*
the request to set
*/
public void setRequest(HttpServletRequest request) {
this.request = request;
}
{
flag1 = true;
captchaCaptureFilter.setUserCaptchaResponse(null);
}
}
Authentication authenticate = authenticationManager
.authenticate(new
UsernamePasswordAuthenticationToken(
username, password));
if (authenticate.isAuthenticated()) {
SecurityContextHolder.getContext().setAuthentication(
authenticate);
flag2 = true;
}
correct
}
@Override
public void logout() {
SecurityContextHolder.getContext().setAuthentication(null);
}
/**
* @return the flag1
*/
public boolean isFlag1() {
return flag1;
}
/**
* @param flag1 the flag1 to set
*/
public void setFlag1(boolean flag1) {
this.flag1 = flag1;
}
/**
* @return the flag2
*/
public boolean isFlag2() {
return flag2;
}
/**
* @param flag2 the flag2 to set
*/
public void setFlag2(boolean flag2) {
this.flag2 = flag2;
}
/**
* @return the captchaPassed
*/
public boolean isCaptchaPassed() {
return captchaPassed;
}
/**
* @param captchaPassed the captchaPassed to set
*/
public void setCaptchaPassed(boolean captchaPassed) {
this.captchaPassed = captchaPassed;
}
/**
* @return the captchaCaptureFilter
*/
public CaptchaCaptureFilter getCaptchaCaptureFilter() {
return captchaCaptureFilter;
}
/**
* @param captchaCaptureFilter the captchaCaptureFilter to set
*/
public void setCaptchaCaptureFilter(CaptchaCaptureFilter
captchaCaptureFilter) {
this.captchaCaptureFilter = captchaCaptureFilter;
}
}
java.sql.ResultSet;
java.sql.SQLException;
org.springframework.stereotype.Repository;
XXXX.database.DbConnectionController;
@Repository
public class LoginUser {
DbConnectionController dbConnectionController;
ResultSet resultSet;
public UserEntity getUser(String userName) throws ClassNotFoundException {
UserEntity user = new UserEntity();
dbConnectionController = new DbConnectionController();
String query = "SELECT * FROM LOGIN WHERE USERNAME = '" + userName
+ "'";
try {
resultSet =
dbConnectionController.getStmt().executeQuery(query);
if (resultSet.next()) {
user.setUsername(resultSet.getString(1));
user.setPassword(resultSet.getString(2));
user.setSuperUser(resultSet.getString(3));
user.setFullname(resultSet.getString(4));
user.setDepartment(resultSet.getString(5));
dbConnectionController.setConn(null);
dbConnectionController.setStmt(null);
this.dbConnectionController = null;
} catch (SQLException e) {
e.printStackTrace();
return null;
return user;
}
/**
* @return the dbConnectionController
*/
public DbConnectionController getDbConnectionController() {
return dbConnectionController;
}
/**
* @param dbConnectionController
*
the dbConnectionController to set
*/
public void setDbConnectionController(
DbConnectionController dbConnectionController) {
this.dbConnectionController = dbConnectionController;
}
/**
* @return the resultSet
*/
public ResultSet getResultSet() {
return resultSet;
}
/**
* @param resultSet
*
the resultSet to set
*/
public void setResultSet(ResultSet resultSet) {
this.resultSet = resultSet;
}
}
org.springframework.security.core.userdetails.User;
org.springframework.security.core.userdetails.UsernameNotFoundException;
org.springframework.stereotype.Service;
org.springframework.security.core.userdetails.UserDetails;
org.springframework.security.core.userdetails.UserDetailsService;
org.springframework.transaction.annotation.Transactional;
import org.springframework.security.core.authority.AuthorityUtils;
@Service
public class UserDetailsManager implements UserDetailsService {
@Override
@Transactional
public UserDetails loadUserByUsername(final String userName)
throws UsernameNotFoundException {
boolean
boolean
boolean
boolean
enabled = true;
accountNonExpired = true;
credentialsNonExpired = true;
accountNonLocked = true;
<h:inputText id="jcaptchaString"
value="#{loginUIController.captchaString}" />
<br /> <br />
<h:commandButton value="Submit"
action="#{loginUIController.login()}" />
<br /> <br />
<h:outputText value="#{loginUIController.message}"
style="color:red" />
</fieldset>
</center>
</h:form>
</h:body>
</html>