This tutorial shows you Remember-Me authentication using Spring Security 4 with Hibernate. Let’s get going.
In Remember-me
or persistent-login authentication, Applications remember the identity of user between sessions. Basically, during login, when you ask for Remember-Me support, application will send a cookie to the browser during login. This cookie will be stored at browser side and will remain there for certain period(defined by cookie lifetime). Next time when you try to access the application, browser will detect the cookie (if still valid) and user will be automatically logged in, without providing userid/password e.g.
Spring Security provides two implementations for Remember-Me :
In this post, we will be discussing about Persistent Token Approach
Modifications compare to normal login posts:
1. With Persistent Token Approach, the database should contain a persistent_logins table, created using the following SQL (or equivalent):
CREATE TABLE persistent_logins ( username VARCHAR(64) NOT NULL, series VARCHAR(64) NOT NULL, token VARCHAR(64) NOT NULL, last_used TIMESTAMP NOT NULL, PRIMARY KEY (series) );
This table contains the username, last_used timestamp of active remember-me, security token and series information which are internals of Spring Bcrypt implementation. For further details, please refer here.
2. Configure Remember-Me in Spring Security
package com.websystique.springsecurity.configuration; import javax.sql.DataSource; 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.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.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("customUserDetailsService") UserDetailsService userDetailsService; @Autowired DataSource dataSource; @Autowired public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/home").permitAll() .antMatchers("/admin/**").access("hasRole('ADMIN')") .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") .and().formLogin().loginPage("/login") .usernameParameter("ssoId").passwordParameter("password") .and().rememberMe().rememberMeParameter("remember-me").tokenRepository(persistentTokenRepository()).tokenValiditySeconds(86400) .and().csrf() .and().exceptionHandling().accessDeniedPage("/Access_Denied"); } @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl(); tokenRepositoryImpl.setDataSource(dataSource); return tokenRepositoryImpl; } }
Notice how we called rememberMe() to configure Remember-Me authenticationm providing HTTP parameter name attached with remember-me checkbox from view(we will see in view). We have also specified the tokenRepository(where the token will be stored) to be used, and how long (in seconds) this token remains valid ( we have provided 1 day). To configure Repository itself we have injected a DataSource here.
This is all you need to activate Remember-Me in your Spring Security based application.
Optionally, you can use Spring Security Built-in expressions along with Spring security tags in your view to customize/show/hide specific logic based on Remember-Me or full-authentication.
Note : In case you are not willing to use JDBC, may be because rest of your application is Hibernate based, have a look at Spring MVC 4 + Spring Security 4 + Hibernate Example post, where i’ve created a Hibernate based implementation of PersistentTokenRepository, named as HibernateTokenRepositoryImpl.
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"/> </authentication-manager> <remember-me remember-me-parameter="remember-me" remember-me-cookie="remember-me" token-validity-seconds="86400" data-source-ref="dataSource" /> <beans:bean id="customUserDetailsService" class="com.websystique.springsecurity.service.CustomUserDetailsService" /> </beans:beans>
Complete code example is shown below.
Following technologies being used:
Let’s begin.
Following will be the final project structure:
<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>SpringSecurityRememberMeAnnotationExample</artifactId> <version>1.0.0</version> <packaging>war</packaging> <name>SpringSecurityRememberMeAnnotationExample</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.version>5.1.31</mysql.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> <dependency> <groupId>org.springframework.security</groupId> <artifactId>spring-security-taglibs</artifactId> <version>${springsecurity.version}</version> </dependency> <!-- Hibernate --> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-core</artifactId> <version>${hibernate.version}</version> </dependency> <!-- MySQL --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql.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>SpringSecurityRememberMeAnnotationExample</warName> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> </pluginManagement> <finalName>SpringSecurityRememberMeAnnotationExample</finalName> </build> </project>
/* For Remember-Me token storage purpose */CREATE TABLE persistent_logins ( username VARCHAR(64) NOT NULL, series VARCHAR(64) NOT NULL, token VARCHAR(64) NOT NULL, last_used TIMESTAMP NOT NULL, PRIMARY KEY (series) ); /*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. We need only one user to demonstrate this example. You can add more as done in previous posts*/INSERT INTO APP_USER(sso_id, password, first_name, last_name, email, state) VALUES ('sam','abc125', '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';
package com.websystique.springsecurity.configuration; import javax.sql.DataSource; 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.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.web.authentication.rememberme.JdbcTokenRepositoryImpl; import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository; @Configuration @EnableWebSecurity public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("customUserDetailsService") UserDetailsService userDetailsService; @Autowired DataSource dataSource; @Autowired public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService); } @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/", "/home").permitAll() .antMatchers("/admin/**").access("hasRole('ADMIN')") .antMatchers("/db/**").access("hasRole('ADMIN') and hasRole('DBA')") .and().formLogin().loginPage("/login") .usernameParameter("ssoId").passwordParameter("password") .and().rememberMe().rememberMeParameter("remember-me").tokenRepository(persistentTokenRepository()).tokenValiditySeconds(86400) .and().csrf() .and().exceptionHandling().accessDeniedPage("/Access_Denied"); } @Bean public PersistentTokenRepository persistentTokenRepository() { JdbcTokenRepositoryImpl tokenRepositoryImpl = new JdbcTokenRepositoryImpl(); tokenRepositoryImpl.setDataSource(dataSource); return tokenRepositoryImpl; } }
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>
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; } }
package com.websystique.springsecurity.controller; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; 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.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class HelloWorldController { @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"; } 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; } }
package com.websystique.springsecurity.configuration; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.ViewResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; 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 { @Bean(name="HelloWorld") public ViewResolver viewResolver() { InternalResourceViewResolver viewResolver = new InternalResourceViewResolver(); viewResolver.setViewClass(JstlView.class); viewResolver.setPrefix("/WEB-INF/views/"); viewResolver.setSuffix(".jsp"); return viewResolver; } /* * Configure ResourceHandlers to serve static resources like CSS/ Javascript etc... * */ @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/static/**").addResourceLocations("/static/"); } }
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 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
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; @Entity @Table(name="APP_USER") public class User { @Id @GeneratedValue(strategy=GenerationType.IDENTITY) private int id; @Column(name="SSO_ID", unique=true, nullable=false) private String ssoId; @Column(name="PASSWORD", nullable=false) private String password; @Column(name="FIRST_NAME", nullable=false) private String firstName; @Column(name="LAST_NAME", nullable=false) private String lastName; @Column(name="EMAIL", nullable=false) private String email; @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(); } }
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 { 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 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.service; import com.websystique.springsecurity.model.User; public interface UserService { User findById(int id); User findBySso(String sso); }
package com.websystique.springsecurity.service; import org.springframework.beans.factory.annotation.Autowired; 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; public User findById(int id) { return dao.findById(id); } public User findBySso(String sso) { return dao.findBySSO(sso); } }
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" >admin.jsp</code> <%@ 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"%> <%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags"%> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1"> <title>Admin page</title> </head> <body> Dear <strong>${user}</strong>, Welcome to Admin Page. <sec:authorize access="isFullyAuthenticated()"> <label><a href="#">Create New User</a> | <a href="#">View existing Users</a></label> </sec:authorize> <sec:authorize access="isRememberMe()"> <label><a href="#">View existing Users</a></label> </sec:authorize> <a href="<c:url value="/logout" />">Logout</a> </body> </html>
Below mentioned views are not required for this post, just mentioned here for completion.
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>
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> </head> <body> Dear <strong>${user}</strong>, Welcome to DBA Page. <a href="<c:url value="/logout" />">Logout</a> </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> </head> <body> Dear <strong>${user}</strong>, You are not authorized to access this page <a href="<c:url value="/logout" />">Logout</a> </body> </html>
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/SpringSecurityRememberMeAnnotationExample/admin
you will be prompted for login.
Login with Remember-Me checked in. You will get admin view
Verify Cookies. Open Google Chrome Settings/Show advanced settings/Content Setting/All cookies and site data, fill localhost in serachbox. You will find two cookies, one for current session [JSESSIONID], and one for Remember-Me authentication [remember-me].
Click on remember-me cookie to view the details. You can see that cookie will expire in one day(as was set in Security Configuration)
Now verify persistent_logins table in database.There should be an entry in there for current user.
Now logout.
Try to goto localhost:8080/SpringSecurityRememberMeAnnotationExample/admin again, You should not be prompted for login.
You can also see that ‘Create New User’ option is not available as that one is only available when user is full authenticated(using login/password e.g.)
Now remove the remember-me cookie from chrome(as shown before), and try to access localhost:8080/SpringSecurityRememberMeAnnotationExample/admin again, You should be prompted for login.
That’s it. Next post shows Method level security using Spring Security @PreAuthorize, @PostAuthorize, @Secured and Spring EL expressions.
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.
In this post we will be developing a full-blown CRUD application using Spring Boot, AngularJS, Spring Data, JPA/Hibernate and MySQL,…
Spring Boot complements Spring REST support by providing default dependencies/converters out of the box. Writing RESTful services in Spring Boot…
Being able to start the application as standalone jar is great, but sometimes it might not be possible to run…
Spring framework has taken the software development industry by storm. Dependency Injection, rock solid MVC framework, Transaction management, messaging support,…
Let's secure our Spring REST API using OAuth2 this time, a simple guide showing what is required to secure a…
This post shows how an AngularJS application can consume a REST API which is secured with Basic authentication using Spring…
View Comments
Hi there,
Got a very general problem with connection of the app to the database:
I created the db, launched the app on a server (Glass fish) but it did not retrieve the credentials provided in APP_USER table. What could I have possible done wrong here?
Observations:
1. My database reproduces exactly the tables given in this tutorial and appliction.properties file is well present in the app.
2. My server works alright and for tutorials without persistence the app used to work perfectly and always accept credentials.
Thanks in advance, cheers...
Hi websystique! could you please add some tests using junit to this tutorial code? thank you
Hi, Thank you very much for your educational efforts. I have one doubt regarding the working of enum here. please excuse me for my ignorance. Here you have used
@Column(name="TYPE", length=15, unique=true, nullable=false)
private String type = UserProfileType.USER.getUserProfileType();
and when sam is entering, he get admin privilege. In toString it display "Admin"
But how you got this.. what I am expecting is UserProfileType.USER should give value "User". Could you please give me a clarity on "how you are getting admin there"
hi,
I am implementing role based login following your examples and it is working fine and when I add remember me to the same example the password encryption doesn't work. can't we implement both together with existing examples?
Hello! What behavior should I expect after clicking logout? In this example after logout I can still go to admin page without login, my cookies and token in DB are still validate.
Hi Vladimir, Yes this is expected behavior as i explained above in very detail & screenshots. With Remember-me, the token and cookies remain valid until validity time expires. But it is possible to force user to login while accessing certain part of your site [remember Amazon shopping-cart?], even if they have remember me set. Let me know if you still have any doubt.
Thanks a lot for a super quick answer :-) Yes, your example is the best i found. But i still don't understand how to do if i want to invalidate session after user logout. I think it will work if i will use hibernate for removing token from DB. But is it possible to to without removing token from DB?
I tried to do in security config something like this:
.and().logout().permitAll().logoutUrl("/logout")
.deleteCookies("JSESSIONID")
.logoutSuccessUrl("/login?logout");
.invalidateHttpSession(true)
tried to do in controller:
SecurityContextLogoutHandler securityContextLogoutHandler =
new SecurityContextLogoutHandler();
securityContextLogoutHandler.logout(request, response, auth);
securityContextLogoutHandler.setClearAuthentication(true);
securityContextLogoutHandler.setInvalidateHttpSession(false);
request.getSession().invalidate();
even tried to set coockie max age like this:
Cookie[] cookies = request.getCookies()
for (Cookie cookie : cookies) {
if (cookie.getName().equals("JESSIONID")) {
cookie.setMaxAge(0);
}
}
Hi Vladimir,
Why would you want to preserve the token in database, if you are force invalidating the session? Please keep things consistent by removing both(remember-me & JSESSIONID) and removing token from hibernate.Otherwise you may encounter unexpected results.
Ok, thanks! I'll try to do this with hibernate. Can you advise approaches to remove cookies with Spring in the best way? Should I remove them in security config (deleteCookies("JSESSIONID")) or in controller (cookie.setMaxAge(0);) or somewhere else?
Hi Vladimir,
I would prefer second approach as this is a generic way to get rid of cookies.
I solved my problem only with this code in my controller:
request.logout();
Spring Security integrates with servlet and understands that it should remove cookies and persistent login from database.
Thanks!
I'm working on a new project where there are two parts.
The first part is a web-app, it is a collection of RESTful web-services on the back-end. I am implementing Spring Security 4 and assigning roles to specific URL's.
The second is the front-end web-site with your traditional html5, css, and jquery which will be making a
lot of AJAX calls to the back-end.
I expect both of these will be on the same Tomcat 8 instance, but as two different web-apps ...
So, we have a load balance, and when I call: https://my.website.com/myapp
We get re-directed to OpenAM 10, we have a login page, I can put in my username and password, get authenticated, and then redirected to my new front-end site.
BTW ... we do not have groups or roles in our OpenAM or OpenDJ.
So, at this point, I have an OpenAM 10 token in a cookie in my web-browser ....
I can take a token, and call an OpenAM web-service, and I can get the username, and some other information as well, that works.
But some how, in some way, I'd like to pass that token to the backend when I do an AJAX call ... and I can do it manually, but do I need to?
So, how does this work? How does that token get passed back to the backend?
When that does happen, we should make a call to OpenAM to get who that user is, and get their username?
In Spring Secutity 4, I have marked several URL's to work with only certain roles.
In Spring Security 4, I have a CustomUserDetails which, based on a username, looks up a user from our database, and gets their roles.
Any ideas? Thanks!
Hi Thomas, Sorry for late reply. BTW, did you consider sending this info via setting HTTP headers?
I found an example of using Spring Security 4 with something called SiteForce, or something like that. This is where a third-party is establishing a token with a particular name and that token is placed in the header. I took this example, and I changed it a little bit. From the
Hi,
The example is educational, however, I also found it to be incomplete since you are only handling the user related entities with hibernate. The "persistent_logins" entries are still being handled by the *Jdbc*TokenRepositoryImpl.
Hi Daniel,
Thanks for the remark. However, I see it differently. Hibernate is not really managing the persistent_logins table. This table is managed by Spring security itself. Application code does not perform any explicit insert delete in this table using hibernate. If you are not managing it, why would you make it an Entity then?
If that was your assumption then the title of your article should had been "Spring Security 4 UserDetailsService Example with Hibernate" as the remember functionality has nothing to do with hibernate in your example.
I only discovered your article because I was researching hibernate implementations of the "remember-me" functionality. Regarding your question "why?" supposed I need to extend/customize the table "persistent_logins" (e.g. different table name, change fields names/datatype lengths, add more columns for auditing), I'd rather use hibernate consistently instead of dealing with jdbc just for that.
Hi Daniel,
Recently i stumbled again on your valuable comments, and so finally created a post implementing Hibernate version of PersistentTokenRepository this time. Feel free to have a look & comment on Spring Security 4+Spring MVC 4 + hibernate Integration. Suggestions on improvement are more than welcome.
Thanks.
Hi,
You did great job again. Works like a charm! But I wonder if it is possible that the project create database table (if not exist) itself at startup? I can force it via "ddl-auto = update" with my JPA projects but it didn't work on this project. :
Thanks again.