
Tutorial 0 : Authentication with spring security
This tutorial will show you how to implement a login process using the following tech stack:
- Spring Boot
- Spring Security
- JPA (Hibernate)
- Spring Data JPA
- PostgreSQL
- Spring MVC
- Thymeleaf
- Bootstrap
- Java 8
Introduction
Spring Security is one of the Spring Framework’s Security modules. It is a Java SE/Java EE Security Framework to provide Authentication, Authorization, SSO and other Security features for Web Applications or Enterprise Applications.
Define JPA Entities
JPA Entity is defined with annotation, represent a table in the database.
maps the entity with the table. If no is defined, the default value is used for the class name of the entity.
declares the identifier property of the entity (primary key).
indicates the entity is the owner of the relationship: the corresponding table has a column with a foreign key to the referenced table.
indicates the entity is the inverse of the relationship.
src/main/java/com/intellitech/springlabs/model/User.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 |
package com.intellitech.springlabs.model; import java.io.Serializable; 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.OneToOne; import javax.persistence.Table; @Entity @Table(name = "user", schema = "public") public class User implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Long id; @Column(name = "first_name", length = 75) private String firstName; @Column(name = "last_name", length = 80) private String lastName; @Column(name = "username", length = 65) private String username; @Column(name = "password", length = 64) private String password; @Column(name = "email", unique = true, length = 115) private String email; @OneToOne(fetch = FetchType.EAGER) @JoinColumn(name = "role_id") private Role role; public User() { } public Long getId() { return id; } public void setId(Long id) { this.id = id; } 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 getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public Role getRole() { return role; } public void setRole(Role role) { this.role = role; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } } |
src/main/java/com/intellitech/springlabs/model/Role.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
package com.intellitech.springlabs.model; import java.io.Serializable; 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 = "role", schema = "public") public class Role implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "id") private Integer id; @Column(name = "role_name", length = 65) private String roleName; public Role() { } public Integer getId() { return id; } public void setId(Integer id) { this.id = id; } public String getRoleName() { return roleName; } public void setRoleName(String roleName) { this.roleName = roleName; } } |
Spring Data JPA Repositories
Spring Data JPA contains some built-in implemented some common functions to work with database: ,…
src/main/java/com/intellitech/repository/UserRepository.java
1 2 3 4 5 6 7 8 9 10 |
package com.intellitech.springlabs.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.intellitech.springlabs.model.User; public interface UserRepository extends JpaRepository { @Query("SELECT u FROM User u WHERE u.username=:usernameOrEmail OR u.email=:usernameOrEmail") User findByUsernameOrEmail(String usernameOrEmail); } |
src/main/java/com/intellitech/repository/Role.java
1 2 3 4 5 6 7 8 9 |
package com.intellitech.springlabs.repository; import org.springframework.data.jpa.repository.JpaRepository; import com.intellitech.springlabs.model.Role; public interface RoleRepository extends JpaRepository{ } |
Service Layer
Now let´s create user service layer(interface and implementation). We will inject the UserRepository into our service class.
src/main/java/com/intellitech/service/UserService.java
1 2 3 4 5 6 7 |
package com.intellitech.springlabs.service; import com.intellitech.springlabs.model.User; public interface UserService { User findByUsernameOrEmail(String usernameOrEmail); } |
src/main/java/com/intellitech/service/impl/UserServiceImpl.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
package com.intellitech.springlabs.service.impl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.intellitech.springlabs.model.User; import com.intellitech.springlabs.repository.UserRepository; import com.intellitech.springlabs.service.UserService; @Service("userService") public class UserServiceImpl implements UserService { @Autowired private UserRepository userRepository; @Override @Transactional(readOnly = true) public User findByUsernameOrEmail(String usernameOrEmail) { User user = null; try { user = userRepository.findByUsernameOrEmail(usernameOrEmail); } catch (Exception e) { throw e; } return user; } } |
Secure the application
To implement login/authentication with Spring Security, we need to implement interface.This interface requires only one method loadUserByUsername(String username).
src/main/java/com/intellitech/security/CustomUserDetailsService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package com.intellitech.springlabs.security; 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.intellitech.springlabs.model.Role; import com.intellitech.springlabs.model.User; import com.intellitech.springlabs.service.UserService; @Service("customUserDetailsService") public class CustomUserDetailsService implements UserDetailsService { @Autowired private UserService userService; @Override @Transactional(readOnly = true) public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { if (username.trim().isEmpty()) { throw new UsernameNotFoundException("username is empty"); } User user = userService.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User " + username + " not found"); } return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), getGrantedAuthorities(user)); } private List<GrantedAuthority> getGrantedAuthorities(User user) { List<GrantedAuthority> authorities = new ArrayList<GrantedAuthority>(); Role role = user.getRole(); authorities.add(new SimpleGrantedAuthority(role.getRoleName())); return authorities; } } |
For web applications, we need to create a specific class that extends . This class have multiples methods we ovveride to setup our security configuration.
- protected void configure(HttpSecurity http): this method will embed all the authorized requests and urls allowed for public access in application and the ones that are secured. In our case we permit access to all just for the home page. Any other request from users must be over authenfication before access. We have also the possibility to define a login page for authentificate users.
- protected void configure(AuthenticationManagerBuilder authManagerBuilder): This overriden method serves to specify to the AuthentificationManagerBuilder which UserDetailsService and PasswordEncoder we are going to use.
src/main/java/com/intellitech/SecurityConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
package com.intellitech.springlabs; 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.AuthenticationManager; import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 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; @Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true) public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired @Qualifier("customUserDetailsService") private UserDetailsService customUserDetailsService; @Override protected void configure(HttpSecurity http) throws Exception { http.csrf().disable() .authorizeRequests().antMatchers("/").permitAll() .anyRequest().authenticated() .and().formLogin().loginPage("/login").defaultSuccessUrl("/welcome").failureUrl("/login?error=true").permitAll() .and().logout().deleteCookies("JSESSIONID").logoutUrl("/logout").logoutSuccessUrl("/login"); } @Override protected void configure(AuthenticationManagerBuilder authManagerBuilder) throws Exception { authManagerBuilder.userDetailsService(customUserDetailsService).passwordEncoder(bCryptPasswordEncoder()); } @Bean public BCryptPasswordEncoder bCryptPasswordEncoder() { return new BCryptPasswordEncoder(); } @Bean @Override public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } } |
src/main/java/com/intellitech/SpringlabsApplication.java
1 2 3 4 5 6 7 8 9 10 11 12 |
package com.intellitech.springlabs; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class SpringlabsApplication { public static void main(String[] args) { SpringApplication.run(SpringlabsApplication.class, args); } } |
Manage the views
The register request is used to map all views in the application.
it’s mapping the two views:
- login.html page
- welcome.html page
src/main/java/com/intellitech/WebMvcConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
package com.intellitech.springlabs; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; @Configuration public class WebMvcConfig extends WebMvcConfigurerAdapter{ @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/login").setViewName("login"); registry.addViewController("/welcome").setViewName("welcome"); registry.addViewController("/").setViewName("login"); } } |
Here is the thymeleaf code source for the two views described above. Thymeleaf based view should be located in resources/templates folder :
src/main/java/ressources/templates/login.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <head> <title>Login</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"> <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script> </head> <body> <div class="container"> <br/><br/> <form th:action="@{/login}" method="POST" class="form-signin" style="width:30%;margin:auto"> <h3 class="form-signin-heading">LOGIN PAGE</h3> <br/> <div align="center" th:if="${param.error}"> <p style="font-size: 20; color: #FF1C19;">Username or password is invalid</p> </div> <input type="text" id="username" name="username" th:placeholder="Username or email" class="form-control" /> <br/> <input type="password" th:placeholder="Password" id="password" name="password" class="form-control" /> <br /> <button class="btn btn-lg btn-primary btn-block" name="Submit" value="Login" type="Submit" th:text="Login"></button> </form> </div> </body> </html> |
src/main/java/resources/templates/welcome.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Welcome</title> </head> <body> <h1 style="width: 50%; margin: auto">WELCOME TO SPRING LABS 2017</h1> <br/> <a th:href="@{/logout}"> <button type="button">LOGOUT</button> </a> </body> </html> |
Run the application
First you need to create a database with name and run the application by launching the SpringBootApplication class using the command mvn spring-boot:run and visit
Run the script in PostgreSQL database :
1 2 3 4 |
INSERT INTO public.role (id, role_name) VALUES (1, 'ROLE_ADMIN'); INSERT INTO public.role (id, role_name) VALUES (2, 'ROLE_USER'); INSERT INTO public.user (id, first_name, last_name, email, password, username, role_id) VALUES (1, 'Admin', 'Admin','admin@gmail.com', '$2a$10$bpNMKeaQXKpJ4JVxOHWvu.tZdmCLT9nKcZreJ/ELfCgmTCyhC7GPy', 'admin', 1); INSERT INTO public.user (id, first_name, last_name, email, password, username, role_id) VALUES (2, 'User', 'User','user@gmail.com', '$2a$10$TA.UfUqLa8uDeGkt95FfLeq7T5Y5vpDpzAtvJrHSLzLliY/PARXUq', 'user', 2); |
Authenticate with one of the two users:
: username=admin, password=admin
: username=user, password=user
Download Source code from