This post explains how to create a customized Spring user and persistence mechanism for authentication. The code is available
at
GitHub in the
Spring-MVC-Customized-User directory.
Things To Take Into Consideration
Before creating a customized user, we need to remind what a user is in Spring. We know from
here that a user is an implementation of the
UserDetails interface. Spring security also requires a loading mechanism, as an implementation of the
UserDetailsService interface. To keep this example simple, we are not going to implement login/logout or other security features.
The
UserDetails interface is a bit incomplete in order to define a user in a real application. It defines getters and no setters. It is not a big deal, but the real pain is that the
username (a string) is considered as 'the' key. Most software developers will prefer a
long id.
To solve these issues, we define a
PracticalUser interface:
public interface PracticalUserDetails
extends UserDetails, CredentialsContainer {
long getId();
void setPassword(String password);
void setAccountExpired(boolean b);
void setAccountLocked(boolean b);
void setCredentialsExpired(boolean b);
void setEnabled(boolean b);
void setAuthorities(
Collection<? extends GrantedAuthority> authorities);
}
Typically, one would use JPA annotations on the implementation bean and save it in a database in a real application. However, for this example, the DAO will register users in an in-memory map as described further.
The
UserDetailsService interface does not provide a get user per name or id, which is necessary for an administrator (for example). More, it does not allow to retrieve all existing users' id and name, or to update or insert users, or even to delete them.
Therefore, we create a
PracticalUserDetailsService interface to solve these issues:
public interface PracticalUserDetailsService
extends UserDetailsService {
Set<PracticalUserDetailsDAO.UserIdentifiers> getUsers();
void deleteUser(long id);
void upsertUser(PracticalUserDetails user);
PracticalUserDetails getUser(long id);
PracticalUserDetails getUser(String username);
}
The corresponding implementation is called
PracticalUserDetailsServiceInMemory for this example.
In-Memory DAO
To keep this example simple, we define a simple
PracticalUserDetailsDAO:
public interface PracticalUserDetailsDAO<U extends PracticalUserDetails> {
void create(U user);
boolean contains(U user);
U read(String username);
U read(long id);
void update(U user);
void delete(String username);
void delete(long id);
interface UserIdentifiers {
long getId();
String getUsername();
}
Set<UserIdentifiers> getUsers();
}
We also define a special user id and name identifier interface to retrieve the set of existing user data, without returning all users. Our implementation of
PracticalUserDetailsDAO is
PracticalUserDetailsDAOInMemory.
In a real implementation, using a
JPA Repository is more appropriate.
Configuration
In the
security.xml file, we define our practical user detail service (it will be registered in the authentication manager) and the in-memory DAO bean:
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
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.1.xsd">
<http auto-config="true">
</http>
<authentication-manager alias="authenticationManager">
<authentication-provider user-service-ref='practicalUserDetailsServiceInMemory' />
</authentication-manager>
<beans:bean id="pud"
class="com.jverstry.DAO.PracticalUserDetailsDAOInMemory">
</beans:bean>
</beans:beans>
The JSP Pages
We use two pages. The main page displays registered users (together with a delete link) and registration form:
The second page is displayed when the
Create User button is clicked to check the name and the password:
The Controller
The controller is used to check the username and password:
@Controller
public class MyController {
@Autowired
private PracticalUserDetailsServiceInMemory pudm;
@RequestMapping(value = "/")
public ModelAndView index() {
ModelAndView result = new ModelAndView("index");
result.addObject("users", this.pudm.getUsers());
return result;
}
@RequestMapping(value = "/delete/{id}")
public String delete(@PathVariable(value="id") String id) {
this.pudm.deleteUser(Long.parseLong(id));
return "redirect:/";
}
@RequestMapping(value = "/create")
@SuppressWarnings("AssignmentToMethodParameter")
public ModelAndView add(
@RequestParam(value="name")
String name,
@RequestParam(value="password")
String password) {
name = StringUtils.replace(name, " ", "");
password = StringUtils.replace(password, " ", "");
String errorMsg = "";
if ( name.length() == 0 ) {
errorMsg += "Name is empty ";
}
if ( password.length() == 0 ) {
errorMsg += "Password is empty ";
}
if ( errorMsg.isEmpty() ) {
this.pudm.upsertUser(new PracticalUserDetailsImpl(name, password));
}
ModelAndView result = new ModelAndView("create");
result.addObject("errorMsg", errorMsg);
result.addObject("username", name);
return result;
}
}
Running The Example
One can run it using the maven
tomcat:run goal. Then, browse:
http://localhost:9191/spring-mvc-customized-user/
More Spring related posts
here.