What is JWT Authentication?
JWT (JSON Web Token) is a compact, URL-safe means of representing claims to be transferred between two parties. It is often used for authentication and authorization in modern web applications. JWTs are stateless and can be verified without querying a database, making them efficient for distributed systems.
A JWT consists of three parts:
- Header: Specifies the algorithm used to sign the token and the type of token.
- Payload: Contains the claims, which are statements about an entity (typically, the user) and additional metadata.
- Signature: Used to verify the authenticity of the token and ensure that the token has not been tampered with.
Prerequisites
Before we start, ensure you have the following:
- Java Development Kit (JDK) installed
- Apache Maven installed
- An IDE (Integrated Development Environment) like IntelliJ IDEA or Eclipse
Step 1: Setting Up the Project
Create a Spring Boot Project
- Open your IDE and create a new Spring Boot project using Spring Initializr.
- Add the following dependencies:
- Spring Web
- Spring Security
- Spring Data JPA
- H2 Database (for simplicity, but you can use any database of your choice)
- jjwt (for generating and validating JWTs)
Project Structure
Your project structure should look like this:
Step 2: Adding Dependencies
Add the necessary dependencies for Spring Security, Spring Data JPA, H2 database, and jjwt in the pom.xml
file.
pom.xml
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- JJWT (Java JWT) -->
<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>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.5</version>
<scope>runtime</scope>
</dependency>
<!-- Spring Boot Starter Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
Step 3: Configuring the Application Properties
Configure the application properties for the H2 database and other settings.
application.properties
# H2 Database configuration
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
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
# JPA settings
spring.jpa.hibernate.ddl-auto=update
# JWT settings
jwt.secret=yourSecretKey
jwt.expiration=3600000 # 1 hour in milliseconds
Step 4: Creating the User and Role Entities
Create User
and Role
entities with a many-to-many relationship in the com.example.security.model
package.
Role.java
package com.example.security.model;
import jakarta.persistence.*;
import java.util.Set;
@Entity
public class Role {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToMany(mappedBy = "roles")
private Set<User> users;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Set<User> getUsers() {
return users;
}
public void setUsers(Set<User> users) {
this.users = users;
}
}
User.java
package com.example.security.model;
import jakarta.persistence.*;
import java.util.Set;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_role",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "role_id")
)
private Set<Role> roles;
// Getters and Setters
public Long getId() {
return id;
}
public void setId(Long 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 Set<Role> getRoles() {
return roles;
}
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
}
Step 5: Creating the Repositories
Create UserRepository
and RoleRepository
interfaces in the com.example.security.repository
package.
UserRepository.java
package com.example.security.repository;
import com.example.security.model.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
User findByUsername(String username);
}
RoleRepository.java
package com.example.security.repository;
import com.example.security.model.Role;
import org.springframework.data.jpa.repository.JpaRepository;
public interface RoleRepository extends JpaRepository<Role, Long> {
Role findByName(String name);
}
Step 6: Creating the JWT Service
Create a JwtService
class to generate and validate JWT tokens in the com.example.security.service package.
JwtService.java
package com.example.security.service;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class JwtService {
@Value("${jwt.secret}")
private String secret;
@Value("${jwt.expiration}")
private long expiration;
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + expiration))
.signWith(SignatureAlgorithm.HS512, secret)
.compact();
}
public Claims getClaimsFromToken(String token) {
return Jwts.parser()
.setSigningKey(secret)
.parseClaimsJws(token)
.getBody();
}
public boolean isTokenValid(String token) {
try {
Claims claims = getClaimsFromToken(token);
return !claims.getExpiration().before(new Date());
} catch (Exception e) {
return false;
}
}
public String getUsernameFromToken(String token) {
return getClaimsFromToken(token).getSubject();
}
}
Step 7: Creating the User Service
Create a UserService
class and a UserDetailsServiceImpl
class to manage users and load user-specific data.
UserService.java
package com.example.security.service;
import com.example.security.model.Role;
import com.example.security.model.User;
import com.example.security.repository.RoleRepository;
import com.example.security.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.HashSet;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private RoleRepository roleRepository;
@Autowired
private PasswordEncoder passwordEncoder;
@PostConstruct
public void init() {
// Create roles
Role adminRole = new Role();
adminRole.setName("ADMIN");
roleRepository.save(adminRole);
Role userRole = new Role();
userRole.setName("USER");
roleRepository.save(userRole);
// Create users
User admin = new User();
admin.setUsername("admin");
admin.setPassword(passwordEncoder.encode("admin"));
admin.setRoles(new HashSet<>(Set.of(adminRole)));
userRepository.save(admin);
User user = new User();
user.setUsername("user");
user.setPassword(passwordEncoder.encode("password"));
user.setRoles(new HashSet<>(Set.of(userRole)));
userRepository.save(user);
}
}
UserDetailsServiceImpl.java
package com.example.security.service;
import com.example.security.model.User;
import com.example.security.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.security.core.userdetails.User;
import org.springframework.stereotype.Service;
@Service
public class UserDetailsServiceImpl implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
com.example.security.model.User user = userRepository.findByUsername(username);
if (user == null) {
throw new UsernameNotFoundException("User not found");
}
return User.builder()
.username(user.getUsername())
.password(user.getPassword())
.roles(user.getRoles().stream().map(role -> role.getName()).toArray(String[]::new))
.build();
}
}
Step 8: Creating the JWT Authentication Filter
Create a JwtAuthenticationFilter
class to filter and authenticate requests based on JWT tokens in the com.example.security.filter
package.
JwtAuthenticationFilter.java
package com.example.security.filter;
import com.example.security.service.JwtService;
import com.example.security.service.UserDetailsServiceImpl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@Autowired
private JwtService jwtService;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtService.getUsernameFromToken(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtService.isTokenValid(jwt)) {
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
Step 9: Configuring Spring Security
Create a SecurityConfig
class in the com.example.security.config
package to configure Spring Security for JWT authentication.
SecurityConfig.java
package com.example.security.config;
import com.example.security.filter.JwtAuthenticationFilter;
import com.example.security.service.UserDetailsServiceImpl;
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.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
private UserDetailsServiceImpl userDetailsService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(authorizeRequests ->
authorizeRequests
.antMatchers("/auth/**").permitAll()
.anyRequest().authenticated()
)
.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
);
http.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http, PasswordEncoder passwordEncoder, UserDetailsServiceImpl userDetailsService) throws Exception {
return http.getSharedObject(AuthenticationManagerBuilder.class)
.userDetailsService(userDetailsService)
.passwordEncoder(passwordEncoder)
.and()
.build();
}
}
Step 10: Creating the Authentication Controller
Create an AuthController
class in the com.example.security.controller
package to handle authentication requests.
AuthController.java
package com.example.security.controller;
import com.example.security.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.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/auth")
public class AuthController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtService jwtService;
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password));
SecurityContextHolder.getContext().setAuthentication(authentication);
UserDetails userDetails = (UserDetails) authentication.getPrincipal();
return jwtService.generateToken(userDetails.getUsername());
}
}
Step 11: Creating the Main Application Class
Create the main application class to run your Spring Boot application.
SecurityApplication.java
package com.example.security;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class SecurityApplication {
public static void main(String[] args) {
SpringApplication.run(SecurityApplication.class, args);
}
}
Step 12: Running the Application
To run the application, execute the SecurityApplication
class. This will start the Spring Boot application with Spring Security configured for JWT authentication.
Accessing the Application
- Open your browser or a tool like Postman and navigate to
http://localhost:8080/auth/login
. - Send a POST request with
username
andpassword
parameters to authenticate. The response will contain the JWT token. - Use the JWT token to access secured endpoints by including it in the
Authorization
header with the Bearer
prefix.
Using curl to Access the API
# Get JWT token
TOKEN=$(curl -X POST -d "username=user&password=password" http://localhost:8080/auth/login)
# Access secured endpoint
curl -H "Authorization: Bearer $TOKEN" http://localhost:8080/api/hello
Conclusion
In this tutorial, we have walked through setting up a basic Spring Boot application and integrating it with Spring Security for JWT authentication. We configured Spring Security to secure our application with JWT tokens, created a simple RESTful API endpoint, and provided mechanisms for generating and validating JWTs. By following this tutorial, you should now have a good understanding of how to integrate Spring Security with Spring Boot and secure your REST APIs using JWT authentication.
Comments
Post a Comment