Tuesday, February 22, 2011

Spring Security 3: Integrating reCAPTCHA Service

Introduction

In this tutorial we'll integrate Spring Security 3 with the popular reCAPTCHA service. For the Spring Security module we'll based the application from the one we built for Spring Security 3 - MVC: Using a Simple User-Service Tutorial. For the reCAPTCHA module we'll based the implementation from the Using reCAPTCHA with Java/JSP article. Our design goal here is to integrate the reCAPTCHA service unobtrusively. To realize that we'll be relying on Spring Security filters.

Here's what we will do:
1. Declare two CAPTCHA filters
2. Modify a JSP file to include reCAPTCHA login
3. Add two filters in the spring-security.xml configuration

Requirements:
1. A basic understanding of how to setup a simple Spring Security 3 application (See here)
2. A basic understanding of how to setup reCAPTCHA with JSP (See here)
3. An account with the reCAPTCHA service (See here). For the account, make sure you assigned the host that matches your web application. For example http://localhost.

What is reCAPTCHA?
reCAPTCHA is a free CAPTCHA service that helps to digitize books, newspapers and old time radio shows....

A CAPTCHA is a program that can tell whether its user is a human or a computer. You've probably seen them รณ colorful images with distorted text at the bottom of Web registration forms. CAPTCHAs are used by many websites to prevent abuse from "bots," or automated programs usually written to generate spam. No computer program can read distorted text as well as humans can, so bots cannot navigate sites protected by CAPTCHAs.

About 200 million CAPTCHAs are solved by humans around the world every day. In each case, roughly ten seconds of human time are being spent. Individually, that's not a lot of time, but in aggregate these little puzzles consume more than 150,000 hours of work each day. What if we could make positive use of this human effort? reCAPTCHA does exactly that by channeling the effort spent solving CAPTCHAs online into "reading" books.

Source: http://www.google.com/recaptcha/learnmore
Here's a screenshot of the reCAPTCHA service:

Architecture

Scenario 1: Spring Security
A form-based login has two input fields: username and password. To authenticate a user, the user must enter valid information. After submitting the values, the web application will query the database if the submitted values are present. The web application performs the validation.

Scenario 2: reCAPTCHA
A reCAPTCHA form has one input field. To authenticate a user, the user must match the text images shown on the form. After submitting the values, the web application will send a POST request to the reCAPTCHA service. The service performs the validation and returns the result as a booblean value. The web application can use this result to determine if the user should be allowed access or not.

Spring Security and reCAPTCHA
With Spring Security and reCAPTCHA the same logic still applies. To authenticate a user, the user must enter his username, password, and the two words that matches the CAPTCHA text images.

Here's a screenshot of our new login page:

After submitting the values, here's what should happen:
1. A custom filter stores the entered values from the CAPTCHA form for later use. (Scenario 2)
2. The web application queries the database to check if the entered username and password exist. If invalid, deny access. If valid, continue with the remaining filters. (Scenario 1)
3. A second custom filter retrieves the stored values from the CAPTCHA form and sends a POST request to the CAPTCHA service. (Scenario 2)

Development

The Filters

Our application has two custom filters:
1. CAPTCHA Capture filter 
2. CAPTCHA Verifier filter
The purpose of the Capture filter is to store the information entered by the user in the CAPTCHA form. Whereas the Verifier filter's purpose is to send a POST request to the CAPTCHA service and wait for the result. If the result is valid, allow the user to proceed; otherwise, it will show the login page again.

We'll place these two filters in-between the FORM_LOGIN_FILTER alias which is the one responsible for the form-based login for Spring Security. Why do we need to place these two filters in-between this alias?

<security:http auto-config="true" >
 ...
 <security:custom-filter ref="captchaCaptureFilter" before="FORM_LOGIN_FILTER"/>
 <security:custom-filter ref="captchaVerifierFilter" after="FORM_LOGIN_FILTER"/>
</security:http>

Issue 1:
Placing the capture and verifier filters after the FORM_LOGIN_FILTER resets the requests parameters which means they will always be null. The verifier has nothing to verify resulting to denied access!

Issue 2:
Placing the capture and verifier filter before the alias works but you won't be able to redirect the user to the login page if in case the user has entered an invalid information. You can throw an Exception like BadCredentialsException but the exception will be shown on the web page! Again, you can't redirect to a JSP page because the response stream has been already written.

Here's the capture filter:

CaptchaCaptureFilter.java
package org.krams.tutorial.filter;

import java.io.IOException;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * Filter for capturing Captcha fields.
 * It's purpose is to store these values internally
 */
public class CaptchaCaptureFilter extends OncePerRequestFilter {
 
 protected Logger logger = Logger.getLogger("filter");
 private String recaptcha_response;
 private String recaptcha_challenge;
 private String remoteAddr;

 @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("recaptcha_response_field") != null) {
   recaptcha_response = req.getParameter("recaptcha_response_field");
   recaptcha_challenge = req.getParameter("recaptcha_challenge_field");
   remoteAddr = req.getRemoteAddr();
  }
  
  logger.debug("challenge: " + recaptcha_challenge);
  logger.debug("response: " + recaptcha_response);
  logger.debug("remoteAddr: " + remoteAddr);
  
  // Proceed with the remaining filters
  chain.doFilter(req, res);
 }

 public String getRecaptcha_response() {
  return recaptcha_response;
 }

 public void setRecaptcha_response(String recaptchaResponse) {
  recaptcha_response = recaptchaResponse;
 }

 public String getRecaptcha_challenge() {
  return recaptcha_challenge;
 }

 public void setRecaptcha_challenge(String recaptchaChallenge) {
  recaptcha_challenge = recaptchaChallenge;
 }

 public String getRemoteAddr() {
  return remoteAddr;
 }

 public void setRemoteAddr(String remoteAddr) {
  this.remoteAddr = remoteAddr;
 }
}

Here's the verifier filter:

CaptchaVerifierFilter.java
package org.krams.tutorial.filter;

import java.io.IOException;
import java.util.Properties;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import net.tanesha.recaptcha.ReCaptchaImpl;
import net.tanesha.recaptcha.ReCaptchaResponse;
import org.apache.log4j.Logger;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler;
import org.springframework.web.filter.OncePerRequestFilter;

/**
 * Filter for verifying if the submitted Captcha fields
 * are valid. 
 * <p>
* This filter also allows you to set a proxy if needed
 */
public class CaptchaVerifierFilter extends OncePerRequestFilter {
 
 protected Logger logger = Logger.getLogger("filter");
 private Boolean useProxy = false;
 private String proxyPort;
 private String proxyHost;
 private String failureUrl;
 private CaptchaCaptureFilter captchaCaptureFilter;
 private String privateKey;
 
 // 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("challenge: " + captchaCaptureFilter.getRecaptcha_challenge());
  logger.debug("response: " + captchaCaptureFilter.getRecaptcha_response());
  logger.debug("remoteAddr: " + captchaCaptureFilter.getRemoteAddr());
  
  // Assign values only when user has submitted a Captcha value
  if (captchaCaptureFilter.getRecaptcha_response() != null) {
   
   // Create a new recaptcha (by Soren Davidsen)
   ReCaptchaImpl reCaptcha = new ReCaptchaImpl();
   
   // Set the private key (assigned by Google)
   reCaptcha.setPrivateKey(privateKey);
 
   // Assign proxy if needed
   if (useProxy) {
    Properties systemSettings = System.getProperties();
    systemSettings.put("http.proxyPort",proxyPort);     
    systemSettings.put("http.proxyHost",proxyHost);
   }
   
   // Send HTTP request to validate user's Captcha
   ReCaptchaResponse reCaptchaResponse = reCaptcha.checkAnswer(captchaCaptureFilter.getRemoteAddr(), captchaCaptureFilter.getRecaptcha_challenge(), captchaCaptureFilter.getRecaptcha_response());
 
   // Check if valid
   if (!reCaptchaResponse.isValid()) {
    logger.debug("Captcha is invalid!");
    
          // Redirect user to login page
    failureHandler.setDefaultFailureUrl(failureUrl);
    failureHandler.onAuthenticationFailure(req, res, new BadCredentialsException("Captcha invalid!"));

   } 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.setRemoteAddr(null);
  captchaCaptureFilter.setRecaptcha_challenge(null);
  captchaCaptureFilter.setRecaptcha_response(null);
 }
 
 public Boolean getUseProxy() {
  return useProxy;
 }

 public void setUseProxy(Boolean useProxy) {
  this.useProxy = useProxy;
 }

 public String getProxyPort() {
  return proxyPort;
 }

 public void setProxyPort(String proxyPort) {
  this.proxyPort = proxyPort;
 }

 public String getProxyHost() {
  return proxyHost;
 }

 public void setProxyHost(String proxyHost) {
  this.proxyHost = proxyHost;
 }

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

 public String getPrivateKey() {
  return privateKey;
 }

 public void setPrivateKey(String privateKey) {
  this.privateKey = privateKey;
 }
}

Configuration

We've already seen earlier the required configuration to activate the CAPTCHA filters. Here's the full spring-security.xml configuration file:

spring-security.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:p="http://www.springframework.org/schema/p" 
    xmlns:security="http://www.springframework.org/schema/security"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
   http://www.springframework.org/schema/security 
   http://www.springframework.org/schema/security/spring-security-3.0.xsd">
 
 <security:http auto-config="true" use-expressions="true" access-denied-page="/krams/auth/denied" >
 
  <security:intercept-url pattern="/krams/auth/login" access="permitAll"/>
  <security:intercept-url pattern="/krams/main/admin" access="hasRole('ROLE_ADMIN')"/>
  <security:intercept-url pattern="/krams/main/common" access="hasRole('ROLE_USER')"/>
  
  <security:form-login
    login-page="/krams/auth/login" 
    authentication-failure-url="/krams/auth/login?error=true" 
    default-target-url="/krams/main/common"/>
   
  <security:logout 
    invalidate-session="true" 
    logout-success-url="/krams/auth/login" 
    logout-url="/krams/auth/logout"/>
 
  <security:custom-filter ref="captchaCaptureFilter" before="FORM_LOGIN_FILTER"/>
  <security:custom-filter ref="captchaVerifierFilter" after="FORM_LOGIN_FILTER"/>
 </security:http>
 
 <!-- For capturing CAPTCHA fields -->
 <bean id="captchaCaptureFilter" class="org.krams.tutorial.filter.CaptchaCaptureFilter" />
 
 <!-- For verifying CAPTCHA fields -->
 <!-- Private key is assigned by the reCATPCHA service -->
 <bean id="captchaVerifierFilter" class="org.krams.tutorial.filter.CaptchaVerifierFilter" 
    p:useProxy="false" 
    p:proxyPort="" 
    p:proxyHost=""
    p:failureUrl="/krams/auth/login?error=true"
    p:captchaCaptureFilter-ref="captchaCaptureFilter"
    p:privateKey="ADD-YOUR-PRIVATE-KEY-HERE"/>
 
 <!-- Declare an authentication-manager to use a custom userDetailsService -->
 <security:authentication-manager>
         <security:authentication-provider user-service-ref="userDetailsService">
           <security:password-encoder ref="passwordEncoder"/>
         </security:authentication-provider>
 </security:authentication-manager>
 
 <!-- Use a Md5 encoder since the user's passwords are stored as Md5 in the database -->
 <bean class="org.springframework.security.authentication.encoding.Md5PasswordEncoder" id="passwordEncoder"/>

  <!-- An in-memory list of users. No need to access an external database layer.
      See Spring Security 3.1 Reference 5.2.1 In-Memory Authentication -->
  <!-- john's password is admin, while jane;s password is user  -->
  <security:user-service id="userDetailsService">
     <security:user name="john" password="21232f297a57a5a743894a0e4a801fc3" authorities="ROLE_USER, ROLE_ADMIN" />
     <security:user name="jane" password="ee11cbb19052e40b07aac0ca060c23ee" authorities="ROLE_USER" />
   </security:user-service>
 
</beans>
Notice the only changes we made here is add the two filters:
<security:http auto-config="true" >
 ...
 <security:custom-filter ref="captchaCaptureFilter" before="FORM_LOGIN_FILTER"/>
 <security:custom-filter ref="captchaVerifierFilter" after="FORM_LOGIN_FILTER"/>
</security:http>

And declare the beans:
<!-- For capturing CAPTCHA fields -->
 <bean id="captchaCaptureFilter" class="org.krams.tutorial.filter.CaptchaCaptureFilter" />
 
 <!-- For verifying CAPTCHA fields -->
 <!-- Private key is assigned by the reCATPCHA service -->
 <bean id="captchaVerifierFilter" class="org.krams.tutorial.filter.CaptchaVerifierFilter" 
    p:useProxy="false" 
    p:proxyPort="" 
    p:proxyHost=""
    p:failureUrl="/krams/auth/login?error=true"
    p:captchaCaptureFilter-ref="captchaCaptureFilter"
    p:privateKey="ADD-YOUR-PRIVATE-KEY-HERE"/>
The verifier filter is configurable. You can enable proxy if needed and assign a failure URL which is usually the same URL declared in your form-login tag. Don't forget to add your private key which is provided freely by reCAPTCHA!

The Login Page

Our last task is modify the login page so that the CAPTCHA form is shown along with the username and password fields. To implement the login page we just mix directly the contents from the original loginpage.jsp and the one from http://code.google.com/apis/recaptcha/docs/java.html

Here's the login page:

loginpage.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<%@ page import="net.tanesha.recaptcha.ReCaptcha" %>
<%@ page import="net.tanesha.recaptcha.ReCaptchaFactory" %>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>

<h1>Login</h1>

<div id="login-error">${error}</div>

<c:url value="/j_spring_security_check" var="secureUrl"/>

<form action="${secureUrl}" method="post" >
    <%
        ReCaptcha c = ReCaptchaFactory.newReCaptcha("ADD-YOUR-PUBLIC-KEY-HERE", "ADD-YOUR-PRIVATE-KEY-HERE", false);
        out.print(c.createRecaptchaHtml(null, null));
    %>
 <p>
  <label for="j_username">Username</label>
  <input id="j_username" name="j_username" type="text" />
 </p>

 <p>
  <label for="j_password">Password</label>
  <input id="j_password" name="j_password" type="password" />
 </p>

 <input  type="submit" value="Login"/>   
     
</form>
        
</body>
</html>
The main changes here are the addition of the ReCaptcha:
<%
   ReCaptcha c = ReCaptchaFactory.newReCaptcha("ADD-YOUR-PUBLIC-KEY-HERE", "ADD-YOUR-PRIVATE-KEY-HERE", false);
   out.print(c.createRecaptchaHtml(null, null));
%>
Upon running the application, this will automatically build and show the CAPTCHA form.

Run the Application

To run the application, use the following URL:
http://localhost:8080/spring-security-recaptcha/
or to go directly to the login page:
http://localhost:8080/spring-security-recaptcha/krams/auth/login

Reminders:
1. Make sure you've signed-up for a reCAPTCHA account!
2. Don't forget to add your Public and Private keys in the JSP page
3. Don't forget to add the Private key in the spring-security.xml
4. Enable the proxy setting if you have one

The application has two built-in users:
username: john / password: admin
username: jane / password: user

Conclusion

That's it. We've successfully integrated reCAPTCHA with an existing Spring Security application without changing any of the classes. We've modified our configuration by just adding two simple custom filters. We've also successfully followed the guidelines presented in Using reCAPTCHA with Java/JSP article.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-security-recaptcha/

You can download the project as a Maven build. Look for the spring-security-recaptcha.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Security 3: Integrating reCAPTCHA Service ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Saturday, February 19, 2011

Spring Data - MongoDB Tutorial (1.0.0.M1)

Introduction

In this tutorial we will refactor an existing Spring MVC 3 - MongoDB application (see here) to use the newly released Spring Data Document 1.0.0.M1 for MongoDB. Our purpose here is to realize how Spring Data simplifies integration development with MongoDB. To appreciate Spring Data, it is advisable to know how to perform basic CRUD functions with native MongoDB support. And that's the reason why we'll be refactoring an existing Spring MVC 3 - MongoDB application.

Note: An updated version of this tutorial is now accessible at Spring MVC 3.1 - Implement CRUD with Spring Data MongoDB (Part 1)

What is MongoDB?
MongoDB (from "humongous") is a scalable, high-performance, open source, document-oriented database. Written in C++, MongoDB features:
  • Document-oriented storage
  • Full Index Support
  • Replication & High Availability
  • Scale horizontally without compromising functionality.
  • Rich, document-based queries.
  • Atomic modifiers for contention-free performance.
  • Flexible aggregation and data processing.
  • Store files of any size without complicating your stack.
  • Enterprise class support, training, and consulting available.

Source: http://www.mongodb.org/

What is Spring Data - Document?
The Spring Data Document (or DATADOC) framework makes it easy to write Spring applications that use a Document store by eliminating the redundant tasks and boiler place code required for interacting with the store through Spring's excellent infrastructure support.

Source: Spring Datastore Document - Reference Documentation
In a nutshell MongoDB uses JSON instead of SQL There's no static schema to create. All schemas are dynamic, meaning you create them on-the-fly. You can try a real-time online shell for MongoDB at http://try.mongodb.org/. Visit the official MongoDB site for a thorough discussion.

Prerequisites
In order to complete this tutorial, you will be required to install a copy of MongoDB. If you don't have one yet, grab a copy now by visiting http://www.mongodb.org/display/DOCS/Quickstart. Installation is really easy.

Development

Our application is a simple CRUD system for managing a list of Persons. Data is stored in MongoDB database. We'll start by declaring our domain objects. Then we'll discuss the service layer. And lastly we'll add the controllers.

The Domain Layer

Our application contains a single domain object named Person. It consists the following properties:
pid
firstName
lastName
money
Here's the class declaration:

Person.java
package org.krams.tutorial.domain;

import java.io.Serializable;

/**
 * A simple POJO representing a Person
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
public class Person implements Serializable {

 private static final long serialVersionUID = -5527566248002296042L;
 
 private String pid;
 private String firstName;
 private String lastName;
 private Double money;

 public String getPid() {
  return pid;
 }

 public void setPid(String pid) {
  this.pid = pid;
 }

 public String getFirstName() {
  return firstName;
 }

 public void setFirstName(String firstName) {
  this.firstName = firstName;
 }

 public String getLastName() {
  return lastName;
 }

 public void setLastName(String lastName) {
  this.lastName = lastName;
 }

 public Double getMoney() {
  return money;
 }

 public void setMoney(Double money) {
  this.money = money;
 }
}
Warning!
In the original tutorial we used id to signify the primary identity of the Person. But in this tutorial we used pid instead. This is because when we use id with Spring Data, it messes up the id property by merging it with the built-in _id property of MongoDB.

For now, if you plan to use Spring Data, don't use id as your id field. Choose a different name instead. I have filed already an inquiry about this behavior in the Spring forums. See it here.

The Service Layer

Our service class contains the main changes in the original application. Instead of calling native MongoDB methods for performing CRUD operations, we use Spring Data's MongoTemplate instead.

What is MongoTemplate?
The template offers convenience methods and automatic mapping between MongoDB JSON documents and your domain classes. Out of the box, MongoTemplate uses a Java-based default converter but you can also write your own converter classes to be used for reading and storing domain objects.

Source: Spring Datastore Document - Reference Documentation

Here's the class declaration:

PersonService.java
package org.krams.tutorial.service;

import java.util.List;
import java.util.UUID;

import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.krams.tutorial.domain.Person;
import org.springframework.data.document.mongodb.MongoTemplate;
import org.springframework.data.document.mongodb.query.Query;
import org.springframework.data.document.mongodb.query.Update;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import static org.springframework.data.document.mongodb.query.Criteria.where;

/**
 * Service for processing {@link Person} objects.
 * Uses Spring's {@link MongoTemplate} to perform CRUD operations.
 * <p>
 * For a complete reference to MongoDB
 * see http://www.mongodb.org/
 * <p>
 * For a complete reference to Spring Data MongoDB 
 * see http://www.springsource.org/spring-data
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Service("personService")
@Transactional
public class PersonService {

 protected static Logger logger = Logger.getLogger("service");
 
 @Resource(name="mongoTemplate")
 private MongoTemplate mongoTemplate;
 
 /**
  * Retrieves all persons
  */
 public List<Person> getAll() {
  logger.debug("Retrieving all persons");
 
  // Find an entry where pid property exists
        Query query = new Query(where("pid").exists(true));
        // Execute the query and find all matching entries
        List<Person> persons = mongoTemplate.find(query, Person.class);
        
  return persons;
 }
 
 /**
  * Retrieves a single person
  */
 public Person get( String id ) {
  logger.debug("Retrieving an existing person");
  
  // Find an entry where pid matches the id
        Query query = new Query(where("pid").is(id));
        // Execute the query and find one matching entry
        Person person = mongoTemplate.findOne("mycollection", query, Person.class);
     
  return person;
 }
 
 /**
  * Adds a new person
  */
 public Boolean add(Person person) {
  logger.debug("Adding a new user");
  
  try {
   
   // Set a new value to the pid property first since it's blank
   person.setPid(UUID.randomUUID().toString());
   // Insert to db
      mongoTemplate.insert("mycollection", person);

   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to add new user", e);
   return false;
  }
 }
 
 /**
  * Deletes an existing person
  */
 public Boolean delete(String id) {
  logger.debug("Deleting existing person");
  
  try {
   
   // Find an entry where pid matches the id
         Query query = new Query(where("pid").is(id));
         // Run the query and delete the entry
         mongoTemplate.remove(query);
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to delete new user", e);
   return false;
  }
 }
 
 /**
  * Edits an existing person
  */
 public Boolean edit(Person person) {
  logger.debug("Editing existing person");
  
  try {
   
   // Find an entry where pid matches the id
         Query query = new Query(where("pid").is(person.getPid()));
         
   // Declare an Update object. 
         // This matches the update modifiers available in MongoDB
   Update update = new Update();
         
         update.set("firstName", person.getFirstName());
         mongoTemplate.updateMulti(query, update);
         
         update.set("lastName", person.getLastName());
         mongoTemplate.updateMulti(query, update);
         
         update.set("money", person.getMoney());
         mongoTemplate.updateMulti(query, update);
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to edit existing user", e);
   return false;
  }
  
 }
}
The code should should be self-explanatory. Notice how Spring Data has reduced the amount of code. To appreciate this difference, let's do a comparison between using the traditional MongoDB and using Spring Data.

Retrieving all entries

old implementation
public List<person> getAll() {
  logger.debug("Retrieving all persons");
   
  // Retrieve collection
  DBCollection coll = MongoDBFactory.getCollection("mydb","mycollection");
  // Retrieve cursor for iterating records
     DBCursor cur = coll.find();
     // Create new list
  List<person> items = new ArrayList<person>();
  // Iterate cursor
        while(cur.hasNext()) {
         // Map DBOject to Person
         DBObject dbObject = cur.next();
         Person person = new Person();
          
         person.setId(dbObject.get("id").toString());
         person.setFirstName(dbObject.get("firstName").toString());
         person.setLastName(dbObject.get("lastName").toString());
         person.setMoney(Double.valueOf(dbObject.get("money").toString()));
 
         // Add to new list
         items.add(person);
        }
         
        // Return list
  return items;
 }

new implementation
public List<Person> getAll() {
  logger.debug("Retrieving all persons");
 
        Query query = new Query(where("pid").exists(true));
        List<Person> persons = mongoTemplate.find(query, Person.class);
        
  return persons;
 }

Retrieving a single entry

old implementation
public Person get( String id ) {
  logger.debug("Retrieving an existing person");
   
  // Retrieve collection
  DBCollection coll = MongoDBFactory.getCollection("mydb","mycollection");
  // Create a new object
  DBObject doc = new BasicDBObject();
  // Put id to search
        doc.put("id", id);
         
        // Find and return the person with the given id
        DBObject dbObject = coll.findOne(doc);
         
        // Map DBOject to Person
     Person person = new Person();
     person.setId(dbObject.get("id").toString());
     person.setFirstName(dbObject.get("firstName").toString());
     person.setLastName(dbObject.get("lastName").toString());
     person.setMoney(Double.valueOf(dbObject.get("money").toString()));
      
        // Return person
  return person;
 }

new implementation
public Person get( String id ) {
  logger.debug("Retrieving an existing person");
  
  // Find an entry where pid matches the id
        Query query = new Query(where("pid").is(id));
        // Execute the query and find one matching entry
        Person person = mongoTemplate.findOne("mycollection", query, Person.class);
     
  return person;
 }

Adding a new entry

old implementation
public Boolean add(Person person) {
  logger.debug("Adding a new user");
   
  try {
   // Retrieve collection
   DBCollection coll = MongoDBFactory.getCollection("mydb","mycollection");
   // Create a new object
   BasicDBObject doc = new BasicDBObject();
   // Generate random id using UUID type 4
   // See http://en.wikipedia.org/wiki/Universally_unique_identifier
         doc.put("id", UUID.randomUUID().toString() ); 
         doc.put("firstName", person.getFirstName());
         doc.put("lastName", person.getLastName());
         doc.put("money", person.getMoney());
         // Save new person
         coll.insert(doc);
          
   return true;
    
  } catch (Exception e) {
   logger.error("An error has occurred while trying to add new user", e);
   return false;
  }
 }

new implementation
public Boolean add(Person person) {
  logger.debug("Adding a new user");
  
  try {
   
   // Set a new value to the pid property first since it's blank
   person.setPid(UUID.randomUUID().toString());
   // Insert to db
      mongoTemplate.insert("mycollection", person);

   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to add new user", e);
   return false;
  }
 }

Deleting an entry

old implementation
public Boolean delete(String id) {
  logger.debug("Deleting existing person");
   
  try {
   // Retrieve person to delete
   BasicDBObject item = (BasicDBObject) getDBObject( id );
   // Retrieve collection
   DBCollection coll = MongoDBFactory.getCollection("mydb","mycollection");
   // Delete retrieved person
         coll.remove(item);
          
   return true;
    
  } catch (Exception e) {
   logger.error("An error has occurred while trying to delete new user", e);
   return false;
  }
 }

new implementation
public Boolean delete(String id) {
  logger.debug("Deleting existing person");
  
  try {
   
   // Find an entry where pid matches the id
         Query query = new Query(where("pid").is(id));
         // Run the query and delete the entry
         mongoTemplate.remove(query);
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to delete new user", e);
   return false;
  }
 }

Updating an entry

old implementation
public Boolean edit(Person person) {
  logger.debug("Editing existing person");
   
  try {
   // Retrieve person to edit
   BasicDBObject existing = (BasicDBObject) getDBObject( person.getId() );
    
   DBCollection coll = MongoDBFactory.getCollection("mydb","mycollection");
    
   // Create new object
   BasicDBObject edited = new BasicDBObject();
   // Assign existing details
   edited.put("id", person.getId()); 
   edited.put("firstName", person.getFirstName());
   edited.put("lastName", person.getLastName());
   edited.put("money", person.getMoney());
   // Update existing person
         coll.update(existing, edited);
          
   return true;
    
  } catch (Exception e) {
   logger.error("An error has occurred while trying to edit existing user", e);
   return false;
  }
   
 }

new implementation
public Boolean edit(Person person) {
  logger.debug("Editing existing person");
  
  try {
   
   // Find an entry where pid matches the id
         Query query = new Query(where("pid").is(person.getPid()));
         
   // Declare an Update object. 
         // This matches the update modifiers available in MongoDB
   Update update = new Update();
         
         update.set("firstName", person.getFirstName());
         mongoTemplate.updateMulti(query, update);
         
         update.set("lastName", person.getLastName());
         mongoTemplate.updateMulti(query, update);
         
         update.set("money", person.getMoney());
         mongoTemplate.updateMulti(query, update);
         
   return true;
   
  } catch (Exception e) {
   logger.error("An error has occurred while trying to edit existing user", e);
   return false;
  }
  
 }

Configuration

To use Spring's MongoTemplate it needs to be declared via configuration. It also needs a reference to a MongoDB database. Let's declare an XML configuration that satifies these requirements:

mongo-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns:p="http://www.springframework.org/schema/p" 
    xmlns:mongo="http://www.springframework.org/schema/data/mongo"
 xsi:schemaLocation="http://www.springframework.org/schema/beans 
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/data/mongo
      http://www.springframework.org/schema/data/mongo/spring-mongo-1.0.xsd">
 
 <!-- Default bean name is 'mongo' -->
 <mongo:mongo host="localhost" port="27017"/>
 
 <!-- Offers convenience methods and automatic mapping between MongoDB JSON documents and your domain classes. -->
   <bean id="mongoTemplate" class="org.springframework.data.document.mongodb.MongoTemplate">
     <constructor-arg ref="mongo"/>
     <constructor-arg value="mydb"/>
     <constructor-arg value="mycollection"/>
   </bean>
   
   <bean id="initService" class="org.krams.tutorial.service.InitService" init-method="init"></bean>
</beans>
Notice we're using the mongo namespace:
xmlns:mongo="http://www.springframework.org/schema/data/mongo"
We've declared a reference to a MongoDB database by declaring:
<mongo:mongo host="localhost" port="27017"/>

Then we declared a MongoTemplate that references a MongoDB database (mongo), a database (mydb), and a collection (mycollection):
<bean id="mongoTemplate" class="org.springframework.data.document.mongodb.MongoTemplate">
     <constructor-arg ref="mongo"/>
     <constructor-arg value="mydb"/>
     <constructor-arg value="mycollection"/>
   </bean>

Lastly, we declared an initService
<bean id="initService" class="org.krams.tutorial.service.InitService" init-method="init"></bean>
The purpose of the initService is to prepopulate our MongoDB with sample data.

Here's the class declaration:

InitService.java
package org.krams.tutorial.service;

import java.util.UUID;

import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.krams.tutorial.domain.Person;
import org.springframework.data.document.mongodb.MongoTemplate;
import org.springframework.transaction.annotation.Transactional;

/**
 * Service for initializing MongoDB with sample data
 * <p>
 * For a complete reference to MongoDB
 * see http://www.mongodb.org/
 * <p>
 * For transactions, see http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/transaction.html
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Transactional
public class InitService {

 protected static Logger logger = Logger.getLogger("service");
 
 @Resource(name="mongoTemplate")
 private MongoTemplate mongoTemplate;

 private void init() {
  // Populate our MongoDB database
  logger.debug("Init MongoDB users");
  
  // Drop existing collection
  mongoTemplate.dropCollection("mycollection");
  
  // Create new object
  Person p = new Person ();
  p.setPid(UUID.randomUUID().toString());
  p.setFirstName("John");
  p.setLastName("Smith");
  p.setMoney(1000.0);
  
  // Insert to db
     mongoTemplate.insert("mycollection", p);

     // Create new object
  p = new Person ();
  p.setPid(UUID.randomUUID().toString());
  p.setFirstName("Jane");
  p.setLastName("Adams");
  p.setMoney(2000.0);
  
  // Insert to db
     mongoTemplate.insert("mycollection", p);
        
     // Create new object
  p = new Person ();
  p.setPid(UUID.randomUUID().toString());
  p.setFirstName("Jeff");
  p.setLastName("Mayer");
  p.setMoney(3000.0);
  
  // Insert to db
     mongoTemplate.insert("mycollection", p);
 }
}

The Controller Layer

After creating the domain and service classes, we need to declare a controller that will handle the web requests.

MainController.java
package org.krams.tutorial.controller;

import java.util.List;
import javax.annotation.Resource;
import org.apache.log4j.Logger;
import org.krams.tutorial.domain.Person;
import org.krams.tutorial.service.PersonService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;


/**
 * Handles and retrieves person request
 * 
 * @author Krams at {@link http://krams915@blogspot.com}
 */
@Controller
@RequestMapping("/main")
public class MainController {

 protected static Logger logger = Logger.getLogger("controller");
 
 @Resource(name="personService")
 private PersonService personService;
 
 /**
  * Handles and retrieves all persons and show it in a JSP page
  * 
  * @return the name of the JSP page
  */
    @RequestMapping(value = "/persons", method = RequestMethod.GET)
    public String getPersons(Model model) {
     
     logger.debug("Received request to show all persons");
     
     // Retrieve all persons by delegating the call to PersonService
     List persons = personService.getAll();
     
     // Attach persons to the Model
     model.addAttribute("persons", persons);
     
     // This will resolve to /WEB-INF/jsp/personspage.jsp
     return "personspage";
 }
    
    /**
     * Retrieves the add page
     * 
     * @return the name of the JSP page
     */
    @RequestMapping(value = "/persons/add", method = RequestMethod.GET)
    public String getAdd(Model model) {
     logger.debug("Received request to show add page");
    
     // Create new Person and add to model
     // This is the formBackingOBject
     model.addAttribute("personAttribute", new Person());

     // This will resolve to /WEB-INF/jsp/addpage.jsp
     return "addpage";
 }
 
    /**
     * Adds a new person by delegating the processing to PersonService.
     * Displays a confirmation JSP page
     * 
     * @return  the name of the JSP page
     */
    @RequestMapping(value = "/persons/add", method = RequestMethod.POST)
    public String add(@ModelAttribute("personAttribute") Person person) {
  logger.debug("Received request to add new person");
  
     // The "personAttribute" model has been passed to the controller from the JSP
     // We use the name "personAttribute" because the JSP uses that name
  
  // Call PersonService to do the actual adding
  personService.add(person);

     // This will resolve to /WEB-INF/jsp/addedpage.jsp
  return "addedpage";
 }
    
    /**
     * Deletes an existing person by delegating the processing to PersonService.
     * Displays a confirmation JSP page
     * 
     * @return  the name of the JSP page
     */
    @RequestMapping(value = "/persons/delete", method = RequestMethod.GET)
    public String delete(@RequestParam(value="pid", required=true) String id, 
              Model model) {
   
  logger.debug("Received request to delete existing person");
  
  // Call PersonService to do the actual deleting
  personService.delete(id);
  
  // Add id reference to Model
  model.addAttribute("pid", id);
     
     // This will resolve to /WEB-INF/jsp/deletedpage.jsp
  return "deletedpage";
 }
    
    /**
     * Retrieves the edit page
     * 
     * @return the name of the JSP page
     */
    @RequestMapping(value = "/persons/edit", method = RequestMethod.GET)
    public String getEdit(@RequestParam(value="pid", required=true) String id,  
              Model model) {
     logger.debug("Received request to show edit page");
    
     // Retrieve existing Person and add to model
     // This is the formBackingOBject
     model.addAttribute("personAttribute", personService.get(id));
     
     // This will resolve to /WEB-INF/jsp/editpage.jsp
     return "editpage";
 }
    
    /**
     * Edits an existing person by delegating the processing to PersonService.
     * Displays a confirmation JSP page
     * 
     * @return  the name of the JSP page
     */
    @RequestMapping(value = "/persons/edit", method = RequestMethod.POST)
    public String saveEdit(@ModelAttribute("personAttribute") Person person, 
                 @RequestParam(value="pid", required=true) String id, 
                Model model) {
     logger.debug("Received request to update person");
    
     // The "personAttribute" model has been passed to the controller from the JSP
     // We use the name "personAttribute" because the JSP uses that name
     
     // We manually assign the id because we disabled it in the JSP page
     // When a field is disabled it will not be included in the ModelAttribute
     person.setPid(id);
     
     // Delegate to PersonService for editing
     personService.edit(person);
     
     // Add id reference to Model
  model.addAttribute("pid", id);
  
     // This will resolve to /WEB-INF/jsp/editedpage.jsp
  return "editedpage";
 }
    
}
Our controller is a simple class that delegates actual processing to PersonService. When the service is done processing, the controller forwards the result to a JSP view.

Other Configurations and Files

To make the tutorial manageable, I've decided not to post the following configuration files in this tutorial:
web.xml
spring-servlet.xml
applicationContext.xml
These files are standard Spring MVC related configuration files. You can find them in the downloadable application at the end of this tutorial.

I have also left out the JSP declarations. You can find a description of them in the following tutorial: Spring MVC 3: Using a Document-Oriented Database - MongoDB

Run the Application

To run the application, open your browser and enter the following URL:
http://localhost:8080/spring-data-mongodb/krams/main/persons
You should see the following CRUD view:


Conclusion

That's it. We have successfully refactored our existing Spring MVC 3 - MongoDB application to use the newly released Spring Data Document 1.0 for MongoDB. We've compared side-by-side between the native MongoDB development and with the new Spring Data framework. We have seen how Spring Data has simplified our development further.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-mvc-mongodb/

You can download the project as a Maven build. Look for the spring-data-mongodb.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Data - MongoDB Tutorial (1.0.0.M1) ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share

Sunday, February 13, 2011

Spring Security 3 - OpenID with Javascript OpenID Selector

Introduction

In previous tutorials we've discussed how to add OpenID support to an existing Spring Security 3 application using various OpenID providers. We added each provider manually, including the images and specialized URLs. However that solution has introduced a couple of issues:

Problems

1. We're forced to manually add all OpenID providers in the JSP page. Developer A might implement it differently versus Developer B. This means users do not get a unified user experience.

2. Users are still required to know their full OpenID login. For example, to login using Blogspot, the user will type http://krams915.blogspot.com. Ideally the user should only type his username, i.e. krams915. Typing the full URL is error-prone, especially for non-technical users.

3. We could implement our own Javascript library that will parse and concatenate the default OpenID URLs. But that introduces extra work, and again, multiple developers may implement it differently. Also, are you expert enough to start your own Javascript framework?

Solution

Luckily for us there is a solution: the Javascript OpenID Selector

What is Javascript OpenID Selector?
This is a simple Javascript OpenID selector. It has been designed so that users do not even need to know what OpenID is to use it, they simply select their account by a recognisable logo.

Source: http://code.google.com/p/openid-selector/

You can find a live demo of this project at http://openid-selector.googlecode.com/svn/trunk/demo.html

Development

In this tutorial we'll add OpenID login support to an existing Spring Security 3 application. That means we'll be reusing existing configuration.

To fully appreciate this tutorial, it's required you know how to setup a simple Spring Security application using a simple user-service. If you need a review, please read my other guide Spring MVC 3 - Security - Using Simple User-Service.

You are also advised to read my other OpenID tutorials to understand the problem that we're trying to solve. You can check the following Spring Security 3 - OpenID Login with Google Provider and Spring Security 3 - OpenID Login with myOpenID Provider

We will based our application from the one we developed for Spring MVC 3 - Security - Using Simple User-Service tutorial. This is because the changes we need to implement are just a matter of configuration and placing Javascript snippets.

Preview

Before we add the Javascript OpenID Selector, our login page looks like the following:


After adding the Javascript OpenID Selector, our new login page should look like below:


Do you see the big improvement? We've just added multiple OpenID providers by just editing a JSP file which we'll show later.

Adding Javascript OpenID Selector

Since this is a third-party project, we must download it first and place the necessary files to the current Spring project. Here are the steps:

1. Visit the project's site at http://code.google.com/p/openid-selector/

2. Go to the Downloads section. Find the latest open-selector project:

3. Download and save the file.

4. Extract and open the extracted folder:


You should see a list of folders and files. We're only interested with the following:
js/jquery-1.2.6.min.js
js/openid-jquery.js
js/openid-en.js
images folder
css folder

5. Copy these files to the resources folder of the Spring project:

I suggest you create the following folders to logically contain these files.
css
images
js

Our next step is to edit the application's JSP login page to display the Javascript OpenID Selector.

6. Go back to the folder where you extracted the open-selector project.

7. Open the demo.html to see the following:


Right-click on this html file and view its source:

demo.html
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
 <title>JQuery Simple OpenID Selector Demo</title>
 <!-- Simple OpenID Selector -->
 <link type="text/css" rel="stylesheet" href="css/openid.css" />
 <script type="text/javascript" src="js/jquery-1.2.6.min.js"></script>
 <script type="text/javascript" src="js/openid-jquery.js"></script>
 <script type="text/javascript" src="js/openid-en.js"></script>
 <script type="text/javascript">
  $(document).ready(function() {
   openid.init('openid_identifier');
   openid.setDemoMode(true); //Stops form submission for client javascript-only test purposes
  });
 </script>
 <!-- /Simple OpenID Selector -->
 <style type="text/css">
  /* Basic page formatting */
  body {
   font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  }
 </style>
</head>

<body>
 <h2>JQuery Simple OpenID Selector Demo</h2>
 <p>This is a simple example to show how you can include the Javascript into your page.</p>
 <br/>
 <!-- Simple OpenID Selector -->
 <form action="examples/consumer/try_auth.php" method="get" id="openid_form">
  <input type="hidden" name="action" value="verify" />
  <fieldset>
   <legend>Sign-in or Create New Account</legend>
   <div id="openid_choice">
    <p>Please click your account provider:</p>
    <div id="openid_btns"></div>
   </div>
   <div id="openid_input_area">
    <input id="openid_identifier" name="openid_identifier" type="text" value="http://" />
    <input id="openid_submit" type="submit" value="Sign-In"/>
   </div>
   <noscript>
    <p>OpenID is service that allows you to log-on to many different websites using a single indentity.
    Find out <a href="http://openid.net/what/">more about OpenID</a> and <a href="http://openid.net/get/">how to get an OpenID enabled account</a>.</p>
   </noscript>
  </fieldset>
 </form>
 <!-- /Simple OpenID Selector -->
</body>
</html>

8. Copy the code inside head and body section. Paste the code in the corresponding sections of the JSP login page. Here's the final JSP page:

loginpage.jsp
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://www.springframework.org/tags/form" prefix="form" %>
<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
 
 <c:url var="rootUrl" value="/resources/" />
 
 <title>JQuery Simple OpenID Selector Demo</title>
 <!-- Simple OpenID Selector -->
 <link type="text/css" rel="stylesheet" href="${rootUrl}css/openid.css" />
 <script type="text/javascript" src="${rootUrl}js/jquery-1.2.6.min.js"></script>
 <script type="text/javascript" src="${rootUrl}js/openid-jquery.js"></script>
 <script type="text/javascript" src="${rootUrl}js/openid-en.js"></script>
 <script type="text/javascript">
  $(document).ready(function() {
   openid.init('openid_identifier');
   //openid.setDemoMode(true); //Stops form submission for client javascript-only test purposes
   });
 </script>
 <!-- /Simple OpenID Selector -->
 <style type="text/css">
 /* Basic page formatting */
 body {
  font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
 }
 </style>
</head>
<body>

<h1>Spring Security 3</h1>
<div id="login-error">${error}</div>

<c:url var="openIDLoginUrl" value="/j_spring_openid_security_check" />
<h2>JQuery Simple OpenID Selector Demo</h2> 
 <p>This is a simple example to show how you can include the Javascript into your page.</p> 
 <br/> 
 <!-- Simple OpenID Selector --> 
 <form action="${openIDLoginUrl}" method="post" id="openid_form"> 
  <input type="hidden" name="action" value="verify" /> 
  <fieldset> 
   <legend>Sign-in or Create New Account</legend> 
   <div id="openid_choice"> 
    <p>Please click your account provider:</p> 
    <div id="openid_btns"></div> 
   </div> 
   <div id="openid_input_area"> 
    <input id="openid_identifier" name="openid_identifier" type="text" value="http://" /> 
    <input id="openid_submit" type="submit" value="Sign-In"/> 
   </div> 
   <noscript> 
    <p>OpenID is service that allows you to log-on to many different websites using a single indentity.
    Find out <a href="http://openid.net/what/">more about OpenID</a> and <a href="http://openid.net/get/">how to get an OpenID enabled account</a>.</p>
   </noscript> 
  </fieldset> 
 </form> 
 <!-- /Simple OpenID Selector --> 

</body>
</html>

To make this code work, we have to correctly set the root URL by using a JSTL tag library:
<c:url var="rootUrl" value="/resources/" />
We've also commented out the following line which is responsible for activating the demo function:
//openid.setDemoMode(true); //Stops form submission for client 

Try running the application. You'll notice the images are missing. That's because we need to set the correct image path property inside the openid-jquery.js.

9. Go to the resources folder. Open the openid-jquery.js file.

10. Find the img_path property. Set the value as follows:
img_path : '../../resources/images/',

Run the Application

To run the application, use the following URL to display the login page:
http://localhost:8080/spring-security-openid-selector/krams/auth/login

Here's the final screenshot:

Try playing with all the various OpenID providers. Notice how user-friendly the interface is.

Reminder

Notice regardless whether you've entered the correct credentials the Spring application still throws out an error that the username or password is incorrect. That's because you need to add the correct OpenID identifier under the user-service tag. See the spring-security.xml

<security:user-service id="userDetailsService">
   <!-- user name is based on the returned open id identifier -->
   <!-- YOU MUST MANUALLY ADD THE RETURNED OPENID IDENTIFIER HERE -->
     <security:user name="http://krams915.blogspot.com/" password="" authorities="ROLE_USER, ROLE_ADMIN" />
  </security:user-service>

Conclusion

That's it. We've managed to add the Javascript OpenID Selector. Of course to make the whole project work, you need to add the corresponding returned OpenID identifiers in the Spring Security's in-memory user-service. If you're going to test these with Google, Yahoo, or any of your favorite providers, please log out first so that you can test the login functionality!

The best way to learn further is to try the actual application.

Download the project
You can access the project site at Google's Project Hosting at http://code.google.com/p/spring-security-openid/

You can download the project as a Maven build. Look for the spring-security-openid-selector.zip in the Download sections.

You can run the project directly using an embedded server via Maven.
For Tomcat: mvn tomcat:run
For Jetty: mvn jetty:run

If you want to learn more about Spring MVC and integration with other technologies, feel free to read my other tutorials in the Tutorials section.
StumpleUpon DiggIt! Del.icio.us Blinklist Yahoo Furl Technorati Simpy Spurl Reddit Google I'm reading: Spring Security 3 - OpenID with Javascript OpenID Selector ~ Twitter FaceBook

Subscribe by reader Subscribe by email Share