Featured blog image
Spring Boot 6 min read

Build a Secure REST API in Spring Boot with JWT Authentication

Author

Krishna pariyar

Backend development

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

  1. Start the application using mvn spring-boot:run.

  2. Test the login endpoint (POST /auth/login) with valid credentials, and get the JWT token in response.

  3. Test Authorized user like "Admin" can access the "Dashboard" other Users are rejected

  4. 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)

Share Your Thoughts

Your thoughts inspire us.

250/250