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

How to Implement AWS RDS Database IAM Authentication in Spring Boot

Amazon RDS for MySQL allows authentication using AWS Identity and Access Management (IAM) database authentication. With this authentication method, you don't need to use a password when you connect to a DB instance. Instead, you use an authentication token. Let us understand how this works? An authentication token is a unique string of characters that Amazon RDS generates on request. Authentication tokens are generated using AWS Signature Version 4. Each token has a lifetime of 15 minutes. You don't need to store user credentials in the database, because authentication is managed externally using IAM. You can also still use standard database authentication. Since IAM authentication tokens are short-lived access tokens that are valid for 15 minutes. For the RDS database this token works as a database password that is required to establish a connection and does not determine how long the existing connection can last. The default value for connection to be alive without activit

How to upload files in Amazon S3 Bucket using Spring Boot

As stated in the title, we are going to demonstrate that how we can upload and retrieve files from the amazon s3 bucket in spring boot. For this, we must have an account on amazon web services (AWS) . And the next thing you need to have is an IAM user that has programmatic access to the s3 bucket. Follow the steps below to create an IAM user and s3 bucket. Table of Contents 1. Steps to create an IAM user in AWS with S3 bucket full access permission Step 1.1 Login to your AWS account   Step 1.2 Set the user details Step 1.3 Set user permissions Step 1.4 Create a user group and set the access policy Step 1.5 Add user to the group Step 1.6  Set the tags (optional) Step 1.7  Review the user details and permission summary Step 1.8 Download the user credentials 2. See, how to create s3 bucket. Step 2.1 Click on the "Create bucket" button. Step 2.2 Enter the bucket name and select bucket region. Step 2.3 Set file accessibility for bucket items as public/private

What Is SSL Certificate and how it works?

Deep Dive into SSL Certificate What Is an SSL Certificate? SSL (Secure Sockets Layer) is the common name for TLS (Transport Layer Security), a security protocol that enables encrypted communications between two machines. An SSL certificate is a small data file leveraging this security protocol to serve two functions: Authentication – SSL certificates serve as credentials to authenticate the identity of a website. They are issued to a specific domain name and web server after a Certificate Authority, also known as a Certification Authority (CA), performs a strict vetting process on the organization requesting the certificate. Depending on the certificate type, it can provide information about a business or website's identity and authenticate that the website is a legitimate business. Secure data communication - When SSL is installed on a web server, it enables the padlock to appear in the web browser. It activates the HTTPS protocol and creates a secure connection between th

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.

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 to

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