Build a Secure REST API in Spring Boot with JWT Authentication
In today's API is widely used to build scalable, maintainable and powerful software system. However, securing them is a major concern. but JWT (JSON Web Token) provides a robust authencation mechanism, ensuring safe and stateless communication between client and servers. In this blog we will walk through Step-by-Step Guide to Implementing JWT in Spring Boot and Setting up role based authorization in Spring Boot.
What is JWT?
JWT stands for JSON Web Token. It's an open standard used to securely transmit information between parties as a JSON object. This information is digitally signed using a secret (with the HMAC algorithm) or a public/private key pair (using RSA or ECDSA).
A JWT typically has three parts:
-
Header – Contains the signing algorithm and token type.
-
Payload – Holds the claims (user data).
-
Signature – Ensures the token hasn't been tampered with.
Why Use JWT in Spring Boot?
Spring Boot is popular for building REST APIs, and JWT fits perfectly for securing those APIs because:
-
It is stateless (no session needed).
-
It is scalable for distributed systems.
-
It works well with mobile and SPA clients.
💡 Tips:
You may follow the our previous How to Build a Spring Boot REST API with Java
You may Follow this Project Structure
How to Implement JWT in Spring Boot
Here are Step-by-Step guide to implementing JWT in Spring Boot Project.
Step 1: Add Dependencies
Modify in your pom.xml (for Maven):
<!-- Spring Boot Web Starter (for building REST APIs) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>
<!-- Spring Security (required for JWT-based authentication) --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency>
<!-- JWT API Dependency --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency>
<!-- JWT Implementation Dependency --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
<!-- JWT Jackson Integration --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
Step 2: Create a JWT Utility Service to Generate & Validate Tokens
package com.example.demo.security;
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.Component;
import java.security.Key; import java.util.Date; import java.util.HashMap; import java.util.Map; import java.util.function.Function;
@Component public class JwtService {
public static final String SECRET = "Your Secrete Key";
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()) .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); }
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)); } }
Step 3: Create UserDetails Implementation (UserInfoDetails
)
This class implements UserDetails
from Spring Security.
import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails;
import com.example.demo.model.UserInfo;
import java.util.Collection; import java.util.List; import java.util.stream.Collectors;
public class UserInfoDetails implements UserDetails {
private String username; private String password; private List<GrantedAuthority> authorities;
public UserInfoDetails(UserInfo userInfo) { this.username = userInfo.getUsername(); this.password = userInfo.getPassword(); this.authorities = List.of(userInfo.getRoles().split(",")) .stream() .map(role -> new SimpleGrantedAuthority("ROLE_" + role.trim())) .collect(Collectors.toList()); }
@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 true; } }
Step 4: Create UserInfoService
The UserInfoService
class implements UserDetailsService
to load user information by username.
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.userdetails.User; 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.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service;
import com.example.demo.model.UserInfo; import com.example.demo.repository.UserInfoRepository;
import java.util.Optional;
@Service public class UserInfoService implements UserDetailsService {
@Autowired private UserInfoRepository repository;
@Autowired private PasswordEncoder encoder;
@Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { return repository.findByUsername(username) // Query the database by 'username' .map(UserInfoDetails::new) // Map the result to UserInfoDetails .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username)); }
public String addUser(UserInfo userInfo) { // Encode password before saving the user userInfo.setPassword(encoder.encode(userInfo.getPassword())); repository.save(userInfo); return "User Added Successfully"; } }
Step 5: Configure JWT Filter & Security
package com.example.demo.security;
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.core.userdetails.UserDetailsService; 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 UserDetailsService 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 (token == null || username == null) { filterChain.doFilter(request, response); return; }
if (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); } else { response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); response.getWriter().write("Invalid token"); return; } }
filterChain.doFilter(request, response); } }
Step 6: Conguring Security
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
private final JwtAuthFilter authFilter;
private final UserDetailsService userDetailsService;
private final UnauthorizedHandler unauthorizedHandler;
@Autowired
public SecurityConfig(JwtAuthFilter authFilter, @Qualifier("userInfoService") UserDetailsService userDetailsService, UnauthorizedHandler unauthorizedHandler) {
this.authFilter = authFilter;
this.userDetailsService = userDetailsService;
this.unauthorizedHandler = unauthorizedHandler;
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
.cors(AbstractHttpConfigurer::disable)
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.formLogin(AbstractHttpConfigurer::disable)
.exceptionHandling(h -> h.authenticationEntryPoint(unauthorizedHandler))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/auth/welcome", "/auth/addNewUser", "/auth/generateToken", "/auth/forgotPassword").permitAll()
.requestMatchers("/menu/add/**").hasAuthority("ROLE_admin")
.requestMatchers("/menu/delete/**").hasAuthority("ROLE_admin")
.requestMatchers("/menu/update/**").hasAuthority("ROLE_admin")
.anyRequest().authenticated() // Protect all other endpoints
)
.sessionManagement(sess -> sess
.sessionCreationPolicy(SessionCreationPolicy.STATELESS) // No sessions
)
.authenticationProvider(authenticationProvider()) // Custom authentication provider
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class); // Add JWT filter
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder());
return authenticationProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}
Step 7: Create Authentication Controller
Create an endpoint to authenticate users and return a JWT token.
package com.example.demo.controller; import com.example.demo.security.JwtService; import com.example.demo.security.
UserInfoService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/auth") public class AuthController {
@Autowired
private UserInfoService service;
@Autowired
private JwtService jwtService;
@Autowired
private AuthenticationManager authenticationManager; @PostMapping("/login") public String authenticate(@RequestParam String username, @RequestParam String password) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
);
if (authentication.isAuthenticated()) {
return jwtService.generateToken(authRequest.getUsername());
} else {
throw new UsernameNotFoundException("Invalid user request!");
} }
@GetMapping("/admin/dashboard")
@PreAuthorize("hasAuthority('admin')")
public String Dashboard() {
return "Welcome to Admin dashboard";
} }
Step 8: Test the Application
-
Start the application using
mvn spring-boot:run
. -
Test the login endpoint (
POST /auth/login
) with valid credentials, and get the JWT token in response. -
Test Authorized user like "Admin" can access the "Dashboard" other Users are rejected
-
Use the JWT token in the
Authorization
header for other secured routes.
Conclusion :
In this blog, we've successfully implemented JWT authentication in a Spring Boot application. By setting up user details, creating JWT utilities, and configuring Spring Security, we've built a secure, token-based authentication system and role based Authorization. This approach enhances security and ensures smooth and stateless authentication for users in modern web applications.
Comments (0)