How to Integrate JCaptcha into Spring Security

Before starting the answer, I know that there is ReCaptcha, which is simpler and simpler, but I can not use it. The production server is not connected to the network. So let's go.

I am using Spring mvc 3 with Spring Security on the maven Project and weblogic as a web server (dock at development time). I will be very specific in this matter.

Before I see my configurations and files, I would like to show you a list of my problems:

  • I tried ReCaptcha before JCaptcha with the same coding structure and it works fine.
  • logger.debug does not appear at all in the CaptchaCaptureFilter and / or CaptchaVerifierFilter class (as long as it appears in the ArtajasaAuthenticationProvider class).
  • I see a captcha image, but regardless of the answer, it is always invalid.
  • In its current state, it does not work on the pier or in the weblog, but if I changed the position of the custom filter to the one below, it only works on the pier.

    <custom-filter ref="captchaCaptureFilter" position="FIRST"/> <custom-filter ref="captchaVerifierFilter" after="FIRST"/> 

Thanks for watching and many thanks for answering my question. See below for details.

The repository for JCaptcha is:

 <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 settings I made in the .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"/> <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> 

And these are the java classes:

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.se curity.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 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; } } 

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=" 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> 

+4
source share
3 answers

The problem is SOLVED! I have found the answer. Therefore, we do not need a CaptchaVerifierFilter. I check captcha inside AuthenticationProvider.

this is a list of changes:

in spring -security.xml, this

 <!--JCaptcha Filtering--> <custom-filter ref="captchaCaptureFilter" before="FORM_LOGIN_FILTER"/> <custom-filter ref="captchaVerifierFilter" after="FORM_LOGIN_FILTER"/> 

will become this

 <!--JCaptcha Filtering--> <custom-filter ref="captchaCaptureFilter" before="FORM_LOGIN_FILTER"/> 

remove

 <!-- 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"/> 

and confirm captcha here

 <beans:bean id="customAuthenticationProvider" class="com.pusilkom.artajasa.ecr.backend.util.MyAuthenticationProvider" p:captchaCaptureFilter-ref="captchaCaptureFilter"/> 
+3
source

I'm not sure if this is the right way to do it, but its work is perfect for me. I created the same classes as yours, with minor changes to the filter class code and a slight change to security-context.xml .

  • I check the captcha in CaptchaCaptureFilter and save the result of the check in the variable iscaptchaPassed, which has a property in CaptchaCaptureFilter. Together with iscaptchaPassed I also save the response as captchaResponse to check if it is null later in CaptchaVerifierFilter.

Public class CaptchaCaptureFilter extends OnePerRequestFilter {

 private String captchaResponse; private boolean iscaptchaPassed; 

// setters and getters

 @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); } 
  1. Read the values ​​for captchaCaptureFilter entered in CaptchaVerifyFilter and follow these steps.

public class CaptchaVerifierFilter extends the value 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);; } 
  1. Change the following in security-context.xml.

security: custom-filter ref = "captchaCaptureFilter" before = "FIRST"

security: custom-filter ref = "captchaVerifierFilter" after = "FORM_LOGIN_FILTER"

I add captchaCaptureFilter in front of all filters where captcha is checked. the verification result is used after UserNameAndPasswordAuthFilter, which is FORM_LOGIN_FILTER .

+1
source

I tried another JCaptcha validation method, i.e. before Spring Security Authentication. First, JCaptcha will be checked, and if it is correct, control will pass to Spring Security Authentication. I have not added CaptchaCaptureFilter to security-context.xml yet.

Below I have tried. It worked correctly.

 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; } 
0
source

Source: https://habr.com/ru/post/1439288/


All Articles