Spring security and JWT

Introduction

In the world of web development, securing your applications is a top priority. As cyber threats continue to grow in sophistication, the need for robust security mechanisms has never been more critical. For Java developers, leveraging Spring Security combined with JSON Web Tokens (JWT) is an effective way to ensure that your web applications remain secure.

In this blog, we’ll explore how to implement security in a Java web application using Spring Security and JWT. We will walk you through essential components like authentication, authorization, and token management while providing practical examples and guidance along the way.

What is Spring Security?

Spring Security is a robust and flexible framework for managing authentication and access control. It allows for extensive customization and is the de facto standard for securing Spring-based applications. Moreover, it provides comprehensive security services for Java EE-based enterprise software applications.

What is JWT?

JSON Web Token (JWT) is an open standard (RFC 7519) that defines a lightweight and self-contained way to securely transmit data between parties in the form of a JSON object. It’s widely used in stateless authentication mechanisms, where the server doesn’t need to keep session information between requests..

 Setting Up the Spring Boot Project

Before you begin, you’ll need to set up a Spring Boot project. Whether you use Spring Initializr or your preferred IDE, the following dependencies are crucial:
  • Spring Web: For building web applications.
  • Spring Security: For implementing security features.
  • Spring Boot DevTools: For development-time automation.
  • Spring Data JPA: For data persistence.
  • Java JWT: A Java library for creating and verifying JSON Web Tokens.
Here’s a pom.xml snippet with the necessary dependencies:

 Implementing User AuthenticationStep

1: Create a User Entity Start by creating a User entity to store user credentials. We’ll use JPA to map this entity to a database table.

@Entity
public class User {
  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;
  @Column(nullable = false, unique = true)
  private String username;
  @Column(nullable = false)
  private String password;
  @Column
  private String roles;
}

Step 2: Create a UserRepository

Create a repository interface to perform database operations on the User entity.

Step 3: Implement UserDetailsService

Spring Security uses the UserDetailsService interface to fetch user details during authentication. Implement this interface to load users from the database.

@Service
public class CustomUserDetailsService implements UserDetailsService {
  @Autowired
  private UserRepository userRepository;
  @Override
  public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
    User user = userRepository.findByUsername(username)
      .orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
    return new org.springframework.security.core.userdetails.User(
      user.getUsername(),
      user.getPassword(),
      Arrays.stream(user.getRoles().split(","))
         .map(SimpleGrantedAuthority::new)
         .collect(Collectors.toList())
    );
  }
}

Step 4: Configure Spring Security

Next, create a configuration class to set up Spring Security with JWT.

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
  @Autowired
  private CustomUserDetailsService userDetailsService;
  @Autowired
  private JwtAuthenticationEntryPoint unauthorizedHandler;
  @Bean
  public JwtAuthenticationFilter jwtAuthenticationFilter() {
    return new JwtAuthenticationFilter();
  }
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
    auth.userDetailsService(userDetailsService)
      .passwordEncoder(passwordEncoder());
  }
  @Bean
  public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder();
  }
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.cors().and().csrf().disable()
      .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
      .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
      .authorizeRequests()
        .antMatchers("/api/auth/**").permitAll()
        .anyRequest().authenticated();
    http.addFilterBefore(jwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
  }
}
In this configuration:
  • We define an entry point for unauthorized access attempts.
  • We disable CSRF (since we are using JWT).
  • We permit all requests to the /api/auth/** endpoints (for authentication).
  • All other requests require authentication.

Generating and Validating JWT

Step 1: Create a JWT Utility Class
Create a utility class for generating & validating JWT tokens.

@Component
public class JwtTokenProvider {
  private final String JWT_SECRET = "secretKey";
  private final long JWT_EXPIRATION = 604800000L;
  public String generateToken(Authentication authentication) {
    UserDetails userDetails = (UserDetails) authentication.getPrincipal();
    return Jwts.builder()
      .setSubject(userDetails.getUsername())
      .setIssuedAt(new Date())
      .setExpiration(new Date(new Date().getTime() + JWT_EXPIRATION))
      .signWith(SignatureAlgorithm.HS512, JWT_SECRET)
      .compact();
  }
  public String getUsernameFromToken(String token) {
    return Jwts.parser()
      .setSigningKey(JWT_SECRET)
      .parseClaimsJws(token)
      .getBody()
      .getSubject();
  }
  public boolean validateToken(String token) {
    try {
      Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token);
      return true;
    } catch (Exception e) {
      e.printStackTrace();
    }
    return false;
  }
}

Step 2: Create an Authentication Filter

Create a filter to intercept requests and authenticate them using the JWT token.

public class JwtAuthenticationFilter extends OncePerRequestFilter {
  @Autowired
  private JwtTokenProvider tokenProvider;
  @Autowired
  private CustomUserDetailsService customUserDetailsService;
  @Override
  protected void doFilterInternal(HttpServletRequest request,
                  HttpServletResponse response,
                  FilterChain filterChain) throws ServletException, IOException {
    String token = getJwtFromRequest(request);
    if (StringUtils.hasText(token) && tokenProvider.validateToken(token)) {
      String username = tokenProvider.getUsernameFromToken(token);
      UserDetails userDetails = customUserDetailsService.loadUserByUsername(username);
      UsernamePasswordAuthenticationToken authentication =
        new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
      authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
      SecurityContextHolder.getContext().setAuthentication(authentication);
    }
    filterChain.doFilter(request, response);
  }
  private String getJwtFromRequest(HttpServletRequest request) {
    String bearerToken = request.getHeader("Authorization");
    if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
      return bearerToken.substring(7);
    }
    return null;
  }
}

 Implementing Authentication and Authorization Endpoints

Step 1: Create Authentication Controller

Create a controller to handle user authentication.

Step 2: Create DTOs for Authentication

Define the LoginRequest and JwtAuthenticationResponse DTOs:

public class LoginRequest {
  private String username;
  private String password;
}
public class JwtAuthenticationResponse {
  private String accessToken;
  private String tokenType = "Bearer";
  public JwtAuthenticationResponse(String accessToken) {
    this.accessToken = accessToken;
  }
}

Testing the Application

To test your setup:
  1. Register a new user (this can be done via a dedicated registration endpoint or manually in the database).
  2. Authenticate by sending a POST request to /api/auth/login with the user’s credentials.
  3. Use the received JWT token in the Authorization header for subsequent requests.

Conclusion

By following this guide, you’ve learned how to secure a Java web application using Spring Security and JWT. This approach ensures that your application is protected from unauthorized access, and user sessions are managed securely without relying on server-side sessions.
Implementing JWT with Spring Security provides a scalable and secure solution for modern web applications. You can further enhance this setup by adding features like token refresh, role-based access control, and more.