This tutorial shows Password Encoding in Spring Security 4 using BCryptPasswordEncoder. We will take a Spring MVC 4, Hibernate 4 & Spring Security 4 example to demonstrate a real-world setup involving login authentication and user creation.Both Annotation + XML based projects are available for download at the end of this post. Let’s get going.
- Spring Boot+AngularJS+Spring Data+Hibernate+MySQL CRUD App
- Secure Spring REST API using OAuth2
- Spring Boot REST API Tutorial
- Spring Boot WAR deployment example
- Spring Boot Introduction + Hello World Example
- AngularJS+Spring Security using Basic Authentication
- Secure Spring REST API using Basic Authentication
- Spring 4 MVC+JPA2+Hibernate Many-to-many Example
- Spring 4 Caching Annotations Tutorial
- Spring 4 Cache Tutorial with EhCache
- Spring 4 Email Template Library Example
- Spring 4 Email With Attachment Tutorial
- Spring 4 Email Integration Tutorial
- Spring MVC 4+JMS+ActiveMQ Integration Example
- Spring 4+JMS+ActiveMQ @JmsLister @EnableJms Example
- Spring 4+JMS+ActiveMQ Integration Example
- Spring MVC 4+Apache Tiles 3 Integration Example
- Spring MVC 4+AngularJS Example
- Spring MVC 4+AngularJS Server communication example : CRUD application using ngResource $resource service
- Spring MVC 4+AngularJS Routing with UI-Router Example
- Spring MVC 4+Hibernate 4 Many-to-many JSP Example
- Spring MVC 4+Hibernate 4+MySQL+Maven integration + Testing example using annotations
- Spring Security 4 Hibernate Integration Annotation+XML Example
- Spring MVC4 FileUpload-Download Hibernate+MySQL Example
- Spring MVC 4 Form Validation and Resource Handling
- Spring Batch- MultiResourceItemReader & HibernateItemWriter example
A word on Password Encoding
Any application, which takes Security seriously, should NEVER store passwords in plain text format. Passwords should always be encoded using a secure hashing algorithm. There are many standard algorithms like SHA or MD5 which combined with a proper SALT can be a good choice for password encoding. Spring Security provides BCryptPasswordEncoder
, and implementation of Spring’s PasswordEncoder
interface that uses the BCrypt strong hashing function
to encode the password.
Where all possibly do we need Password Encoding in application?
1. During password comparison. Encode input password before comparing with the one stored in database(which is encoded)
2. During New user creation/existing user password updation. Encode the new input password before saving/updating in database.
Changes compare to previous posts?
1. Create & Inject PasswordEncoder into AuthenticationProvider
& set it as authentication provider on AuthenticationManagerBuilder
package com.websystique.springsecurity.configuration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("customUserDetailsService") UserDetailsService userDetailsService; @Autowired public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); auth.authenticationProvider(authenticationProvider()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsService); authenticationProvider.setPasswordEncoder(passwordEncoder()); return authenticationProvider; } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/home").permitAll() .antMatchers("/admin/**","/newuser").access("hasRole('ADMIN')") .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") .and().formLogin().loginPage("/login") .usernameParameter("ssoId").passwordParameter("password") .and().csrf() .and().exceptionHandling().accessDeniedPage("/Access_Denied"); } }
Above setup will take care of password comparisons during Authentication anywhere in application.
Above security configuration in XML configuration format would be:
<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-4.1.xsd http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security-4.0.xsd"> <http auto-config="true" > <intercept-url pattern="/" access="permitAll" /> <intercept-url pattern="/home" access="permitAll" /> <intercept-url pattern="/admin**" access="hasRole('ADMIN')" /> <intercept-url pattern="/dba**" access="hasRole('ADMIN') and hasRole('DBA')" /> <form-login login-page="/login" username-parameter="ssoId" password-parameter="password" authentication-failure-url="/Access_Denied" /> <csrf/> </http> <authentication-manager > <authentication-provider user-service-ref="customUserDetailsService"> <password-encoder ref="bcryptEncoder"/> </authentication-provider> </authentication-manager> <beans:bean id="bcryptEncoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder" /> <beans:bean id="customUserDetailsService" class="com.websystique.springsecurity.service.CustomUserDetailsService" /> </beans:beans>
2. Update UserService to include password encoding before saving new password in database.
@Service("userService") @Transactional public class UserServiceImpl implements UserService{ @Autowired private UserDao dao; @Autowired private PasswordEncoder passwordEncoder; public void save(User user){ user.setPassword(passwordEncoder.encode(user.getPassword())); dao.save(user); } public User findById(int id) { return dao.findById(id); } public User findBySso(String sso) { return dao.findBySSO(sso); } }
That’s all you have to do to setup password encoding in your application using Spring Security’s BCrypt implementation.
Complete code example is shown below.
Complete Example
Following technologies being used:
- Spring 4.1.6.RELEASE
- Spring Security 4.0.1.RELEASE
- Hibernate 4.3.6.Final
- MySQL Server 5.6
- Maven 3
- JDK 1.7
- Tomcat 8.0.21
- Eclipse JUNO Service Release 2
Let’s begin.
Step 1: Project directory structure
Following will be the final project structure:
Step 2: Update pom.xml to include required dependencies
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.websystique.springsecurity</groupId> <artifactId>SpringSecurityPasswordEncodingWithBcryptExample</artifactId> <version>1.0.0</version> <packaging>war</packaging> <name>SpringSecurityPasswordEncodingWithBcryptExample</name> <properties> <springframework.version>4.1.6.RELEASE</springframework.version> <springsecurity.version>4.0.1.RELEASE</springsecurity.version> <hibernate.version>4.3.6.Final</hibernate.version> <mysql.connector.version>5.1.31</mysql.connector.version> </properties> <dependencies> <!-- Spring --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${springframework.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${springframework.version}</version> </dependency> <!-- Spring Security --> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-web</artifactId> <version>${springsecurity.version}</version> </dependency> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-config</artifactId> <version>${springsecurity.version}</version> </dependency> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <!-- jsr303 validation --> <dependency> <groupId>javax.validation</groupId> <artifactId>validation-api</artifactId> <version>1.1.0.Final</version> </dependency> <!-- Hibernate validators --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-validator</artifactId> <version>5.1.3.Final</version> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.connector.version}</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.1.0</version> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>javax.servlet.jsp-api</artifactId> <version>2.3.1</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> </dependencies> <build> <pluginManagement> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.7</source> <target>1.7</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <warSourceDirectory>src/main/webapp</warSourceDirectory> <warName>SpringSecurityPasswordEncodingWithBcryptExample</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </pluginManagement> <finalName>SpringSecurityPasswordEncodingWithBcryptExample</finalName> </build> </project>
Database Schema Part
Step 3: Create Database Schema & Populate dummy data
/*All User's gets stored in APP_USER table*/ create table APP_USER ( id BIGINT NOT NULL AUTO_INCREMENT, sso_id VARCHAR(30) NOT NULL, password VARCHAR(100) NOT NULL, first_name VARCHAR(30) NOT NULL, last_name VARCHAR(30) NOT NULL, email VARCHAR(30) NOT NULL, state VARCHAR(30) NOT NULL, PRIMARY KEY (id), UNIQUE (sso_id) ); /* USER_PROFILE table contains all possible roles */ create table USER_PROFILE( id BIGINT NOT NULL AUTO_INCREMENT, type VARCHAR(30) NOT NULL, PRIMARY KEY (id), UNIQUE (type) ); /* JOIN TABLE for MANY-TO-MANY relationship*/ CREATE TABLE APP_USER_USER_PROFILE ( user_id BIGINT NOT NULL, user_profile_id BIGINT NOT NULL, PRIMARY KEY (user_id, user_profile_id), CONSTRAINT FK_APP_USER FOREIGN KEY (user_id) REFERENCES APP_USER (id), CONSTRAINT FK_USER_PROFILE FOREIGN KEY (user_profile_id) REFERENCES USER_PROFILE (id) ); /* Populate USER_PROFILE Table */ INSERT INTO USER_PROFILE(type) VALUES ('USER'); INSERT INTO USER_PROFILE(type) VALUES ('ADMIN'); INSERT INTO USER_PROFILE(type) VALUES ('DBA'); /* Populate one Admin User which will further create other users for the application using GUI */ INSERT INTO APP_USER(sso_id, password, first_name, last_name, email, state) VALUES ('sam','$2a$10$4eqIF5s/ewJwHK1p8lqlFOEm2QIA0S8g6./Lok.pQxqcxaBZYChRm', 'Sam','Smith','samy@xyz.com', 'Active'); /* Populate JOIN Table */ INSERT INTO APP_USER_USER_PROFILE (user_id, user_profile_id) SELECT user.id, profile.id FROM app_user user, user_profile profile where user.sso_id='sam' and profile.type='ADMIN';
Note that we have inserted one user manually(we do need one Admin user to actually login and create further users for application). This is a real-world scenario. Notice the password. It’s generated using below mentioned utility class [it could even have been a script] which is used only and only to generate a password for one initial Admin user. It can well be removed from application.
package com.websystique.springsecurity.util; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; public class QuickPasswordEncodingGenerator { /** * @param args */ public static void main(String[] args) { String password = "abc125"; BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder(); System.out.println(passwordEncoder.encode(password)); } }
Above program generates the encoded password we used in above Schema. Please do note that you may get a different value for the same password as BCrypt hashing algorithm generates a different hash value of length 60 each time it’s encode gets invoked.
Security Part
Step 4: Add Spring Security Configuration Class
package com.websystique.springsecurity.configuration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("customUserDetailsService") UserDetailsService userDetailsService; @Autowired public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); auth.authenticationProvider(authenticationProvider()); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public DaoAuthenticationProvider authenticationProvider() { DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider(); authenticationProvider.setUserDetailsService(userDetailsService); authenticationProvider.setPasswordEncoder(passwordEncoder()); return authenticationProvider; } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/home").permitAll() .antMatchers("/admin/**","/newuser").access("hasRole('ADMIN')") .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") .and().formLogin().loginPage("/login") .usernameParameter("ssoId").passwordParameter("password") .and().csrf() .and().exceptionHandling().accessDeniedPage("/Access_Denied"); } }
Step 5: Register the springSecurityFilter with war
Below specified initializer class registers the springSecurityFilter
[created in Step 3] with application war.
package com.websystique.springsecurity.configuration; import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer; public class SecurityWebApplicationInitializer extends AbstractSecurityWebApplicationInitializer { }
Above setup in XML configuration format would be:
<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> </filter-mapping>
Step 6: Define UserDetailsService implementation
This service is responsible for providing authentication details to Authentication Manager.
package com.websystique.springsecurity.service; import java.util.ArrayList; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.websystique.springsecurity.model.User; import com.websystique.springsecurity.model.UserProfile; @Service("customUserDetailsService") public class CustomUserDetailsService implements UserDetailsService{ @Autowired private UserService userService; @Transactional(readOnly=true) public UserDetails loadUserByUsername(String ssoId) throws UsernameNotFoundException { User user = userService.findBySso(ssoId); System.out.println("User : "+user); if(user==null){ System.out.println("User not found"); throw new UsernameNotFoundException("Username not found"); } return new org.springframework.security.core.userdetails.User(user.getSsoId(), user.getPassword(), user.getState().equals("Active"), true, true, true, getGrantedAuthorities(user)); } private List<GrantedAuthority> getGrantedAuthorities(User user){ List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); for(UserProfile userProfile : user.getUserProfiles()){ System.out.println("UserProfile : "+userProfile); authorities.add(new SimpleGrantedAuthority("ROLE_"+userProfile.getType())); } System.out.print("authorities :"+authorities); return authorities; } }
SpringMVC Part
Step 7: Add Controller
package com.websystique.springsecurity.controller; import java.util.List; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import com.websystique.springsecurity.model.User; import com.websystique.springsecurity.model.UserProfile; import com.websystique.springsecurity.service.UserProfileService; import com.websystique.springsecurity.service.UserService; @Controller public class HelloWorldController { @Autowired UserProfileService userProfileService; @Autowired UserService userService; @RequestMapping(value = { "/", "/home" }, method = RequestMethod.GET) public String homePage(ModelMap model) { model.addAttribute("greeting", "Hi, Welcome to mysite"); return "welcome"; } @RequestMapping(value = "/admin", method = RequestMethod.GET) public String adminPage(ModelMap model) { model.addAttribute("user", getPrincipal()); return "admin"; } @RequestMapping(value = "/db", method = RequestMethod.GET) public String dbaPage(ModelMap model) { model.addAttribute("user", getPrincipal()); return "dba"; } @RequestMapping(value = "/Access_Denied", method = RequestMethod.GET) public String accessDeniedPage(ModelMap model) { model.addAttribute("user", getPrincipal()); return "accessDenied"; } @RequestMapping(value = "/login", method = RequestMethod.GET) public String loginPage() { return "login"; } @RequestMapping(value="/logout", method = RequestMethod.GET) public String logoutPage (HttpServletRequest request, HttpServletResponse response) { Authentication auth = SecurityContextHolder.getContext().getAuthentication(); if (auth != null){ new SecurityContextLogoutHandler().logout(request, response, auth); } return "redirect:/login?logout"; } @RequestMapping(value = "/newUser", method = RequestMethod.GET) public String newRegistration(ModelMap model) { User user = new User(); model.addAttribute("user", user); return "newuser"; } /* * This method will be called on form submission, handling POST request It * also validates the user input */ @RequestMapping(value = "/newUser", method = RequestMethod.POST) public String saveRegistration(@Valid User user, BindingResult result, ModelMap model) { if (result.hasErrors()) { System.out.println("There are errors"); return "newuser"; } userService.save(user); System.out.println("First Name : "+user.getFirstName()); System.out.println("Last Name : "+user.getLastName()); System.out.println("SSO ID : "+user.getSsoId()); System.out.println("Password : "+user.getPassword()); System.out.println("Email : "+user.getEmail()); System.out.println("Checking UsrProfiles...."); if(user.getUserProfiles()!=null){ for(UserProfile profile : user.getUserProfiles()){ System.out.println("Profile : "+ profile.getType()); } } model.addAttribute("success", "User " + user.getFirstName() + " has been registered successfully"); return "registrationsuccess"; } private String getPrincipal(){ String userName = null; Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); if (principal instanceof UserDetails) { userName = ((UserDetails)principal).getUsername(); } else { userName = principal.toString(); } return userName; } @ModelAttribute("roles") public List<UserProfile> initializeProfiles() { return userProfileService.findAll(); } }
Step 8: Add SpringMVC Configuration Class
package com.websystique.springsecurity.configuration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.format.FormatterRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.view.InternalResourceViewResolver; import org.springframework.web.servlet.view.JstlView; @Configuration @EnableWebMvc @ComponentScan(basePackages = "com.websystique.springsecurity") public class HelloWorldConfiguration extends WebMvcConfigurerAdapter { @Autowired RoleToUserProfileConverter roleToUserProfileConverter; @Override public void configureViewResolvers(ViewResolverRegistry registry) { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setViewClass(JstlView.class); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); registry.viewResolver(viewResolver); } /* * Configure ResourceHandlers to serve static resources like CSS/ Javascript etc... * */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); } /* * Configure Converter to be used. * In our example, we need a converter to convert string values[Roles] to UserProfiles in newUser.jsp */ @Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(roleToUserProfileConverter); } }
Only interesting thing in this class is registration of a converter who will be responsible for converting an id into an Object.This is required to handle one-to-many relationship in JSP. During User creation, A User can be allocated multiple roles/userProfiles, and so we need a converter to map a particular role/userProfile to a user based on profile id. Below provided is the converter class.
Above Converter setup in XML configuration will be:
<mvc:annotation-driven conversion-service="conversionService"/> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <list> <bean id="roleToUserProfile" class="com.websystique.springsecurity.configuration.RoleToUserProfileConverter" /> </list> </property> </bean>
Step 9: Add SpringMVC Converter Class
package com.websystique.springsecurity.configuration; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.converter.Converter; import org.springframework.stereotype.Component; import com.websystique.springsecurity.model.UserProfile; import com.websystique.springsecurity.service.UserProfileService; @Component public class RoleToUserProfileConverter implements Converter<Object, UserProfile>{ @Autowired UserProfileService userProfileService; /* * Gets UserProfile by Id * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ public UserProfile convert(Object element) { Integer id = Integer.parseInt((String)element); UserProfile profile= userProfileService.findById(id); System.out.println("Profile : "+profile); return profile; } /* * Gets UserProfile by type * @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object) */ /* public UserProfile convert(Object element) { String type = (String)element; UserProfile profile= userProfileService.findByType(type); System.out.println("Profile ... : "+profile); return profile; } */ }
Step 10: Add Initializer class
package com.websystique.springsecurity.configuration; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; public class SpringMvcInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return new Class[] { HelloWorldConfiguration.class }; } @Override protected Class<?>[] getServletConfigClasses() { return null; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
Hibernate Configuration Part
Step 11: Create Hibernate Configuration
Hibernate configuration class contains @Bean methods for DataSource, SessionFactory & Transaction Manager. Datasource properties are taken from
application.properties file and contains connection details for MySQL database.
package com.websystique.springsecurity.configuration; import java.util.Properties; import javax.sql.DataSource; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.core.env.Environment; import org.springframework.jdbc.datasource.DriverManagerDataSource; import org.springframework.orm.hibernate4.HibernateTransactionManager; import org.springframework.orm.hibernate4.LocalSessionFactoryBean; import org.springframework.transaction.annotation.EnableTransactionManagement; @Configuration @EnableTransactionManagement @ComponentScan({ "com.websystique.springsecurity.configuration" }) @PropertySource(value = { "classpath:application.properties" }) public class HibernateConfiguration { @Autowired private Environment environment; @Bean public LocalSessionFactoryBean sessionFactory() { LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean(); sessionFactory.setDataSource(dataSource()); sessionFactory.setPackagesToScan(new String[] { "com.websystique.springsecurity.model" }); sessionFactory.setHibernateProperties(hibernateProperties()); return sessionFactory; } @Bean public DataSource dataSource() { DriverManagerDataSource dataSource = new DriverManagerDataSource(); dataSource.setDriverClassName(environment.getRequiredProperty("jdbc.driverClassName")); dataSource.setUrl(environment.getRequiredProperty("jdbc.url")); dataSource.setUsername(environment.getRequiredProperty("jdbc.username")); dataSource.setPassword(environment.getRequiredProperty("jdbc.password")); return dataSource; } private Properties hibernateProperties() { Properties properties = new Properties(); properties.put("hibernate.dialect", environment.getRequiredProperty("hibernate.dialect")); properties.put("hibernate.show_sql", environment.getRequiredProperty("hibernate.show_sql")); properties.put("hibernate.format_sql", environment.getRequiredProperty("hibernate.format_sql")); return properties; } @Bean @Autowired public HibernateTransactionManager transactionManager(SessionFactory s) { HibernateTransactionManager txManager = new HibernateTransactionManager(); txManager.setSessionFactory(s); return txManager; } }
application.properties
jdbc.driverClassName = com.mysql.jdbc.Driver jdbc.url = jdbc:mysql://localhost:3306/websystique jdbc.username = myuser jdbc.password = mypassword hibernate.dialect = org.hibernate.dialect.MySQLDialect hibernate.show_sql = true hibernate.format_sql = true
DAO, Model & Service Part
Step 12: Create Model classes
A User can have multiple roles [DBA,ADMIN,USER]. And a Role can be assigned to more than one user. Hence there is a Many-To-Many relationship
between a User and UserProfile[role]. We kept this relationship uni-directional [User to UserProfile] as we are only interested in finding Roles for a give user (and not vice-versa). We will be using Many-To-Many association using Join table.
package com.websystique.springsecurity.model; import java.util.HashSet; import java.util.Set; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.JoinTable; import javax.persistence.ManyToMany; import javax.persistence.Table; import org.hibernate.validator.constraints.NotEmpty; @Entity @Table(name="APP_USER") public class User { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; @NotEmpty @Column(name="SSO_ID", unique=true, nullable=false) private String ssoId; @NotEmpty @Column(name="PASSWORD", nullable=false) private String password; @NotEmpty @Column(name="FIRST_NAME", nullable=false) private String firstName; @NotEmpty @Column(name="LAST_NAME", nullable=false) private String lastName; @NotEmpty @Column(name="EMAIL", nullable=false) private String email; @NotEmpty @Column(name="STATE", nullable=false) private String state=State.ACTIVE.getState(); @ManyToMany(fetch = FetchType.EAGER) @JoinTable(name = "APP_USER_USER_PROFILE", joinColumns = { @JoinColumn(name = "USER_ID") }, inverseJoinColumns = { @JoinColumn(name = "USER_PROFILE_ID") }) private Set<UserProfile> userProfiles = new HashSet<UserProfile>(); public int getId() { return id; } public void setId(int id) { this.id = id; } public String getSsoId() { return ssoId; } public void setSsoId(String ssoId) { this.ssoId = ssoId; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } 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 String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getState() { return state; } public void setState(String state) { this.state = state; } public Set<UserProfile> getUserProfiles() { return userProfiles; } public void setUserProfiles(Set<UserProfile> userProfiles) { this.userProfiles = userProfiles; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; result = prime * result + ((ssoId == null) ? 0 : ssoId.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof User)) return false; User other = (User) obj; if (id != other.id) return false; if (ssoId == null) { if (other.ssoId != null) return false; } else if (!ssoId.equals(other.ssoId)) return false; return true; } @Override public String toString() { return "User [id=" + id + ", ssoId=" + ssoId + ", password=" + password + ", firstName=" + firstName + ", lastName=" + lastName + ", email=" + email + ", state=" + state + ", userProfiles=" + userProfiles +"]"; } }
package com.websystique.springsecurity.model; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.Table; @Entity @Table(name="USER_PROFILE") public class UserProfile { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; @Column(name="TYPE", length=15, unique=true, nullable=false) private String type = UserProfileType.USER.getUserProfileType(); public int getId() { return id; } public void setId(int id) { this.id = id; } public String getType() { return type; } public void setType(String type) { this.type = type; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; result = prime * result + ((type == null) ? 0 : type.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (!(obj instanceof UserProfile)) return false; UserProfile other = (UserProfile) obj; if (id != other.id) return false; if (type == null) { if (other.type != null) return false; } else if (!type.equals(other.type)) return false; return true; } @Override public String toString() { return "UserProfile [id=" + id + ", type=" + type + "]"; } }
package com.websystique.springsecurity.model; public enum UserProfileType { USER("USER"), DBA("DBA"), ADMIN("ADMIN"); String userProfileType; private UserProfileType(String userProfileType){ this.userProfileType = userProfileType; } public String getUserProfileType(){ return userProfileType; } }
package com.websystique.springsecurity.model; public enum State { ACTIVE("Active"), INACTIVE("Inactive"), DELETED("Deleted"), LOCKED("Locked"); private String state; private State(final String state){ this.state = state; } public String getState(){ return this.state; } @Override public String toString(){ return this.state; } public String getName(){ return this.name(); } }
Step 13: Create Dao Layer
package com.websystique.springsecurity.dao; import java.io.Serializable; import java.lang.reflect.ParameterizedType; import org.hibernate.Criteria; import org.hibernate.Session; import org.hibernate.SessionFactory; import org.springframework.beans.factory.annotation.Autowired; public abstract class AbstractDao<PK extends Serializable, T> { private final Class<T> persistentClass; @SuppressWarnings("unchecked") public AbstractDao(){ this.persistentClass =(Class<T>) ((ParameterizedType) this.getClass().getGenericSuperclass()).getActualTypeArguments()[1]; } @Autowired private SessionFactory sessionFactory; protected Session getSession(){ return sessionFactory.getCurrentSession(); } @SuppressWarnings("unchecked") public T getByKey(PK key) { return (T) getSession().get(persistentClass, key); } public void persist(T entity) { getSession().persist(entity); } public void delete(T entity) { getSession().delete(entity); } protected Criteria createEntityCriteria(){ return getSession().createCriteria(persistentClass); } }
package com.websystique.springsecurity.dao; import com.websystique.springsecurity.model.User; public interface UserDao { void save(User user); User findById(int id); User findBySSO(String sso); }
package com.websystique.springsecurity.dao; import org.hibernate.Criteria; import org.hibernate.criterion.Restrictions; import org.springframework.stereotype.Repository; import com.websystique.springsecurity.model.User; @Repository("userDao") public class UserDaoImpl extends AbstractDao<Integer, User> implements UserDao { public void save(User user) { persist(user); } public User findById(int id) { return getByKey(id); } public User findBySSO(String sso) { Criteria crit = createEntityCriteria(); crit.add(Restrictions.eq("ssoId", sso)); return (User) crit.uniqueResult(); } }
package com.websystique.springsecurity.dao; import java.util.List; import com.websystique.springsecurity.model.UserProfile; public interface UserProfileDao { List<UserProfile> findAll(); UserProfile findByType(String type); UserProfile findById(int id); }
package com.websystique.springsecurity.dao; import java.util.List; import org.hibernate.Criteria; import org.hibernate.criterion.Order; import org.hibernate.criterion.Restrictions; import org.springframework.stereotype.Repository; import com.websystique.springsecurity.model.UserProfile; @Repository("userProfileDao") public class UserProfileDaoImpl extends AbstractDao<Integer, UserProfile>implements UserProfileDao{ @SuppressWarnings("unchecked") public List<UserProfile> findAll(){ Criteria crit = createEntityCriteria(); crit.addOrder(Order.asc("type")); return (List<UserProfile>)crit.list(); } public UserProfile findById(int id) { return getByKey(id); } public UserProfile findByType(String type) { Criteria crit = createEntityCriteria(); crit.add(Restrictions.eq("type", type)); return (UserProfile) crit.uniqueResult(); } }
Step 14: Create Service Layer
package com.websystique.springsecurity.service; import java.util.List; import com.websystique.springsecurity.model.UserProfile; public interface UserProfileService { List<UserProfile> findAll(); UserProfile findByType(String type); UserProfile findById(int id); }
package com.websystique.springsecurity.service; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.websystique.springsecurity.dao.UserProfileDao; import com.websystique.springsecurity.model.UserProfile; @Service("userProfileService") @Transactional public class UserProfileServiceImpl implements UserProfileService{ @Autowired UserProfileDao dao; public List<UserProfile> findAll() { return dao.findAll(); } public UserProfile findByType(String type){ return dao.findByType(type); } public UserProfile findById(int id) { return dao.findById(id); } }
package com.websystique.springsecurity.service; import com.websystique.springsecurity.model.User; public interface UserService { void save(User user); User findById(int id); User findBySso(String sso); }
package com.websystique.springsecurity.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.websystique.springsecurity.dao.UserDao; import com.websystique.springsecurity.model.User; @Service("userService") @Transactional public class UserServiceImpl implements UserService{ @Autowired private UserDao dao; @Autowired private PasswordEncoder passwordEncoder; public void save(User user){ user.setPassword(passwordEncoder.encode(user.getPassword())); dao.save(user); } public User findById(int id) { return dao.findById(id); } public User findBySso(String sso) { return dao.findBySSO(sso); } }
Views Part
Step 15: Add Views
login.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>HelloWorld Login page</title> <link href="<c:url value='/static/css/bootstrap.css' />" rel="stylesheet"></link> <link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link> <link rel="stylesheet" type="text/css" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.2.0/css/font-awesome.css" /> </head> <body> <div id="mainWrapper"> <div class="login-container"> <div class="login-card"> <div class="login-form"> <c:url var="loginUrl" value="/login" /> <form action="${loginUrl}" method="post" class="form-horizontal"> <c:if test="${param.error != null}"> <div class="alert alert-danger"> <p>Invalid username and password.</p> </div> </c:if> <c:if test="${param.logout != null}"> <div class="alert alert-success"> <p>You have been logged out successfully.</p> </div> </c:if> <div class="input-group input-sm"> <label class="input-group-addon" for="username"><i class="fa fa-user"></i></label> <input type="text" class="form-control" id="username" name="ssoId" placeholder="Enter Username" required> </div> <div class="input-group input-sm"> <label class="input-group-addon" for="password"><i class="fa fa-lock"></i></label> <input type="password" class="form-control" id="password" name="password" placeholder="Enter Password" required> </div> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" /> <div class="form-actions"> <input type="submit" class="btn btn-block btn-primary btn-default" value="Log in"> </div> </form> </div> </div> </div> </div> </body> </html>
As you can see, the CSRF parameters are accessed using EL expressions in your JSP, you may additionally prefer to force EL expressions to be evaluated, by adding following to the top of your JSP:
<%@ page isELIgnored="false"%>
welcome.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Welcome page</title> <link href="<c:url value='/static/css/bootstrap.css' />" rel="stylesheet"></link> <link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link> </head> <body> <div class="success"> Greeting : ${greeting} This is a welcome page. </div> </body> </html>
admin.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Admin page</title> <link href="<c:url value='/static/css/bootstrap.css' />" rel="stylesheet"></link> <link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link> </head> <body> <div class="success"> Dear <strong>${user}</strong>, Welcome to Admin Page. <br/> Would you like to <a href="<c:url value='/newUser' />">Add Some Users</a> to keep yourself busy? <br/> <a href="<c:url value="/logout" />">Logout</a> </div> </body> </html>
dba.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>DBA page</title> <link href="<c:url value='/static/css/bootstrap.css' />" rel="stylesheet"></link> <link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link> </head> <body> <div class="success"> Dear <strong>${user}</strong>, Welcome to DBA Page. <br/> <a href="<c:url value="/logout" />">Logout</a> </div> </body> </html>
newuser.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="form" uri="http://www.springframework.org/tags/form"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>User Registration Form</title> <link href="<c:url value='/static/css/bootstrap.css' />" rel="stylesheet"></link> <link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link> </head> <body> <div class="form-container"> <h1>New User Registration Form</h1> <form:form method="POST" modelAttribute="user" class="form-horizontal"> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="firstName">First Name</label> <div class="col-md-7"> <form:input type="text" path="firstName" id="firstName" class="form-control input-sm"/> <div class="has-error"> <form:errors path="firstName" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="lastName">Last Name</label> <div class="col-md-7"> <form:input type="text" path="lastName" id="lastName" class="form-control input-sm"/> <div class="has-error"> <form:errors path="lastName" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="ssoId">SSO ID</label> <div class="col-md-7"> <form:input type="text" path="ssoId" id="ssoId" class="form-control input-sm"/> <div class="has-error"> <form:errors path="ssoId" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="password">Password</label> <div class="col-md-7"> <form:input type="password" path="password" id="password" class="form-control input-sm"/> <div class="has-error"> <form:errors path="password" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="email">Email</label> <div class="col-md-7"> <form:input type="text" path="email" id="email" class="form-control input-sm"/> <div class="has-error"> <form:errors path="email" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-group col-md-12"> <label class="col-md-3 control-lable" for="userProfiles">Roles</label> <div class="col-md-7"> <form:select path="userProfiles" items="${roles}" multiple="true" itemValue="id" itemLabel="type" class="form-control input-sm"/> <div class="has-error"> <form:errors path="userProfiles" class="help-inline"/> </div> </div> </div> </div> <div class="row"> <div class="form-actions floatRight"> <input type="submit" value="Register" class="btn btn-primary btn-sm"> or <a href="<c:url value='/admin' />">Cancel</a> </div> </div> </form:form> </div> </body> </html>
registrationsuccess.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>User Registration Form</title> <link href="<c:url value='/static/css/bootstrap.css' />" rel="stylesheet"></link> <link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link> </head> <body> <div class="success"> Confirmation message : ${success} <br> Would you like to <a href="<c:url value='/newUser' />">Add More Users</a>? <br/> Go to <a href="<c:url value='/admin' />">Admin Page</a> OR <a href="<c:url value="/logout" />">Logout</a> </div> </body> </html>
accessDenied.jsp
<%@ page language="java" contentType="text/html; charset=ISO-8859-1" pageEncoding="ISO-8859-1"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>AccessDenied page</title> <link href="<c:url value='/static/css/bootstrap.css' />" rel="stylesheet"></link> <link href="<c:url value='/static/css/app.css' />" rel="stylesheet"></link> </head> <body> Dear <strong>${user}</strong>, You are not authorized to access this page. <br/> <a href="<c:url value="/home" />">Go to home</a> OR <a href="<c:url value="/logout" />">Logout</a> </body> </html>
Step 16: Build and Deploy the application
Now build the war (either by eclipse/m2eclipse) or via maven command line( mvn clean install
). Deploy the war to a Servlet 3.0 container . Since here i am using Tomcat, i will simply put this war file into tomcat webapps folder
and click on start.bat
inside tomcat bin directory.
Run the application
Open browser and goto localhost:8080/SpringSecurityPasswordEncodingWithBcryptExample/
Now try to access localhost:8080/SpringSecurityPasswordEncodingWithBcryptExample/admin, you will be prompted for login, provide Admin Role credentials (sam,abc125)(only user in system at this moment)
Submit
Click on Add Some Users link
Fill in details [password : abc123], select USER as Role
Submit.
Click Again on Add Some Users link, fill in details [password : abc127] , Select ADMIN,DBA as roles
Submit
Click on logout. Fill in DBA credentials (kenny,abc127) (as created in previous step)
Submit. Now try to access localhost:8080/SpringSecurityPasswordEncodingWithBcryptExample/db
Logout
Verify Database
That’s it. Next post shows you Remember-Me authentication using Spring Security 4 with Hibernate.
Download Source Code
Annotation Example
XML Example
References
If you like tutorials on this site, why not take a step further and connect me on Facebook , Google Plus & Twitter as well? I would love to hear your thoughts on these articles, it will help improve further our learning process.