In this write-up, we will cover in-depth how to secure a Spring Boot REST API without reinventing the wheel. Spring Security and JWT have got us covered.

Protecting our APIs against any type of misuse that falls out of normal behavior is a crucial consideration that we should never ignore to make our APIs more secure and efficient.

So, we will walk you through the whole process of configuring Spring Security along with JWT authentication to secure a Spring Boot REST API.

Stay tuned…

Project Overview

Throughout this tutorial, we’ll create a basic Spring Boot REST API and secure it with Spring Security and JWT.

For a better and clear understanding, we’re going to divide the development process of our project into three main parts.

1) Build a simple RESTful API with Spring Boot for managing a list of employees stored in H2 database.

In general, the API will expose the following endpoints:

GET /protected/employees get all Employees from database
GET /protected/employee/{id} retrieve an Employee by his ID
POST /protected/employees create a new Employee resource
PUT /protected/employees/{id} get and update one specific Employee details
DELETE /protected/employee/{id} delete a particular Employee based on his ID

2) Build an Auth API that lets the users log in and generates JWT tokens for successfully authenticated users.

3) Configure Spring Security with JWT to secure our Employee REST API from unauthorized users.

In short, the workflow of the application can be described as follows:

  • A client sends a POST request to sign in using his username and password

  • The Auth API generates a JWT token if the client is successfully authenticated

  • The client sends HTTP requests, with the generated JWT token included, to access protected resources

  • If the specified JWT token is not valid, Spring Security will throw an unauthorized error

What we will use?

To build our application with all the details described above, we need to install the following tools on our machine: JDK 8, MySQL, and a Java IDE.

We’ll rely on the following technology stack to write the necessary code required to build and secure our Employee REST API:

  • Maven

  • Spring Boot

  • Spring Security

  • JPA/Hibernate

  • Jackson

  • jjwt

JWT Authentication Workflow

JWT is a cryptographically signed JSON token that provides a great way to achieve secured token-based authentication.

Nowadays, JSON Web Token becomes a standard for securing API authentication and server-to-server authorization.

The nice thing about JWT is that it is self-contained, verified, and trusted! Even Google relies on it to secure its APIs.

In general, a typical JWT object is a combination of three main parts:

  • Header: contains details about the type of the token and the algorithm used to sign the content.

  • Payload: holds the actual data that we want to send. It is not recommended to put sensitive data (emails, passwords) in the payload.

  • Signature: ensures that the token is trustworthy and not changed along the way to the destination.

JWT authentication flow

The workflow of JWT based authentication can be summarized as follows:

  • The client provides his username and password to log in.

  • The server generates a JWT token for the user if the supplied credentials are valid.

  • every time the client sends a request to access a secured resource, the server verifies the JWT signature.

  • If the signature is valid, the server grants access, otherwise it denies access to the protected resource.

Spring Security Workflow

Spring Security provides a handy way to easily implement authentication/authorization logic to secure spring-based applications.

In this tutorial, we are going to use Spring Security with JWT to secure a RESTful API from unauthenticated and unauthorized users.

Authentication Flow With Spring Security

In general, when an auth request is intercepted by Spring Security, it goes through a list of filters (filter chain) until it reaches the right authentication filter based on the specified authentication method (HTTP Basic, Form-based…).

For example, a basic Auth request goes through the filter chain until it reaches BasicAuthenticationFilter.

The right filter captures the supplied credentials and delegates the request to AuthenticationManager. This interface provides authenticate() method to encapsulate the logic of performing the authentication mechanism.

spring security authentication flow

Spring Security provides many built-in AuthenticationFilter implementations. Each implementation is responsible for handling one specific authentication mechanism.

Since we will be using JWT, we will need to create a custom JWTAuthenticationFilter class to deal with our JWT-based authentication process.

spring security JWT authentication flow

Build Employee REST API

In this section, we are going to build a basic Employee API using Spring Boot, but we will not go into much detail.

We have already covered in detail how to create a Spring Boot REST API with JPA/Hibernate and MySQL here.

So, let’s skip over the details and get to the point.

Configure H2 Datasource

First, we are going to configure H2 database. To do so, add the following lines in application.properties:

    
        spring.datasource.url=jdbc:h2:mem:testdb
        spring.datasource.driverClassName=org.h2.Driver
        spring.datasource.username=sa
        spring.datasource.password=
        spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
    

By default, Spring Boot creates an in-memory data source with the username sa and an empty password.

Next, let’s create data.sql file under src/main/resources to prepare some data:

    
        DROP TABLE IF EXISTS users_roles;
        DROP TABLE IF EXISTS users;
        DROP TABLE IF EXISTS roles;
        DROP TABLE IF EXISTS employees;

        CREATE TABLE users (
          id INT AUTO_INCREMENT  PRIMARY KEY,
          username VARCHAR(250) NOT NULL,
          password VARCHAR(250) NOT NULL,
          enabled BOOLEAN
        );

        INSERT INTO users (username, password, enabled) VALUES
          ('admin', '$2a$10$e6H1Jgrft/scpmpzbMFO0uqF1gxqop73l5wOlwF30Aem6Tty1nI2G', true),
          ('azhwani', '$2a$10$kWWOnNOiToOxcIQ7UJ.cB.XFAflYvMS5BPASR1eqqojc6H9ELWUfC', true),
          ('guest', '$2a$10$SNsPkXTh0ryc82.D2HRJqOcY8sYh/TPnJW8WLqrERWkOq01ViWaCq', true);

        CREATE TABLE roles (
          id INT AUTO_INCREMENT  PRIMARY KEY,
          name VARCHAR(250) NOT NULL
        );

        INSERT INTO roles (name) VALUES
          ('USER'),
          ('ADMIN'),
          ('MANAGER');


        CREATE TABLE users_roles (
          user_id INTEGER NOT NULL,
          role_id INTEGER NOT NULL,
          FOREIGN KEY(user_id) REFERENCES users(id),
          FOREIGN KEY(role_id) REFERENCES roles(id),
          PRIMARY KEY (user_id, role_id)
        );

        INSERT INTO users_roles (user_id, role_id) VALUES
          (1, 2),
          (2, 1),
          (2, 2),
          (2, 3),
          (3, 1);

        CREATE TABLE employees (
          id INT AUTO_INCREMENT  PRIMARY KEY,
          first_name VARCHAR(250) NOT NULL,
          last_name VARCHAR(250) NOT NULL,
          job VARCHAR(250) NOT NULL,
          salary DECIMAL(9,2) NOT NULL
        );

        INSERT INTO employees (first_name, last_name, job, salary) VALUES
          ('JAMES', 'Jones', 'Laborer', 3000),
          ('ROBERT', 'Williams', 'Retail sales associate', 4500),
          ('MICHAEL', 'Johnson', 'Administrative assistant', 6600);
    

Spring Boot will automatically run data.sql to insert default data into tables during application startup.

Employee Entity

Next, let’s define a JPA Entity for our domain model employee:

    
        @Entity
        @Table(name = "employees")
        public class Employee implements Serializable {

            private static final long serialVersionUID = 1L;
            @Id
            @GeneratedValue(strategy = GenerationType.IDENTITY)
            private int id;
            @NotNull
            private String firstName;
            @NotNull
            private String lastName;
            @NotNull
            private String job;
            @NotNull
            private double salary;

            public int getId() {
                return id;
            }
            public void setId(int 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 getJob() {
                return job;
            }
            public void setJob(String job) {
                this.job = job;
            }
            public double getSalary() {
                return salary;
            }
            public void setSalary(double salary) {
                this.salary = salary;
            }
        }
    

Employee JPA Repository

Now, we need to implement a custom JPA repository for Employee entity class. To do that, we will use Spring’s JpaRepository interface:

    
        @Repository
        public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
        }
    

Spring uses the SimpleJpaRepository class under the hood as the default implementation for repository interfaces.

Employee DTO

Next up, let’s create a DTO class (Data Transfer Object pattern) for the Employee entity:

    
        public class EmployeeDTO implements Serializable {

            private static final long serialVersionUID = 1L;

            @NotBlank
            private String firstName;
            @NotBlank
            private String lastName;
            @NotBlank
            private String job;
            @Positive
            @Min(value = 100)
            private double salary;

            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 getJob() {
                return job;
            }
            public void setJob(String job) {
                this.job = job;
            }
            public double getSalary() {
                return salary;
            }
            public void setSalary(double salary) {
                this.salary = salary;
            }
        }
    

DTO pattern allows to shift data in expensive remote calls. As we can see, EmployeeDTO holds only the required data we want to transfer.

DTO/Entity Mapper

Next up, we need to create a simple helper class to convert EmployeeDTO objects to Employee objects and vice versa:

    
        public class EmployeeMapper {

            public static EmployeeDTO entityToDTO(Employee emp) {
                EmployeeDTO empdto = new EmployeeDTO();
                empdto.setFirstName(emp.getFirstName());
                empdto.setLastName(emp.getLastName());
                empdto.setJob(emp.getJob());
                empdto.setSalary(emp.getSalary());
                return empdto;
            }
            public static Employee dtoToEntity(EmployeeDTO empdto) {
                Employee emp = new Employee();
                emp.setFirstName(empdto.getFirstName());
                emp.setLastName(empdto.getLastName());
                emp.setJob(empdto.getJob());
                emp.setSalary(empdto.getSalary());
                return emp;
            }
        }
    

Employee Controller

Lastly, let’s implement a basic RESTful Spring controller to expose our API’s endpoints:

    
        @RestController
        @RequestMapping(value = "/protected")
        @Validated
        public class EmployeeController {
            @Autowired
            EmployeeRepository employeeRepository;

            @GetMapping(value = "/employees")
            List findAll() {
                return employeeRepository.findAll();
            }
            @PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
            @GetMapping(value = "/employee/{id}")
            ResponseEntity<Employee> findById(@PathVariable("id") @Min(1) int id) {
                Employee emp = employeeRepository.findById(id)
                    .orElseThrow(() -> new EmployeeNotFoundException("Employee with ID :" + id));
                return ResponseEntity.ok().body(emp);
            }
            @PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
            @PostMapping(value = "/employees")
            ResponseEntity<?> CreateEmployee(@Valid @RequestBody EmployeeDTO empdto) {
                Employee emp = EmployeeMapper.dtoToEntity(empdto);
                Employee addedemp = employeeRepository.save(emp);
                URI location = ServletUriComponentsBuilder.fromCurrentRequest().path("/{id}").buildAndExpand(addedemp.getId())
                    .toUri();
                return ResponseEntity.created(location).build();
            }
            @PreAuthorize("hasAnyRole('ADMIN','MANAGER')")
            @PutMapping(value = "/employee/{id}")
            ResponseEntity<Employee> updateEmployee(@PathVariable("id") @Min(1) int id,
                    @Valid @RequestBody EmployeeDTO empdto) {
                Employee emp = employeeRepository.findById(id)
                    .orElseThrow(() -> new EmployeeNotFoundException("Employee with ID :" + id));
                Employee empu = EmployeeMapper.dtoToEntity(empdto);
                empu.setId(emp.getId());
                employeeRepository.save(empu);
                return ResponseEntity.ok().body(empu);
            }
            @PreAuthorize("hasAuthority('ROLE_MANAGER')")
            @DeleteMapping(value = "/employee/{id}")
            ResponseEntity<String> deleteEmployee(@PathVariable("id") @Min(1) int id) {
                Employee emp = employeeRepository.findById(id)
                    .orElseThrow(() -> new EmployeeNotFoundException("Employee Not Found with ID :" + id));
                employeeRepository.deleteById(emp.getId());
                return ResponseEntity.ok().body("Employee deleted with success!");
            }
        }
    

As we can see, we used @PreAuthorize annotation to configure security at the method level.

For example, we told Spring security that only users with role ADMIN or MANAGER are authorized to create a new Employee.

REST API Exception Handling

In this chapter, we are going to create the necessary code required to handle exceptions that may be thrown by our API.

Our article on how to handle REST API exceptions does a good job of covering this. Feel free to check it.

Custom API Error Class

Let’s start by creating a custom class - ApiError - to represent an error response that might be returned by our REST API:

    
        public class ApiError {
    
            @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy hh:mm:ss")
            private LocalDateTime timestamp;
            private String message;
            private List<String> errors;

            public ApiError(LocalDateTime timestamp, String message, List<String> errors) {
                this.timestamp = timestamp;
                this.message = message;
                this.errors = errors;
            }
            public LocalDateTime getTimestamp() {
                return timestamp;
            }
            public void setTimestamp(LocalDateTime timestamp) {
                this.timestamp = timestamp;
            }
            public String getMessage() {
                return message;
            }
            public void setMessage(String message) {
                this.message = message;
            }
            public List<String> getErrors() {
                return errors;
            }
            public void setErrors(List<String> errors) {
                this.errors = errors;
            }
        }
    

Employee NotFound Exception Class

Now, we are going to create EmployeeNotFoundException class. This custom exception will be thrown when a requested Employee is not found in the database.

    
        public class EmployeeNotFoundException extends RuntimeException {
            private static final long serialVersionUID = 1L;

            public EmployeeNotFoundException(String message) {
                super(message);
            }
        }
    

Global Exception Handler

Finally, let’s create a global handler class to deal with some relevant API-related exceptions across the whole application:

    
        @ControllerAdvice
        public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
            @Override
            protected ResponseEntity<Object> handleMethodArgumentNotValid(
                MethodArgumentNotValidException ex,
                HttpHeaders headers, 
                HttpStatus status, 
                WebRequest request) {

                List<String> fieldErrors = ex.getBindingResult()
                    .getFieldErrors()
                    .stream()
                    .map(field -> field.getField() + ", " + field.getDefaultMessage())
                    .collect(Collectors.toList());
                
                List<String> globalErrors = ex.getBindingResult()
                    .getGlobalErrors()
                    .stream()
                    .map(field -> field.getObjectName() + ", " + field.getDefaultMessage())
                    .collect(Collectors.toList());
                List<String> errors = new ArrayList<String>();
                errors.addAll(globalErrors);
                errors.addAll(fieldErrors);
            
                ApiError err = new ApiError(LocalDateTime.now(),
                    "Validation Errors" ,
                    errors);
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(err);
            }
            @ExceptionHandler(ConstraintViolationException.class)
            public ResponseEntity<Object> handleConstraintViolationException(
                Exception ex, 
                WebRequest request) {
                
                List<String> details = new ArrayList<String>();
                details.add(ex.getMessage());
                ApiError err = new ApiError(
                    LocalDateTime.now(), 
                    "Constraint Violation" ,
                    details);
                
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(err);
            }
            @ExceptionHandler(EmployeeNotFoundException.class)
            public ResponseEntity<Object> handleResourceNotFoundException(
                    EmployeeNotFoundException ex) {
                
                List<String> details = new ArrayList<String>();
                details.add(ex.getMessage());
                ApiError err = new ApiError(
                    LocalDateTime.now(), 
                    "Employee Not Found" ,
                    details);
                return ResponseEntity.status(HttpStatus.NOT_FOUND).body(err);
            }
        }
    

Secure REST API with Spring Security and JWT

Now that we have created our REST API, it is time to see how to configure Spring Security with JWT to secure it.

In this tutorial, we will assume that a user can have many roles and a role can be granted to many users.

So, the association between the User class and the Role class will be many-to-many. This is why we have created the association table users_roles.

RoleName Enum

Let’s assume here that we have three user roles:

  • USER

  • ADMIN

  • MANAGER

    
        public enum RoleName {
            USER, ADMIN, MANAGER
        }
    

Role Entity Class

The Role entity simply holds an enum value which denotes a role name:

    
        @Entity
        @Table(name = "roles")
        public class Role {
            @Id
            @GeneratedValue(strategy=GenerationType.IDENTITY)
            private int id;
            @Enumerated(EnumType.STRING)
            private RoleName name;

            public int getId() {
                return id;
            }
            public void setId(int id) {
                this.id = id;
            }
            public RoleName getName() {
                return name;
            }
            public void setName(RoleName name) {
                this.name = name;
            }
        }
    

User Entity Class

Now that we have defined the roles, let’s create the User class:

    
        @Entity
        @Table(name = "users")
        public class User {
            
            @Id
            @GeneratedValue(strategy=GenerationType.IDENTITY)
            private int id;
            @NotNull
            private String username;
            @NotNull
            private String password;
            @NotNull
            private boolean enabled;
            @ManyToMany(fetch=FetchType.LAZY)
            @JoinTable(
                name="users_roles",
                joinColumns= {@JoinColumn(name="user_id")},
                inverseJoinColumns = {@JoinColumn(name="role_id")}
            )
            private List<Role> roles;

            public int getId() {
                return id;
            }
            public void setId(int id) {
                this.id = id;
            }
            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 boolean isEnabled() {
                return enabled;
            }
            public void setEnabled(boolean enabled) {
                this.enabled = enabled;
            }
            public List<Role> getRoles() {
                return roles;
            }
            public void setRoles(List<Role> roles) {
                this.roles = roles;
            }
        }
    

User class contains a list of roles and the necessary data (username, password …) required for authentication.

User JPA Repositoty

Newt, we will create a custom JPA repository for User class:

    
        @Repository
        public interface UserRepository extends JpaRepository {
            Optional<User> findByUsername(String username);
        }
    

Create UserPrincipal

Now, let’s create an implementation of the UserDetails interface to get the details of the currently authenticated principal user:

    
        public class UserPrincipal implements UserDetails {

            private static final long serialVersionUID = 1L;
            private String username;
            private String password;
            private boolean enabled;
            private Collection<? extends GrantedAuthority> authorities;

            @Override
            public Collection<? extends GrantedAuthority> getAuthorities() {
                return authorities;
            }
            @Override
            public String getPassword() {
                return password;
            }
            @Override
            public String getUsername() {
                return username;
            }
            @Override
            public boolean isAccountNonExpired() {
                return true;
            }
            @Override
            public boolean isAccountNonLocked() {
                return true;
            }
            @Override
            public boolean isCredentialsNonExpired() {
                return true;
            }
            @Override
            public boolean isEnabled() {
                return enabled;
            }
            public void setUsername(String username) {
                this.username = username;
            }
            public void setPassword(String password) {
                this.password = password;
            }
            public void setEnabled(boolean enabled) {
                this.enabled = enabled;
            }
            public void setAuthorities(Collection<? extends GrantedAuthority> authorities) {
                this.authorities = authorities;
            }
        }
    

User to UserPrincipal Mapper

Next, let’s implement the logic of maping a User object to a UserPrincipal object:

    
        public class UserMapper {
            public static UserPrincipal userToPrincipal(User user) {
                UserPrincipal userp = new UserPrincipal();
                List<SimpleGrantedAuthority> authorities = user.getRoles().stream()
                    .map(role -> new SimpleGrantedAuthority("ROLE_" + role.getName())).collect(Collectors.toList());

                userp.setUsername(user.getUsername());
                userp.setPassword(user.getPassword());
                userp.setEnabled(user.isEnabled());
                userp.setAuthorities(authorities);
                return userp;
            }
        }
    

Create Custom UserDetailsService

UserDetailsService is one of the core interfaces in Spring Security. It provides loadUserByUsername() method to load user-related data:

    
        @Service
        @Transactional
        public class UserDetailsServiceImpl implements UserDetailsService {
            @Autowired
            private UserRepository userRepository;
            @Override
            public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
                User user = userRepository.findByUsername(username)
                    .orElseThrow(() -> new UsernameNotFoundException("User NOT Found"));
                return UserMapper.userToPrincipal(user);
            }
        }
    

As we can see, the method calls findByUsername of the UserRepository to retrieve a user by username.

An exception of type UsernameNotFoundException is thrown if there is no corresponding user.

JWT Provider

Our JWT provider will handle two important things:

  • Generating a token from the authenticated user

  • Parsing the generated token to retrieve the username

    
        @Component
        public class JwtProvider {
            @Value("${app.jwtSecret}")
            private String jwtSecret;
            @Value("${app.jwtExpirationInMs}")
            private int jwtExpirationInMs;

            public String generateToken(Authentication authentication) {
                UserPrincipal userPrincipal = (UserPrincipal) authentication.getPrincipal();
                Date now = new Date();
                Date expiryDate = new Date(now.getTime() + jwtExpirationInMs);

                return Jwts.builder()
                    .setSubject(userPrincipal.getUsername())
                    .setIssuedAt(new Date())
                    .setExpiration(expiryDate)
                    .signWith(SignatureAlgorithm.HS512, jwtSecret)
                    .compact();
            }
            public String getUserUsernameFromJWT(String token) {
                Claims claims = Jwts.parser()
                    .setSigningKey(jwtSecret)
                    .parseClaimsJws(token)
                    .getBody();
                return claims.getSubject();
            }
            public boolean validateToken(String authToken) {
                try {
                    Jwts.parser()
                        .setSigningKey(jwtSecret)
                        .parseClaimsJws(authToken);
                    return true;
                } catch (SignatureException ex) {
                } catch (MalformedJwtException ex) {
                } catch (ExpiredJwtException ex) {
                } catch (UnsupportedJwtException ex) {
                } catch (IllegalArgumentException ex) {
                }
                return false;
            }
        }
    

@Component allows Spring to automatically detect and register our JwtProvider class as a Spring bean.

JWT Authentication Filter

Now, it’s time to add the most important piece of the puzzle. We need to create a filter to act as the main entry point of the JWT authentication mechanism.

    
        public class JwtAuthenticationFilter extends OncePerRequestFilter {
            @Autowired
            private JwtProvider tokenProvider;
            @Autowired
            private UserDetailsService userDetailsService;
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                throws ServletException, IOException {
                try {
                    String jwt = getJwtFromRequest(request);
                    if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
                        String username = tokenProvider.getUserUsernameFromJWT(jwt);
                        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
                        UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                            userDetails, null, userDetails.getAuthorities());
                        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                        SecurityContextHolder.getContext().setAuthentication(authentication);
                    }
                } catch (Exception ex) {
                    logger.error("Could not set user authentication in security context", ex);
                }
                filterChain.doFilter(request, response);
            }
            private String getJwtFromRequest(HttpServletRequest request) {
                String bearerToken = request.getHeader("Authorization");
                if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
                    return bearerToken.substring(7, bearerToken.length());
                }
                return null;
            }
        }
    

This filter extracts the JWT token from the request header Authorization, retrieves the username from the token, and calls the userDetailsService to load a user based on the extracted username.

User Authentication Controller

Now, we are going to create a Spring controller to handle the process of user authentication and JWT token generation:

    
        @RestController
        @RequestMapping("/auth")
        public class AuthController {
            @Autowired
            AuthenticationManager authenticationManager;
            @Autowired
            JwtProvider tokenProvider;
            @PostMapping("/signin")
            public ResponseEntity<JwtResponse> authenticateUser(@Valid @RequestBody LoginDTO login) {
                Authentication authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(login.getUsername(), login.getPassword()));
                SecurityContextHolder.getContext().setAuthentication(authentication);
                String jwt = tokenProvider.generateToken(authentication);
                return ResponseEntity.ok(new JwtResponse(jwt));
            }
        }            
    

Spring Security Configuration Class

Lastly, let’s put all the pieces together and create the security configuration class:

    
        @Configuration
        @EnableWebSecurity
        @EnableGlobalMethodSecurity(prePostEnabled = true)
        public class SecurityAdapter extends WebSecurityConfigurerAdapter {
            @Autowired
            private UserDetailsService userDetailsService;
            @Bean
            public JwtAuthenticationFilter jwtAuthenticationFilter() {
                return new JwtAuthenticationFilter();
            }
            @Bean
            public PasswordEncoder passwordEncoder() {
                return new BCryptPasswordEncoder();
            }
            @Bean
            @Override
            public AuthenticationManager authenticationManagerBean() throws Exception {
                return super.authenticationManagerBean();
            }
            @Override
            protected void configure(AuthenticationManagerBuilder auth) throws Exception {
                auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
            }
            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http
                .headers().frameOptions().sameOrigin()
                .and()
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/auth/**").permitAll()
                .antMatchers("/protected/**").authenticated()
                .and()
                .exceptionHandling()
                .authenticationEntryPoint((req, res, ex) -> res.sendError(HttpServletResponse.SC_UNAUTHORIZED, "UNAUTHORIZED : " + ex.getMessage()))
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
            }
        }
    

Let’s dive into the explication:

  • @EnableWebSecurity tells Spring to enable web security for our application

  • @EnableGlobalMethodSecurity provides AOP security on methods. The attribute prePostEnabled = true enables the usage of pre/post security annotations

As shown above, we overrode configure(AuthenticationManagerBuilder auth) method. This method encapsulates the logic to process user authentication.

Similarly, configure(HttpSecurity http) holds the logic of configuring web-based security for http requests.

As we can see, we have configured the authorization so that all requests are allowed on /auth/ path.

However, only authenticated users can send requests to our API’s endpoints.

authenticationEntryPoint() tells spring security to send a response error when an unauthenticated user tries to access a protected resource that requires authentication.

We used addFilterBefore method to register and add our custom filter JwtAuthenticationFilter before UsernamePasswordAuthenticationFilter.

Testing Security With JWT

Now that we put all the pieces together, let’s test if we secured our Spring Boot REST API with JWT the right way.

To test our API’s endpoints, we will be using curl command.

Test User Authentication and Token Generation

    
        C:\Users\Asus>curl -X POST -H "Content-Type: application/json" -d "{\"username\":\"azhwani\",\"password\":\"@zhwaniPass\"}" http://localhost:9001/auth/signin
        {
            "token":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhemh3YW5pIiwiaWF0IjoxNjI5NjU4NjcxLCJleHAiOjE2Mjk3NDUwNzF9.XwQtf0G1X5DLziYbCfKyuFFCUptzG40NVHW6VZouHNP0JBnveZWjokYvWcIdAE8vAAKbhMHxLfWNVJNHbr9Mww",
            "tokenHeader":"Bearer"
        }
        C:\Users\Asus>curl -X POST -H "Content-Type: application/json" -d "{\"username\":\"user\",\"password\":\"wrongPass\"}" http://localhost:9001/auth/signin
        {
            "timestamp":"2021-08-22T19:02:09.679+0000",
            "status":401,
            "error":"Unauthorized",
            "message":"UNAUTHORIZED : Bad credentials",
            "path":"/auth/signin"
        }       
    

GET /protected/employees for Unauthenticated User

    
        C:\Users\Asus>curl -X GET -H "Content-Type: application/json" http://localhost:9001/protected/employees
        {
            "timestamp":"2021-08-22T19:28:12.666+0000",
            "status":401,
            "error":"Unauthorized",
            "message":"UNAUTHORIZED : Full authentication is required to access this resource",
            "path":"/protected/employees"
        }
        C:\Users\Asus>
    

GET /protected/employee/{id} for Authenticated User with Valid JWT Token

    
        C:\Users\Asus>curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJhemh3YW5pIiwiaWF0IjoxNjI5NjYwOTQ1LCJleHAiOjE2Mjk3NDczNDV9._4BbiXZ4wec6SBngNA-KLeGWJKNZLDRXcOPpwaMG97qXxoR2sqmK90eqNFL7imCE6bIDAQWfVA1ybh4JOew3jw" http://localhost:9001/protected/employee/1
        {
            "id":1,
            "firstName":"JAMES",
            "lastName":"Jones",
            "job"::"Laborer",
            "salary":3000.0
        }
        C:\Users\Asus>
    

GET /protected/employee/{id} for Unauthorized User

    
        curl -X POST -H "Content-Type: application/json" -d ""{\"username\":\"guest\",\"password\":\"guestPasswd\"}" http://localhost:9001/auth/signin
        {
            "token":"eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJndWVzdCIsImlhdCI6MTYyOTY2MTUwOCwiZXhwIjoxNjI5NzQ3OTA4fQ.H2VjKSV3QYsJAGcXpBK0DLPw5--HYQnQOfiHb7mCB_MyqHT1QS3F89MxLvWCJL8wVYTI4Je8CqBujR7GRm-Y8w",
            "tokenHeader":"Bearer"
        }
        C:\Users\Asus>curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJndWVzdCIsImlhdCI6MTYyOTY2MTUwOCwiZXhwIjoxNjI5NzQ3OTA4fQ.H2VjKSV3QYsJAGcXpBK0DLPw5--HYQnQOfiHb7mCB_MyqHT1QS3F89MxLvWCJL8wVYTI4Je8CqBujR7GRm-Y8w" http://localhost:9001/protected/employee/2
        {
            "timestamp":"2021-08-22T19:47:15.844+0000",
            "status":403,
            "error":"Forbidden",
            "message":"Forbidden",
            "path":"/protected/employee/2"
        }
        C:\Users\Asus>
    

The guest user has only USER role, so he is not authorized to retrieve an employee by ID.

The request requires ADMIN or MANAGER role, hence the error code 403.

Conclusion

To sum it up, in this tutorial we covered in-depth how to secure a Spring Boot REST API with JWT.

First, we have seen how to configure H2 database and build a basic restful API. Along the way, we explained how to integrate JWT to secure our Spring Boot REST API.

Finally, we showcased how to test the securing rules we have applied using curl.