Professional Documents
Culture Documents
I chose Java 17 and Maven as the dependency manager, but you can
use whatever you want.
Click on the button "Generate" to download the project, open it in your
IDE, and install the Maven dependencies.
Configure the database connection
Let's configure the application to connect to the database and perform
database table creation (using Hibernate under the hood). Open the
application configuration file src/resources/application.properties
and add the code below:
server.port=8005
spring.datasource.url=jdbc:mysql://localhost:3307/taskdb?serverTimezone=UTC&all
spring.datasource.username=root
spring.datasource.password=secret
## Hibernate properties
spring.jpa.hibernate.ddl-auto=update
spring.jpa.open-in-view=false
<dependencies>
<!---- existing dependencies here....... ---->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
</dependency>
</dependencies>
package com.tericcabrel.authapi.entities;
import jakarta.persistence.*;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import java.util.Date;
@Table(name = "users")
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(nullable = false)
private Integer id;
@Column(nullable = false)
private String fullName;
@Column(unique = true, length = 100, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@CreationTimestamp
@Column(updatable = false, name = "created_at")
private Date createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private Date updatedAt;
// Getters and setters
}
package com.tericcabrel.authapi.repositories;
import com.tericcabrel.authapi.entities.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
import java.util.Optional;
@Repository
public interface UserRepository extends CrudRepository<User, Integer> {
Optional<User> findByEmail(String email);
}
@Table(name = "users")
@Entity
public class User implements UserDetails {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(nullable = false)
private Integer id;
@Column(nullable = false)
private String fullName;
@Column(unique = true, length = 100, nullable = false)
private String email;
@Column(nullable = false)
private String password;
@CreationTimestamp
@Column(updatable = false, name = "created_at")
private Date createdAt;
@UpdateTimestamp
@Column(name = "updated_at")
private Date updatedAt;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return List.of();
}
@Override
public String getUsername() {
return email;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
package com.tericcabrel.authapi.services;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
@Service
public class JwtService {
@Value("${security.jwt.secret-key}")
private String secretKey;
@Value("${security.jwt.expiration-time}")
private long jwtExpiration;
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
To generate the JWT token, we need a secret key and the token
expiration time; these values are read from the application configuration
properties file using the annotation @Value.
We must update the application.properties to define these values:
security.jwt.secret-key=3cfa76ef14937c1c0ea519f8fc057a80fcd04a7420f8e8bcd0a7
# 1h in millisecond
security.jwt.expiration-time=3600000
The secret key must be an HMAC hash string of 256 bits; otherwise,
the token generation will throw an error. I used this website to generate
one.
The token expiration time is expressed in milliseconds, so remember if
your token expires too soon.
package com.tericcabrel.authapi.configs;
import com.tericcabrel.authapi.repositories.UserRepository;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProv
import org.springframework.security.config.annotation.authentication.configu
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundExcepti
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@Configuration
public class ApplicationConfiguration {
private final UserRepository userRepository;
public ApplicationConfiguration(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Bean
UserDetailsService userDetailsService() {
return username -> userRepository.findByEmail(username)
.orElseThrow(() -> new UsernameNotFoundException("User not fou
}
@Bean
BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config)
return config.getAuthenticationManager();
}
@Bean
AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvi
authProvider.setUserDetailsService(userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
}
package com.tericcabrel.authapi.configs;
import com.tericcabrel.authapi.services.JwtService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.lang.NonNull;
import org.springframework.security.authentication.UsernamePasswordAuthentic
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDeta
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final HandlerExceptionResolver handlerExceptionResolver;
private final JwtService jwtService;
private final UserDetailsService userDetailsService;
public JwtAuthenticationFilter(
JwtService jwtService,
UserDetailsService userDetailsService,
HandlerExceptionResolver handlerExceptionResolver
){
this.jwtService = jwtService;
this.userDetailsService = userDetailsService;
this.handlerExceptionResolver = handlerExceptionResolver;
}
@Override
protected void doFilterInternal(
@NonNull HttpServletRequest request,
@NonNull HttpServletResponse response,
@NonNull FilterChain filterChain
) throws ServletException, IOException {
final String authHeader = request.getHeader("Authorization");
if (authHeader == null
|| !authHeader.startsWith( "Bearer ")) {
filterChain.doFilter(request, response);
return;
}
try {
final String jwt = authHeader.substring( ); 7
final String userEmail = jwtService.extractUsername(jwt);
authToken.setDetails( new
WebAuthenticationDetailsSource
SecurityContextHolder.getContext().setAuthentication(aut
}
}
filterChain.doFilter(request, response);
} catch (Exception exception) {
handlerExceptionResolver.resolveException(request, response, nul
}
}
}
A great thing to do here is to use caching to find the user by his email
address to improve the performance. I wrote a blog post to show you
how to implement caching in the SpringBoot application.
Data Caching in a Spring Boot application with Redis
In this post, we will see how to use Redis to cache data
retrieved from a MySQL database through a Spring Boot…
Teco Tutorials • Eric Cabrel TIOGO
package com.tericcabrel.authapi.configs;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.annotation.web.builders.HttpSecur
import org.springframework.security.config.annotation.web.configuration.Enab
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthe
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
private final AuthenticationProvider authenticationProvider;
private final JwtAuthenticationFilter jwtAuthenticationFilter;
public SecurityConfiguration(
JwtAuthenticationFilter jwtAuthenticationFilter,
AuthenticationProvider authenticationProvider
){
this.authenticationProvider = authenticationProvider;
this.jwtAuthenticationFilter = jwtAuthenticationFilter;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeHttpRequests()
.requestMatchers( "/auth/**")
.permitAll()
.anyRequest()
.authenticated()
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authenticationProvider(authenticationProvider)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordA
return http.build();
}
@Bean
CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(List.of("http://localhost:8005"));
configuration.setAllowedMethods(List.of("GET","POST"));
configuration.setAllowedHeaders(List.of("Authorization","Content-Type"))
source.registerCorsConfiguration( "/**",configuration);
return source;
}
}
package com.tericcabrel.authapi.dtos;
package com.tericcabrel.authapi.dtos;
We didn't apply any validation in the DTO for brevity, and if you are
interested in learning how I wrote this complete blog post below to help
you:
import com.tericcabrel.authapi.dtos.LoginUserDto;
import com.tericcabrel.authapi.dtos.RegisterUserDto;
import com.tericcabrel.authapi.entities.User;
import com.tericcabrel.authapi.repositories.UserRepository;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthentic
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class AuthenticationService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public AuthenticationService(
UserRepository userRepository,
AuthenticationManager authenticationManager,
PasswordEncoder passwordEncoder
){
this.authenticationManager = authenticationManager;
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
return userRepository.save(user);
}
return userRepository.findByEmail(input.getEmail())
.orElseThrow();
}
}
import com.tericcabrel.authapi.entities.User;
import com.tericcabrel.authapi.dtos.LoginUserDto;
import com.tericcabrel.authapi.dtos.RegisterUserDto;
import com.tericcabrel.authapi.responses.LoginResponse;
import com.tericcabrel.authapi.services.AuthenticationService;
import com.tericcabrel.authapi.services.JwtService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/auth")
@RestController
public class AuthenticationController {
private final JwtService jwtService;
private final AuthenticationService authenticationService;
@PostMapping("/signup")
public ResponseEntity<User> register(@RequestBody RegisterUserDto registerUserDto) {
User registeredUser = authenticationService.signup(registerUserDto)
return ResponseEntity.ok(registeredUser);
}
@PostMapping("/login")
public ResponseEntity<LoginResponse> authenticate(@RequestBody LoginUserDto loginU
User authenticatedUser = authenticationService.authenticate(loginUse
return ResponseEntity.ok(loginResponse);
}
}
Now, let's try to authenticate with the user we registered. Send a POST
request to /auth/login with the information in the request body.
Call the API route to authenticate a user and generate the JWT.
package com.tericcabrel.authapi.controllers;
import com.tericcabrel.authapi.entities.User;
import com.tericcabrel.authapi.services.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RequestMapping("/users")
@RestController
public class UserController {
private final UserService userService;
@GetMapping("/me")
public ResponseEntity<User> authenticatedUser() {
Authentication authentication = SecurityContextHolder.getContext().g
return ResponseEntity.ok(currentUser);
}
@GetMapping("/")
public ResponseEntity<List<User>> allUsers() {
List <User> users = userService.allUsers();
return ResponseEntity.ok(users);
}
}
In the above code, we retrieve the authenticated user from the security
context that has been set in the file JwtAuthenticationFilter.java
at line 68.
we can see the UserService is injected in the controller and provides
the function allUsers() that retrieves the list of users in the database
and returns it.
Before testing the implementation, let's create the UserService.java
in the package services and add the code below:
package com.tericcabrel.authapi.services;
import com.tericcabrel.authapi.entities.User;
import com.tericcabrel.authapi.repositories.UserRepository;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@Service
public class UserService {
private final UserRepository userRepository;
userRepository.findAll().forEach(users::add);
return users;
}
}
Call the protected API routes using the JWT generated through the login
package com.tericcabrel.authapi.exceptions;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.security.SignatureException;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ProblemDetail;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.AccountStatusException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class)
public ProblemDetail handleSecurityException(Exception exception) {
ProblemDetail errorDetail = null;
if (errorDetail == null) {
errorDetail = ProblemDetail.forStatusAndDetail(HttpStatusCode.va
errorDetail.setProperty( "description", "Unknown internal server error.");
}
return errorDetail;
}
}
💡 The JWT exceptions are caught by the global exception handler because we
used the Handler exception resolver in the file JwtAuthenticationFilter.java to
forward them. The other exceptions come from Spring security.
Wrap up
In this post, we saw how to implement the JSON Web Token
authentication in a Spring Boot application. Here are the main steps of
this process:
JWT authentication filter to extract and validate the token from
the request header.
Whitelist some API routes and protect those requiring a token.
Perform the authentication, generate the JWT, and set an
expiration time.
Use the JWT generated to access protected routes.
Catch authentication exceptions to customize the response
sent to the client.
With this implementation, you have the basis to protect your API, and
you can go a step further by implementing a Role-Based Access
Control (RBAC) that we will implement in the upcoming tutorial.
You can find the code source on the GitHub repository.
Follow me on Twitter or subscribe to my newsletter to avoid missing the
upcoming posts and the tips and tricks I occasionally share.
Join the Newsletter
Get updates about new articles, contents, coding tips and tricks.
Email Address Subscribe