Skip to main content

How to Implement Spring Security in Spring Boot

Security Example in Spring Boot

Implementation of Spring Security in the Spring Boot application is the key point to learn for spring boot developers. Because Authentication and Authorization are the backbones of the whole application.

Getting started with the Spring Security Series, this is the first part, in this article we are going to focus on the authentication part with minimal registration. The implementation of registration flow with email verification, customizing password encoding, and setting up password strengths and rules will be explored in another separate article for each. 

This article will be the base of the spring security series, the other security features will be explained on the basis of this implementation, so be focused and let's understand. The code contains proper naming & brief comments that makes it very comprehensive. If you feel any difficulty or find any issue, please drop a comment below this post

The main goal of this article is to implement optional login means login either with an email or mobile number and password.

 

1. Initialize the project with dependencies listed below:

  1. Spring Web
  2. Spring Security
  3. Thymeleaf
  4. Thymeleaf Extra Security5
  5. Spring Data JPA
  6. Spring Boot Dev Tools
  7. Lombok
  8. MySql

You can use any built tool. To know what to choose go to   Maven or Gradle

2. Now create a User model

Create an Entity class User.java in the models' package. You may aware of the annotations used in creating entity class. If not then we have explained below the code.

@Getter
@Setter
@NoArgsConstructor
@Entity
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;
    @Column(unique = true)
    String uniqueId = UUID.randomUUID().toString();
    String name;
    @Column(unique = true)
    String email;
    @Column(unique = true)
    String mobile;
    String password;
    String role;
    String profilePic;
    LocalDateTime createdOn;
    LocalDateTime updatedOn;

    @PrePersist
    public void creationSpot(){
        this.createdOn=LocalDateTime.now();
        this.updatedOn=LocalDateTime.now();
    }
    @PreUpdate
    public void updationSpot(){
        this.updatedOn=LocalDateTime.now();
    }

}

Annotations used in the above code are self-explanatory but keeping in mind for beginners we would like to describe as follows:

  1. @Getter, @Setter, and @NoArgsConstructor are the annotations of Lombok dev tools used to generate getters, setters, and a constructor with no argument respectively. 
  2. Other annotations are defined well in this article Annotations related to Data Persistency 

3. Creating a repository  for performing database operations for the entity User

For working with JPA we need to create an interface and extend the JpaRepository by telling the entity name and Id type as we have defined in the entity. To do this, create UserRepository.java and define two methods to fetch user details from the database by email id and mobile number, see below code.

public interface UserRepository extends JpaRepository<User,Long> {
    Optional<User> findByEmail(String email);
    Optional<User> findByMobile(String mobile);
//..other methods
}

4. Implementing the UserDetails for authentication

     For authentication, we need to implement the UserDetails interface of the spring security core. Its data will be written in login principal after successful login. We can implement it in the User entity also, but to keep things organized well let's make it separately.

@Getter
@Setter
public class CustomUserPrincipal implements UserDetails, Serializable {

    private String  uniqueId;
    private String name;
    private String email;
    private String mobile;
    private String profilePic;
    @JsonIgnore
    private String username;
    @JsonIgnore
    private String password;
    @JsonIgnore
    private Collection<? extends GrantedAuthority> authorities;

   public CustomUserPrincipal(String uniqueId, String loggedInName, String profilePic, String username, String email, String mobile, String password, 
                                                          Collection<? extends GrantedAuthority> authorities) {
        this.uniqueId = uniqueId;
        this.name =loggedInName;
        this.email=email;
        this.mobile=mobile;
        this.profilePic=profilePic;
        this.username = username;
        this.password = password;
        this.authorities = authorities;
    }

    public static CustomUserPrincipal createWithEmail(User user) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(user.getRole().toString()));
        return new CustomUserPrincipal(
                user.getUniqueId(),
                user.getName(),
                user.getProfilePic(),
                user.getEmail(),
                user.getEmail(),
                user.getMobile(),
                user.getPassword(),
                authorities
        );
    }
    public static CustomUserPrincipal createWithMobile(User user) {
        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority(user.getRole().toString()));
        return new CustomUserPrincipal(
                user.getUniqueId(),
                user.getName(),
                user.getProfilePic(),
                user.getMobile(),
                user.getEmail(),
                user.getMobile(),
                user.getPassword(),
                authorities
          );
    }
    @Override
    public String getUsername() {  return username; }

    @Override
    public String getPassword() { return password; }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() { return authorities; }

    @Override
    public boolean isAccountNonExpired() { return true; }

    @Override
    public boolean isAccountNonLocked() { return true; }

    @Override
    public boolean isCredentialsNonExpired() {  return true; }

    @Override
    public boolean isEnabled() {  return true; }
}

5. Implement the UserDetailsService for handling UserDetails

This is required to implement because the spring security calls method loadUserByUsername(username) that are specified in the UserDetailsService interface. 

And also we define a method that authenticates users first time just after registration by using the UsernameAuthenticationToken class.

@Service
@Log
public class SecurityService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository;
    @Autowired
    private AuthenticationManager authenticationManager;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        // Let people login with either email or mobile
        CustomUserPrincipal userPrincipal = null;
        Optional<User> user = null;

        /* check user if user enters email as username*/
        user = userRepository.findByEmail(username);
        if (user.isPresent())
            userPrincipal = CustomUserPrincipal.createWithEmail(user.get());
        else {
            /* check user if user not present with email, username may be mobile no.*/
            user = userRepository.findByMobile(username);
            if (user.isPresent())
                userPrincipal = CustomUserPrincipal.createWithMobile(user.get());
        }
        /* check that user is present with email or mobile */
        if (userPrincipal == null)
            throw new UsernameNotFoundException("User not found with username: " + username);
        else
            return userPrincipal;
    }

/* Username Password Authentication method to allow user to log in just after registration*/
    public void loginFirstTime(User user,String plainPassowrd) {
        log.info("Auto login .....!");
        UserDetails userDetails = loadUserByUsername(user.getEmail());
        UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(userDetails, plainPassowrd, userDetails.getAuthorities());
        authenticationManager.authenticate(usernamePasswordAuthenticationToken);
        if (usernamePasswordAuthenticationToken.isAuthenticated()) {
            SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
            log.info(String.format("Auto login %s successfully!", user.getEmail()));
        } else System.err.println("auto login failed");
    }
}

6. Define the security configurations by extending WebSecurityConfigurerAdapter

Here we are defining the security rules of the application

@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(
        securedEnabled = true,
        jsr250Enabled = true,
        prePostEnabled = true
)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private SecurityService customSecurityService;

    /* this will handle the login form, will call the loadUserByUsername method to get user details
       then match password */
    @Override
    public void configure(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(customSecurityService).passwordEncoder(passwordEncoder());
    }

    @Override
    public void configure(HttpSecurity httpSecurity) throws Exception {
        httpSecurity
                .authorizeRequests()
                /* permitting these pages to be opened without login */
                .antMatchers("/","/login","/register","/assets/**").permitAll()
                /* assets will contain css, js and fonts, etc */
                /* other pages having any url pattern will require login */
                .anyRequest().authenticated()
                .and()
                .formLogin()
                .loginPage("/login")
                /* always redirect to profile page after successful login  */
                .defaultSuccessUrl("/profile", true)
                .and()
                .logout()
                .permitAll();
        httpSecurity.csrf().disable();
        httpSecurity.headers().frameOptions().disable();
    }

/* these beans will be created for autowiring in components/services */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    @Bean
    public BCryptPasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}

7. Create user service to handle user operations

Inside the user service, we will create methods for registration, get security principal, and user details of the logged-in user, and update the method to change security principal details without logout.

@Service
public class UserService {
    @Autowired
    private SecurityService securityService;
    @Autowired
    private BCryptPasswordEncoder passwordEncoder;

    @Autowired
    private UserRepository userRepository;

    public ModelAndView registerUser(User user) {
        ModelAndView mv = new ModelAndView("profile");
        Optional<User> checkUser = userRepository.findByEmail(user.getEmail());
        if(!checkUser.isPresent()){
            checkUser = userRepository.findByMobile(user.getMobile());
            if(!checkUser.isPresent()) {
                String plainPassowrd = user.getPassword();
                user.setPassword(passwordEncoder.encode(plainPassowrd));
                //setting the default role as string
                user.setRole("ROLE_USER");
                /* role based authorization will be demonstrated in part 2 of this series
                 keep visiting easytutorials.live */
                user = userRepository.save(user);

                /* auto login first time */
                securityService.loginFirstTime(user,plainPassowrd);
            }else{
                mv.setViewName("register");
                mv.addObject("error","A user is already present with this mobile number");
            }
        }else{
            mv.setViewName("register");
            mv.addObject("error","User already registered with this email");
        }
        return mv;
    }


/* This method will use to change the authentication principal details without logout */
    public void updateAuthenticationDetails(User updatedUser) {
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        CustomUserPrincipal user = (CustomUserPrincipal) auth.getPrincipal();
        user.setName(updatedUser.getName());
        user.setProfilePic(updatedUser.getProfilePic());
        user.setEmail(updatedUser.getEmail());
        user.setMobile(updatedUser.getMobile());
        Authentication newAuth = new UsernamePasswordAuthenticationToken(user, auth.getCredentials(), auth.getAuthorities());
        SecurityContextHolder.getContext().setAuthentication(newAuth);
    }

    /* if you need something with logged in user principal details */
    public CustomUserPrincipal getLoggedInUserPrincipal(){
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        CustomUserPrincipal user = (CustomUserPrincipal) auth.getPrincipal();
        return user;
    }

    /* if you need all details of logged in user */
    public User getLoggedInUser(){
        return userRepository.findByEmail(getLoggedInUserPrincipal().getEmail()).get();
    }
}

8. Create a controller to control authentication

@Controller
public class AuthController {

    @Autowired
    private UserService userService;

    @GetMapping(value = {"/", "/login"})
    public String login(Model model, String error, String logout, Principal principal) {
        if (principal != null)
            return "redirect:/profile";
        if (error != null)
            model.addAttribute("error", error);

        if (logout != null)
            model.addAttribute("message", "You have been logged out successfully.");

        return "login";
    }

    @GetMapping("/register")
    public String register(Model model, Principal principal) {
        if (principal != null) {
            return "redirect:/profile";
        }
        return "register";
    }

    @PostMapping("/register")
    public ModelAndView register(User user) {
        ModelAndView mv = userService.registerUser(user);
        return mv;
    }

    /* to check the authentication principal details */
    @GetMapping("/principal")
    @ResponseBody
    public Principal principal(Principal principal) {
        return principal;
    }

    /* you can only open this page if user is authenticated, why? see SecurityConfig.java */
    @GetMapping("/profile")
    public String profile(Model model) {
        /* simply open the profile page
         *  and display the name, email, mobile number and profile picture
         *  through authentication principal, how? see profile.html */
        return "/profile";
    }
}

9. Application properties

spring.datasource.url=jdbc:mysql://localhost:3306/springsecurity?createDatabaseIfNotExist=true
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
server.port=8888

10. Create a registration page

we are showing you here the minimum required lines 

<!-- minimal design of registration form, you can design yourself or visit github link provided below -->
<form action="/register" method="post">
  <input type="text" placeholder="Your Name" name="name" required>
  <input type="email" placeholder="Your Email" name="email"   required>
  <input type="text" placeholder="Mobile number"  name="mobile"  required>
  <input type="password" placeholder="New password" name="password" required>
  <button type="submit">Register</button>
</form>

11. Create a login page

Minimal HTML code required for the login form. You can find the source code link of this project below. 

<!-- minimal design of login form, you can design yourself or visit github link provided below -->
  <form action="/login" method="post">
      <input type="text" placeholder="Email or mobile"   name="username" required>
      <input type="password" placeholder="Enter password"  name="password"  required>
      <button type="submit">Sign In</button>
      <p>Do not have account? <a href="/register">REGISTER HERE</a></p>
 </form>

12. Create profile page (the first page after login)

<!--  showing you the essential part to write the authentication detail in html page using thymeleaf -->
<div class="card">
   <div class="profile-pic" th:with="pic=${#authentication.principal.profilePic}">
    <img th:src="${pic!=null?'data:image/png;base64,'+pic:'/assets/user-icon.png'}">
   </div>
   <br>
   <p class="text-center"><strong th:text="${#authentication.principal.name}"></strong></p>
   <p>Email : <span  th:text="${#authentication.principal.email}"></span></p>
   <p>Mobile:<span th:text="${#authentication.principal.mobile}"></span></p>         
    </div>
</div>

13. Screens

 Login Page 

spring boot login page

 

Profile Page

Spring Security Profile Page

 

Database table data

 

Thanks for reading this article, I hope you understand well the above explanation.

And as always you can find the source code on GitHub

Popular posts from this blog

Custom Pagination with search and filters in Spring Boot

Every spring boot application is made to manage a large set of data. Also, we need to perform a search and filter the data according to need, And also we cannot load all data in one go on a single page so we need pagination too. In this article, we are going to demonstrate custom pagination with search and filter performed through ajax call. Goal: This demonstration is performed on a set of students' data. We have written a method to generate sample data.   Table of Contents 1. Initialize the project with the following dependencies 2. Set the application properties 3. Create the Student entity 4. Enum to denote the class of student 5. Create JPA repository of entity 6. Create the search & filter command object (CO) 7. Create a data transfer object (DTO) of the Entity for returning the response 8. Create a service for implementing the business login 9. Create a controller 10. Create a utility class for date conversions 11. Create the HTML Data Table design 12. ...

Request Mapping Annotation in Spring Boot

The @RequestMapping is a class level  (also called type level) and method level annotation, it is used to process HTTP requests with specified URL patterns. It is used in and along with both @Controller and @RestController . Table of Contents Request Mapping Annotation in Spring Boot 1. How @RequestMapping annotation it is used? 2. Optional Elements of @RequestMapping 2.1 name, value and path 2.2 headers, consumes and produces 3. Specialization of @RequestMapping 1. How @RequestMapping annotation it is used? @Controller @RequestMapping("/student") public class StudentController{ @RequestMapping("/dashboard") public String dashboard(){ return "dashboard"; } @RequestMapping("result") public String result(){ return "result"; } } We can see in above code sample "/student" , "/dashboard" and "result" passed with annotation are called request value/path present in the URL ...

Maven or Gradle - built tool selection in Spring Boot

  Spring Boot -Selection of built tool Gradle Gradle is an open-source build automation tool that is designed to be flexible enough to build almost any type of software, It is fully open source and similar to Maven and Ant. But Gradle has taken advantage of both Maven and Ant and also it has removed the disadvantages of Maven and Ant and created as a first-class built tool. It uses domain-specific language based on the programming language Groovy , differentiating it from Apache Maven, which uses XML for its project configuration. Gradle allows to create or customize built procedure and we can create an additional task with groovy scripts that can be executed before/after built. It also determines the order of tasks run by using a directed acyclic graph . Several developers created Gradle and first released in 2007, and in 2013, it was adopted by Google as the build system for Android projects. It was designed to support multi-project builds that are expected...

Application Properties And Environment Profiling In Spring Boot

Application Properties As we know Spring Boot configures almost all the configurations automatically, it also enables us to customize the configurations and properties according to our needs and environment. There are various methods which we can use to do the same. We can either write all properties in a textual file, do programmatically in our Java classes, or can set it while starting the application through CLI by passing command-line arguments. By default, Spring Initializr creates an application.properties file inside the project's class path. But we can also define it in the YAML file. Know the differences between the .properties file and .yml file The properties that we are talking about are database credentials and URL, server port, logging file path, catch control variables, can write any custom string constants, etc. Table of Contents Application Properties  Method 1: Using application.properties  Method 2: Using application.yml file  Method 3...