In this article, we will see how to implement JWT implementation using Spring Boot 3 and Java 17.
Go to Spring Initialiser and create a project with following dependencies. Please note, we are using MySQL database in this project.
https://start.spring.io/#!type=maven-project&language=java&platformVersion=3.1.1&packaging=jar&jvmVersion=17&groupId=com.example.jwt&artifactId=spring-security-jwt&name=spring-security-jwt&description=Implementation%20of%20JWT%20using%20Spring%20Boot%203&packageName=com.example.jwt&dependencies=web,data-jpa,mysql,security,lombokAdd the following additional dependencies separately as they are not available in Spring Initialiser for the JWT implementation.
<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>
- Define entity class as follows to store the User Details under entities package.
package com.example.jwt.entities;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = "USER_DATA")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class UserData {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String name;
@Column(unique = true)
private String email;
private String password;
private String roles;
private boolean isEnabled;
}
- Create a repository interface named UserRepository as follows.
package com.example.jwt.repository;
import com.example.jwt.entities.UserData;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<UserData, Long> {
Optional<UserData> findByEmail(String email);
}
- Create a package called dto and create two classes named AuthRequestDto and UserDto respectively.
package com.example.jwt.dto;
import lombok.*;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class AuthRequestDto {
private String email;
private String password;
}
package com.example.jwt.dto;
import lombok.*;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Builder
public class UserDto {
private Long id;
private String name;
private String email;
private String password;
}
- Create a UserService interface and its implementation to create the user in the database.
package com.example.jwt.service;
import com.example.jwt.dto.UserDto;
public interface UserService {
public UserDto createUser(UserDto userDto);
}
package com.example.jwt.service.impl;
import com.example.jwt.dto.UserDto;
import com.example.jwt.entities.UserData;
import com.example.jwt.repository.UserRepository;
import com.example.jwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private BCryptPasswordEncoder passwordEncoder;
@Override
public UserDto createUser(UserDto userDto) {
UserData user = convertToEntity(userDto);
return convertToDto(userRepository.save(user));
}
private UserData convertToEntity(UserDto userDto) {
return UserData.builder()
.name(userDto.getName())
.password(passwordEncoder.encode(userDto.getPassword()))
.email(userDto.getEmail())
.roles("ROLE_USER")
.isEnabled(true)
.build();
}
private UserDto convertToDto(UserData user) {
return UserDto.builder()
.id(user.getId())
.name(user.getName())
.email(user.getEmail())
.build();
}
}
- Create a controller class for exposing a REST endpoint to create users. Also, add an additional endpoint which will be secured and will help us to test the JWT implementation post the completion of the implementation.
package com.example.jwt.controller;
import com.example.jwt.dto.UserDto;
import com.example.jwt.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@PostMapping("/create")
public ResponseEntity<UserDto> createUser(@RequestBody UserDto userDto) {
UserDto createdUserDto = userService.createUser(userDto);
return new ResponseEntity<>(createdUserDto, HttpStatus.CREATED);
}
@GetMapping("/secured")
public String test() {
return "secured";
}
}
- Create a Service class for JWT implementation as follows under the service package.
package com.example.jwt.service;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Service
public class JwtService {
private static final String SECRET = "5367566B59703373367639792F423F4528482B4D6251655468576D5A71347437";
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignKey())
.build()
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
public String generateToken(String userName) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userName);
}
private String createToken(Map<String, Object> claims, String userName) {
return Jwts.builder()
.setClaims(claims)
.setSubject(userName)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000*60*30))
.signWith(getSignKey(), SignatureAlgorithm.HS256).compact();
}
private Key getSignKey() {
byte[] keyBytes= Decoders.BASE64.decode(SECRET);
return Keys.hmacShaKeyFor(keyBytes);
}
}
- Create a filter package and create class called JwtAuthFilter class.
package com.example.jwt.filter;
import com.example.jwt.configuration.UserInfoUserDetailsService;
import com.example.jwt.service.JwtService;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
public class JwtAuthFilter extends OncePerRequestFilter {
@Autowired
private JwtService jwtService;
@Autowired
private UserInfoUserDetailsService userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
String token = null;
String username = null;
if (authHeader != null && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7);
username = jwtService.extractUsername(token);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtService.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}
- Create a new package configuration and create the class UserInfoUserDetailsService.
package com.example.jwt.configuration;
import com.example.jwt.entities.UserData;
import com.example.jwt.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
public class UserInfoUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserData> userInfo = userRepository.findByEmail(username);
return userInfo
.map(UserInfoUserDetails::new)
.orElseThrow(() -> new UsernameNotFoundException("User not found :" + username));
}
}
- Create UserInfoUserDetails class under the configuration package.
package com.example.jwt.configuration;
import com.example.jwt.entities.UserData;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class UserInfoUserDetails implements UserDetails {
private String email;
private String password;
private List<GrantedAuthority> authorityList;
private boolean isEnabled;
public UserInfoUserDetails(UserData user) {
this.email = user.getEmail();
this.password = user.getPassword();
this.authorityList = Arrays.stream(user.getRoles().split(","))
.map(SimpleGrantedAuthority::new).collect(Collectors.toList());
this.isEnabled = user.isEnabled();
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorityList;
}
@Override
public String getPassword() {
return password;
}
@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 isEnabled;
}
}
- Create the SecurityConfiguration class under the configuration package for defining the security configuration of the application. Please note that WebSecurityConfigurerAdapter class is removed from latest released version of Spring Security. So, not required to extend any class now. We need to define the bean definition of SecurityFilterChain and provide the security configuration inside it.
package com.example.jwt.configuration;
import com.example.jwt.filter.JwtAuthFilter;
import org.springframework.beans.factory.annotation.Autowired;
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.DaoAuthenticationProvider;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfiguration {
@Autowired
private JwtAuthFilter jwtAuthFilter;
@Bean
public UserDetailsService userDetailsService() {
return new UserInfoUserDetailsService();
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity) throws Exception {
return httpSecurity
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> {
auth.requestMatchers("/users/create", "/authenticate/**").permitAll();
auth.requestMatchers("/users/**").authenticated();
})
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService());
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
return configuration.getAuthenticationManager();
}
@Bean
public BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
- Create the AuthenticationController class and define a Rest endpoint to pass the credentials. In case of successful authentication against the database, a JWT token will be issued to the user for accessing the secured endpoints.
package com.example.jwt.controller;
import com.example.jwt.dto.AuthRequestDto;
import com.example.jwt.service.JwtService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
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;
@RestController
@RequestMapping("/authenticate")
public class AuthenticationController {
@Autowired
private JwtService jwtService;
@Autowired
private AuthenticationManager authenticationManager;
@PostMapping("/")
public String authenticateUserAndGetToken(@RequestBody AuthRequestDto authRequestDto) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequestDto.getEmail(), authRequestDto.getPassword()));
if(authentication.isAuthenticated())
return jwtService.generateToken(authRequestDto.getEmail());
else
throw new UsernameNotFoundException("User not found!");
}
}
- Define the application configurations in the application.yml file.
server:
port: 9002
servlet:
context-path: /jwt-implementation/
spring:
datasource:
url: jdbc:mysql://localhost:3306/<Database name goes here>
username: <User Name goes here>
password: <Password goes here>
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
properties:
hibernate:
dialect: org.hibernate.dialect.MySQLDialect
hibernate:
ddl-auto: update
logging:
level:
org:
hibernate:
SQL: DEBUG
type:
descriptor:
sql:
BasicBinder: TRACE
Testing the implementation
- Create the user.
- Hit the /authenticate endpoint with the credentials to get the JWT token.
- Hit the /secured endpoint with the JWT token received from the previous step. You should be able to access it successfully if the implementation has been done properly as per the above steps mentioned.
Source Code
Top comments (0)