- Authentication Service: Manages user authentication and issues JWT tokens.
- Employee Service: Manages employee information and is secured with JWT.
- API Gateway: Routes requests and applies security checks.
Prerequisites
Before we start, ensure you have the following:
- Java Development Kit (JDK) installed
- Apache Maven installed
- An IDE (such as IntelliJ IDEA, Eclipse, or VS Code) installed
Overview of the Microservices
We will create three Spring Boot microservices:
- Authentication Service: Manages user authentication and issues JWT tokens.
- Employee Service: Manages employee information.
- API Gateway: Routes requests and handles security with JWT.
Step 1: Create the Authentication Service
1.1 Create a Spring Boot Project
-
Open Spring Initializr:
- Go to Spring Initializr in your web browser.
-
Configure Project Metadata:
- Project: Maven Project
- Language: Java
- Spring Boot: Select the latest version of Spring Boot 3.2
- Group: com.example
- Artifact: auth-service
- Name: auth-service
- Description: Authentication Service
- Package Name: com.example.authservice
- Packaging: Jar
- Java Version: 17 (or your preferred version)
- Click
Next
.
-
Select Dependencies:
- On the
Dependencies
screen, select the dependencies you need:- Spring Web
- Spring Security
- Spring Data JPA
- H2 Database
- jjwt (add this manually later)
- Click
Next
.
- On the
-
Generate the Project:
- Click
Generate
to download the project zip file. - Extract the zip file to your desired location.
- Click
-
Open the Project in Your IDE:
- Open your IDE and import the project as a Maven project.
1.2 Add jjwt Dependency
Add the latest version of jjwt to your pom.xml
:
<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>
1.3 Create User Entity
Create a User
entity class in the com.example.authservice.model
package:
package com.example.authservice.model; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @Entity public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String username; private String password; // Getters and Setters }
1.4 Create User Repository
Create a UserRepository
interface in the com.example.authservice.repository
package:
package com.example.authservice.repository; import com.example.authservice.model.User; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface UserRepository extends JpaRepository<User, Long> { User findByUsername(String username); }
1.5 Create User Service
Create a UserService
class in the com.example.authservice.service
package:
package com.example.authservice.service; import com.example.authservice.model.User; import com.example.authservice.repository.UserRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; @Service public class UserService implements UserDetailsService { private final UserRepository userRepository; private final BCryptPasswordEncoder passwordEncoder; @Autowired public UserService(UserRepository userRepository, BCryptPasswordEncoder passwordEncoder) { this.userRepository = userRepository; this.passwordEncoder = passwordEncoder; } public User saveUser(User user) { user.setPassword(passwordEncoder.encode(user.getPassword())); return userRepository.save(user); } public User findByUsername(String username) { return userRepository.findByUsername(username); } @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = userRepository.findByUsername(username); if (user == null) { throw new UsernameNotFoundException("User not found"); } return org.springframework.security.core.userdetails.User.withUsername(user.getUsername()) .password(user.getPassword()) .roles(user.getRole()) .build(); } }
1.6 Create Security Configuration
Create a SecurityConfig
class in the com.example.authservice.config
package:
package com.example.authservice.config; import com.example.authservice.service.UserService; 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.configuration.AuthenticationConfiguration; 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.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; @Configuration @EnableWebSecurity public class SecurityConfig { private final UserService userService; @Autowired public SecurityConfig(UserService userService) { this.userService = userService; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth .requestMatchers("/auth/**").permitAll() .anyRequest().authenticated() ); return http.build(); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } }
1.7 Create JWT Utility Class
Create a JwtUtil
class in the com.example.authservice.util
package:
package com.example.authservice.util; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; import java.util.function.Function; @Component public class JwtUtil { @Value("${jwt.secret}") private String secret; public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } 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.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } public String generateToken(String username) { return Jwts.builder() .setSubject(username) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public boolean validateToken(String token, String username) { final String extractedUsername = extractUsername(token); return (extractedUsername.equals(username) && !isTokenExpired(token)); } private boolean isTokenExpired(String token) { return extractClaim(token, Claims::getExpiration).before(new Date()); } }
1.8 Create Authentication Controller
Create an AuthController
class in the com.example.authservice.controller
package:
package com.example.authservice.controller; import com.example.authservice.model.User; import com.example.authservice.service.UserService; import com.example.authservice.util.JwtUtil; 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 { private final UserService userService; private final AuthenticationManager authenticationManager; private final JwtUtil jwtUtil; @Autowired public AuthController(UserService userService, AuthenticationManager authenticationManager, JwtUtil jwtUtil) { this.userService = userService; this.authenticationManager = authenticationManager; this.jwtUtil = jwtUtil; } @PostMapping("/register") public User registerUser(@RequestBody User user) { return userService.saveUser(user); } @PostMapping("/login") public String loginUser(@RequestBody User user) { Authentication authentication = authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword()) ); SecurityContextHolder.getContext().setAuthentication(authentication); UserDetails userDetails = userService.findByUsername(user.getUsername()); return jwtUtil.generateToken(userDetails.getUsername()); } }
1.9 Configure JWT Secret
Add the following to your application.properties
file:
jwt.secret=your_secret_key
Replace your_secret_key
with a secure key of your choice.
Step 2: Create the Employee Service
2.1 Create a Spring Boot Project
-
Open Spring Initializr:
- Go to Spring Initializr in your web browser.
-
Configure Project Metadata:
- Project: Maven Project
- Language: Java
- Spring Boot: Select the latest version of Spring Boot 3.2
- Group: com.example
- Artifact: employee-service
- Name: employee-service
- Description: Employee Service
- Package Name: com.example.employeeservice
- Packaging: Jar
- Java Version: 17 (or your preferred version)
- Click
Next
.
-
Select Dependencies:
- On the
Dependencies
screen, select the dependencies you need:- Spring Web
- Spring Data JPA
- Spring Security
- H2 Database
- Click
Next
.
- On the
-
Generate the Project:
- Click
Generate
to download the project zip file. - Extract the zip file to your desired location.
- Click
-
Open the Project in Your IDE:
- Open your IDE and import the project as a Maven project.
2.2 Create Employee Entity
Create an Employee
entity class in the com.example.employeeservice.model
package:
package com.example.employeeservice.model; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @Entity public class Employee { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String name; private String department; // Getters and Setters }
2.3 Create Employee Repository
Create an EmployeeRepository
interface in the com.example.employeeservice.repository
package:
package com.example.employeeservice.repository; import com.example.employeeservice.model.Employee; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository public interface EmployeeRepository extends JpaRepository<Employee, Long> { }
2.4 Create Employee Controller
Create an EmployeeController
class in the com.example.employeeservice.controller
package:
package com.example.employeeservice.controller; import com.example.employeeservice.model.Employee; import com.example.employeeservice.repository.EmployeeRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; import java.util.List; @RestController @RequestMapping("/employees") public class EmployeeController { private final EmployeeRepository employeeRepository; @Autowired public EmployeeController(EmployeeRepository employeeRepository) { this.employeeRepository = employeeRepository; } @GetMapping public List<Employee> getAllEmployees() { return employeeRepository.findAll(); } @PostMapping public Employee createEmployee(@RequestBody Employee employee) { return employeeRepository.save(employee); } }
2.5 Create Security Configuration
Create a SecurityConfig
class in the com.example.employeeservice.config
package:
package com.example.employeeservice.config; import com.example.employeeservice.filter.JwtRequestFilter; 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.configuration.AuthenticationConfiguration; 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.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { private final JwtRequestFilter jwtRequestFilter; @Autowired public SecurityConfig(JwtRequestFilter jwtRequestFilter) { this.jwtRequestFilter = jwtRequestFilter; } @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { http .csrf(AbstractHttpConfigurer::disable) .authorizeHttpRequests(auth -> auth .anyRequest().authenticated() ) .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } }
2.6 Create JWT Request Filter
Create a JwtRequestFilter
class in the com.example.employeeservice.filter
package:
package com.example.employeeservice.filter; import com.example.employeeservice.util.JwtUtil; import io.jsonwebtoken.ExpiredJwtException; 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.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; @Component public class JwtRequestFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Autowired private UserDetailsService userDetailsService; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException { final String requestTokenHeader = request.getHeader("Authorization"); String username = null; String jwtToken = null; if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) { jwtToken = requestTokenHeader.substring(7); try { username = jwtUtil.extractUsername(jwtToken); } catch (IllegalArgumentException e) { System.out.println("Unable to get JWT Token"); } catch (ExpiredJwtException e) { System.out.println("JWT Token has expired"); } } else { logger.warn("JWT Token does not begin with Bearer String"); } if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(jwtToken, userDetails.getUsername())) { UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); usernamePasswordAuthenticationToken .setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken); } } chain.doFilter(request, response); } }
2.7 Create JWT Utility Class
Create a JwtUtil
class in the com.example.employeeservice.util
package:
package com.example.employeeservice.util; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; import java.util.function.Function; @Component public class JwtUtil { @Value("${jwt.secret}") private String secret; public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } 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.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } public String generateToken(String username) { return Jwts.builder() .setSubject(username) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } public boolean validateToken(String token, String username) { final String extractedUsername = extractUsername(token); return (extractedUsername.equals(username) && !isTokenExpired(token)); } private boolean isTokenExpired(String token) { return extractClaim(token, Claims::getExpiration).before(new Date()); } }
2.8 Configure JWT Secret
Add the following to your application.properties
file:
jwt.secret=your_secret_key
Replace your_secret_key
with the same key used in the auth-service
.
Step 3: Create the API Gateway
3.1 Create a Spring Boot Project
-
Open Spring Initializr:
- Go to Spring Initializr in your web browser.
-
Configure Project Metadata:
- Project: Maven Project
- Language: Java
- Spring Boot: Select the latest version of Spring Boot 3.2
- Group: com.example
- Artifact: api-gateway
- Name: api-gateway
- Description: API Gateway
- Package Name: com.example.apigateway
- Packaging: Jar
- Java Version
: 17 (or your preferred version)
- Click
Next
.
-
Select Dependencies:
- On the
Dependencies
screen, select the dependencies you need:- Spring Cloud Gateway
- Spring Boot DevTools
- Spring Security
- jjwt (add this manually later)
- Click
Next
.
- On the
-
Generate the Project:
- Click
Generate
to download the project zip file. - Extract the zip file to your desired location.
- Click
-
Open the Project in Your IDE:
- Open your IDE and import the project as a Maven project.
3.2 Add jjwt Dependency
Add the latest version of jjwt to your pom.xml
:
<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>
3.3 Update application.yml
Create an application.yml
file in the src/main/resources
directory and configure it as follows:
server: port: 8080 spring: application: name: api-gateway cloud: gateway: routes: - id: auth_service uri: http://localhost:8081 predicates: - Path=/auth/** - id: employee_service uri: http://localhost:8082 predicates: - Path=/employees/** security: jwt: secret: your_secret_key
Replace your_secret_key
with the same key used in the auth-service
and employee-service
.
3.4 Create JWT Utility Class
Create a JwtUtil
class in the com.example.apigateway.util
package:
package com.example.apigateway.util; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.function.Function; @Component public class JwtUtil { @Value("${spring.security.jwt.secret}") private String secret; public String extractUsername(String token) { return extractClaim(token, Claims::getSubject); } 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.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); } public boolean validateToken(String token, String username) { final String extractedUsername = extractUsername(token); return (extractedUsername.equals(username) && !isTokenExpired(token)); } private boolean isTokenExpired(String token) { return extractClaim(token, Claims::getExpiration).before(new Date()); } }
3.5 Create JWT Filter
Create a JwtAuthenticationFilter
class in the com.example.apigateway.filter
package:
package com.example.apigateway.filter; import com.example.apigateway.util.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.server.reactive.ServerHttpRequest; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.stereotype.Component; import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; import reactor.core.publisher.Mono; @Component public class JwtAuthenticationFilter implements WebFilter { @Autowired private JwtUtil jwtUtil; @Autowired private UserDetailsService userDetailsService; @Override public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) { ServerHttpRequest request = exchange.getRequest(); String authHeader = request.getHeaders().getFirst("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String jwt = authHeader.substring(7); String username = jwtUtil.extractUsername(jwt); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = userDetailsService.loadUserByUsername(username); if (jwtUtil.validateToken(jwt, userDetails.getUsername())) { UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authentication); } } } return chain.filter(exchange); } }
3.6 Create Security Configuration
Create a SecurityConfig
class in the com.example.apigateway.config
package:
package com.example.apigateway.config; import com.example.apigateway.filter.JwtAuthenticationFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebFluxSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.web.server.SecurityWebFilterChain; @Configuration @EnableWebFluxSecurity public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthenticationFilter; @Autowired public SecurityConfig(JwtAuthenticationFilter jwtAuthenticationFilter) { this.jwtAuthenticationFilter = jwtAuthenticationFilter; } @Bean public SecurityWebFilterChain securityWebFilterChain(HttpSecurity http) throws Exception { return http.csrf(AbstractHttpConfigurer::disable) .authorizeExchange(exchange -> exchange .pathMatchers("/auth/**").permitAll() .anyExchange().authenticated() ) .addFilterBefore(jwtAuthenticationFilter, SecurityWebFilterChain.class) .build(); } }
Step 4: Running the Microservices
4.1 Running Authentication Service
- Open the
AuthServiceApplication
class in thesrc/main/java/com/example/authservice
directory. - Click the green
Run
button in your IDE or use the terminal to run the application:./mvnw spring-boot:run
4.2 Running Employee Service
- Open the
EmployeeServiceApplication
class in thesrc/main/java/com/example/employeeservice
directory. - Click the green
Run
button in your IDE or use the terminal to run the application:./mvnw spring-boot:run
4.3 Running API Gateway
- Open the
ApiGatewayApplication
class in thesrc/main/java/com/example/apigateway
directory. - Click the green
Run
button in your IDE or use the terminal to run the application:./mvnw spring-boot:run
Step 5: Testing the Application
You can use tools like Postman to test the authentication and secure access to the microservices.
5.1 Register a User
-
Create a new
POST
request tohttp://localhost:8081/auth/register
. -
Set the body to JSON and add a user object:
{ "username": "testuser", "password": "testpassword" }
-
Send the request.
5.2 Login to Get JWT Token
-
Create a new
POST
request tohttp://localhost:8081/auth/login
. -
Set the body to JSON and add the user object:
{ "username": "testuser", "password": "testpassword" }
-
Send the request and copy the token from the response.
5.3 Access Secured Employee Service
- Create a new
GET
request tohttp://localhost:8080/employees
. - Add the
Authorization
header with the valueBearer <JWT_TOKEN>
. - Send the request and verify that you receive the list of employees.
Conclusion
In this tutorial, we created a microservices architecture with Spring Boot and secured it using JWT. We built an authentication service to issue tokens, secured the employee service with JWT, and created an API Gateway to route and secure requests. This setup provides a solid foundation for developing secure microservices architectures.
in employee service as we not defined the UserDetailsService.loadUserByUsername(), it is asking for the definition. How to resolve this?
ReplyDeleteFixed the issue. Check out the UserService class.
Delete