Spring Security is a powerful and highly customizable authentication and access control framework for Java applications. It is the de facto standard for securing Spring-based applications. With the rise of RESTful APIs and the need for stateless authentication, JSON Web Tokens (JWT) have become popular for securing REST APIs. JWT offers a compact and self-contained way for securely transmitting information between parties as a JSON object. Combined with Spring Security, it provides a robust security solution for your Spring applications. In this blog post, we will explore how to integrate Spring Security with JWT to protect REST APIs.

We’ll cover the basics of JWT, how it works with Spring Security, and provide a step-by-step guide to implementing JWT-based authentication in a Spring Boot application. Whether you’re building a new API or looking to enhance the security of an existing one, this post will provide you with the knowledge and tools to effectively use Spring Security with JWT. Let’s dive into the world of secure REST APIs with Spring Security and JWT!

Dependencies Used In Spring Security Tutorial Project

  • MySQL as DB.
  • Spring Data JPA as an ORM tool
  • Spring Security Starter
  • Lombok to reduce boilerplate code.
  • Validation API to validate inputs for fields.
  • JWT for authentication tokens.
  • Dev Tools for live reload.
  • Maven as a build tool.

pom.xml after adding dependencies.

<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-mail</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>jakarta.validation</groupId>
            <artifactId>jakarta.validation-api</artifactId>
        </dependency>
        <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>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

Entity Classes For Spring Security Implementation

Kicking off with the Entity class is an essential move in the process of integrating Spring Security, given that it lays the groundwork for establishing your domain model. 
This class usually mirrors a database table, serving to outline the attributes and associations of the data you’ll be handling. In this article, we’ll focus on authenticating users through JWT, so for the moment, we will set aside role-based authorization and will build a simple User Entity class 

@Entity
@Table(name = "users", 
        uniqueConstraints = { 
        @UniqueConstraint(columnNames = "username"),
        @UniqueConstraint(columnNames = "email") })

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    @NotBlank
    @Email
    private String username;
    @NotBlank
    private String password;
    private String firstName;
    private String lastName;
    public User(String username, String email, String password) {
    }
}

User Principle In Spring Security

Within Spring Security, setting up a UserPrincipal typically happens once the user’s identity has been confirmed. This involves generating an Authentication object that represents the user’s credentials and then placing it within the SecurityContextHolder. Below is a detailed walkthrough for manually configuring the UserPrincipal.

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class UserDetailsImpl implements UserDetails {
    private static final long serialVersionUID = 1L;
    private Long id;
    private String username;
    @JsonIgnore
    private String password;
    private String firstName;
    private String lastName;
    private String about;
    private String profileImage;
    @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;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o)
        return true;
        if (o == null || getClass() != o.getClass())
        return false;
        UserDetailsImpl user = (UserDetailsImpl) o;
        return Objects.equals(id, user.id);
    }
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return null;
    }
}

DTO For Data Transfer

In this tutorial on Spring Security, we make use of various Data Transfer Objects (DTOs) including SignupRequest, LoginRequest, MessageResponse<T> and JwtResponse. These DTOs are essential for conveying request and response information when interacting with APIs.

@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class SignupRequest {

    @Email
    private String username;
    private String password;
    private String firstName;
    private String lastName;
    private String about;
    private String profileImage;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class LoginRequest {

    private String username;
    private String password;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class MessageResponse<T> {

    private String message;
    private HttpStatus httpStatus;
    private T data;
}
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class JwtResponse {

    private String token;
    private UserDetailsImpl detailsImpl;
}

Repository For Spring Security

We will create a user repository to retrieve and persist or update the user data in the Database.

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
  
    Optional<User> findByUsername(String username);
    Boolean existsByUsername(String username);
}

Configure Properties File

For Spring Security implementation we will Add below properties to application properties file to generated custom security key and trace spring security logs. 
Note: Do not use # and $ in secrete key as they will throw invalid Base64 string.

spring.application.name=ABC
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/abc
spring.datasource.username=abcd
spring.datasource.password=dbPass
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

systems.app.jwtSecret= =====================================ABC===============
=================================
systems.app.jwtExpirationMs = 3600000

logging.level.org.springframework.security=TRACE

JWT Utility Class For Spring Security

In Spring Security we need to develop a JWT Utilities class that will set up, produce, and verify JWT tokens which are essential for user authentication and for granting access to protected resources.

@Component
@Slf4j
public class JwtUtils {

    @Value("${systems.app.jwtSecret}")
    private String jwtSecret;
    @Value("${systems.app.jwtExpirationMs}")
    private int jwtExpirationMs;

    public String generateJwtToken(String username) {

        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(new Date((new Date())
                        .getTime() + jwtExpirationMs))
                .signWith(key(), 
                        SignatureAlgorithm.HS256)
                .compact();
    }

    private Key key() {
        return Keys
                .hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));
    }

    public String getUserNameFromJwtToken(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(key())
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    public boolean validateJwtToken(String authToken) {
        try {
            Jwts.parserBuilder()
            .setSigningKey(key())
            .build()
            .parse(authToken);
            
            return true;
        } catch (MalformedJwtException e) {
            log.error("Invalid JWT token: {}", e.getMessage());
        } catch (ExpiredJwtException e) {
            log.error("JWT token is expired: {}", e.getMessage());
        } catch (UnsupportedJwtException e) {
            log.error("JWT token is unsupported: {}", e.getMessage());
        } catch (IllegalArgumentException e) {
            log.error("JWT claims string is empty: {}", e.getMessage());
        }
        return false;
    }
}

Implementation Of AuthenticationEntryPoint

AuthenticationEntryPoint is a fundamental aspect of Spring Security, especially when it comes to integrating JSON Web Tokens (JWT) for securing Spring-based applications. In this blog post, we will delve into the role of AuthenticationEntryPoint within the Spring Security framework, and how it works in tandem with JWT to provide a robust authentication mechanism. We’ll explore how to set up and configure an AuthenticationEntryPoint to handle authentication errors gracefully, ensuring that your application not only remains secure but also provides meaningful feedback to users when authentication fails. 

@Component
@Slf4j
public class AuthEntryPointJwt implements AuthenticationEntryPoint {

  @Override
  public void commence(HttpServletRequest request, 
                 HttpServletResponse response, AuthenticationException authException)
      throws IOException, ServletException {
    log.error("Unauthorized error: {}", authException.getMessage());
    response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Error: Unauthorized");
  } 
}

Implementing OncePerRequestFilter

OncePerRequestFilter is a class provided by Spring Security that ensures a filter is only executed once per request. This can be particularly useful in scenarios where multiple filter executions need to be avoided, such as when dealing with token-based authentication or request logging.

When implementing a custom filter in Spring Security, extending OncePerRequestFilter can help prevent unnecessary duplicate filter execution, ensuring that the filter logic is only applied once per request. This can be beneficial for performance optimization and avoiding unintended side effects that may arise from multiple executions of the same filter within a single request.

By extending OncePerRequestFilter and overriding the doFilterInternal method, developers can implement their custom filter logic while being assured that it will only be executed once for each incoming request. This class provides a convenient way to maintain the desired behavior of filters within the Spring Security framework.

@Component
public class AuthTokenFilter extends OncePerRequestFilter {
      @Autowired
      private JwtUtils jwtUtils;

      @Autowired
      private UserDetailsServiceImpl userDetailsService;

      @Override
      protected void doFilterInternal(HttpServletRequest request, 
              HttpServletResponse response, FilterChain filterChain)
          throws ServletException, IOException {
        try {
          String jwt = parseJwt(request);
          if (jwt != null && jwtUtils.validateJwtToken(jwt)) {
            String email = jwtUtils.getUserNameFromJwtToken(jwt);
             System.out.println("Username : "+email);
            UserDetails userDetails = userDetailsService.loadUserByUsername(email);
            UsernamePasswordAuthenticationToken authentication 
            = new UsernamePasswordAuthenticationToken(userDetails, null, null);
            authentication.setDetails(new WebAuthenticationDetailsSource()
                    .buildDetails(request));
            SecurityContextHolder.getContext()
            .setAuthentication(authentication);
          }
        } catch (Exception e) {
          logger.error("Cannot set user authentication: {}", e);
        }
        filterChain.doFilter(request, response);
      }

      private String parseJwt(HttpServletRequest request) {
        String headerAuth = request.getHeader("Authorization");

        if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
          return headerAuth.substring(7, headerAuth.length());
        }

        return null;
      }
    }

Role & Implementation Of UserDetailsService

UserDetailsService is an integral part of the Spring Security framework, which plays a crucial role in authentication processes. In the context of building secure applications, the integration of Spring Security with JSON Web Tokens (JWT) has become a popular approach to managing user authentication and authorization. JWT offers a compact and self-contained way for securely transmitting information between parties as a JSON object, and when combined with Spring Security, it provides a robust authentication mechanism.

@Service
@Slf4j
public class UserDetailsServiceImpl implements UserDetailsService {
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Override
    @Transactional
    public UserDetails loadUserByUsername(String username)
                             throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> 
         new UsernameNotFoundException("User Not Found with username: " + username));
        
        UserDetailsImpl userDetails = UserDetailsImpl.builder()
                .password(user.getPassword())
                .firstName(user.getFirstName())
                .lastName(user.getLastName())
                .about(user.getAbout())
                .username(user.getUsername())
                .id(user.getId())
                .profileImage(user.getProfileImage())
                .build();

        return userDetails;
    }

    public MessageResponse<?> signup(SignupRequest signupRequest) {
        try {
            User user = User.builder()
                    .about(signupRequest.getAbout())
                    .username(signupRequest.getUsername())
                    .firstName(signupRequest.getFirstName())
                    .lastName(signupRequest.getLastName())
                    .password(passwordEncoder.encode(signupRequest.getPassword()))
                    .profileImage(signupRequest.getProfileImage()).build();
            userRepository.save(user);
            log.info("Profile created for  :" + signupRequest.getUsername());

            MessageResponse<?> messageResponse = MessageResponse
                    .builder()
                    .message("Profile created for  :" + signupRequest.getUsername())
                    .httpStatus(HttpStatus.CREATED)
                    .build();
            return messageResponse;

        } catch (Exception e) {
            log.error("error while adding new user", e);
            MessageResponse<?> messageResponse = MessageResponse
                    .builder().message("adding user failed")
                    .httpStatus(HttpStatus.INTERNAL_SERVER_ERROR)
                    .build();
            return messageResponse;
        }
    }
}

Spring Security Without WebSecurityConfigurerAdapter

WebSecurityConfigurerAdapter is now considered obsolete within Spring Security, we’ll proceed to set up our Spring Security configuration class without utilizing the WebSecurityConfigurerAdapter. Traditionally, developers have relied on the WebSecurityConfigurerAdapter class to configure security settings. However, it is possible to implement Spring Security without using it, offering a more flexible and customized approach to security configuration. By bypassing the WebSecurityConfigurerAdapter, developers can have more control over their security configuration.

This can be particularly useful in scenarios where the default behavior of WebSecurityConfigurerAdapter does not align with the specific requirements of the application. Implementing Spring Security without relying on WebSecurityConfigurerAdapter allows for a more tailored and fine-grained approach to securing the application, providing the flexibility to define security settings programmatically as needed. In the following sections, we will explore the alternative methods and best practices for implementing Spring Security without the use of WebSecurityConfigurerAdapter.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig { // extends WebSecurityConfigurerAdapter {
    
    @Autowired
    UserDetailsService userDetailsService;
    @Autowired
    private  AuthTokenFilter authenticationJwtTokenFilter;
    
    @Bean
    public static PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http, 
                                     AuthenticationManager authenticationManager)
            throws Exception {
        http.csrf(AbstractHttpConfigurer::disable).cors(Customizer.withDefaults())
                .sessionManagement(session -> session
                      .sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/v1/auth/**").permitAll()
                        .requestMatchers("/api/test/**").permitAll()
                        .anyRequest().authenticated())
                .authenticationManager(authenticationManager)
                .addFilterBefore(authenticationJwtTokenFilter,
                         UsernamePasswordAuthenticationFilter.class);
        return http.build();
    }
    @Bean
    public CorsConfigurationSource corsConfigurationSource() {
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(Arrays.asList("*"));
        configuration.setAllowedMethods(Arrays.asList("*"));
        configuration.setAllowedHeaders(Arrays.asList("*"));
        configuration.setExposedHeaders(Arrays.asList("*"));
        // Limited to API routes (neither actuator nor Swagger-UI)
        // replace * with your expected URLS
        final var source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);
        return source;
    }
    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity httpSecurity)
                                                       throws Exception {
        AuthenticationManagerBuilder authenticationManagerBuilder = httpSecurity
                .getSharedObject(AuthenticationManagerBuilder.class);
        authenticationManagerBuilder
          .userDetailsService(userDetailsService)
          .passwordEncoder(passwordEncoder());
        return authenticationManagerBuilder.build();
    }
}

Rest Entry Point To Check Spring Security

@CrossOrigin(origins = "*", maxAge = 3600)
@RestController
@RequestMapping("/api/v1/auth")
public class AuthenticationController {
    
    @Autowired
    private AuthenticationManager authenticationManager;
    @Autowired
    private UserRepository userRepository;
    @Autowired
    private JwtUtils jwtUtils;
    @Autowired
    private UserDetailsServiceImpl userDetailsServiceImpl;

    @PostMapping("/signin")
    public ResponseEntity<?> authenticateUser(@Valid @RequestBody LoginRequest loginRequest) {

        Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), 
                                                      loginRequest.getPassword()));

        SecurityContextHolder.getContext().setAuthentication(authentication);
        UserDetailsImpl userDetails =  (UserDetailsImpl) authentication.getPrincipal();
        String jwt = jwtUtils.generateJwtToken(userDetails.getUsername());

        JwtResponse jwtResponse = JwtResponse.builder()
                .token(jwt)
                .detailsImpl(userDetails)
                .build();
        return ResponseEntity.ok(jwtResponse);
    }

    @PostMapping("/signup")
    public MessageResponse<?> registerUser(@Valid @RequestBody SignupRequest user) {
        
        if (userRepository.existsByUsername(user.getUsername())) {
            MessageResponse<?> messageResponse = MessageResponse.builder()
                    .message("Error: Email is already in use!")
                    .httpStatus(HttpStatus.ALREADY_REPORTED)
                    .build();
            return messageResponse;
        }
        return userDetailsServiceImpl.signup(user);
    }
}

Run the Application and access APIs with Postman, SoapUi or any other tool of your choice. 

APIs and response Structure 

http://localhost:8080/api/v1/auth/signup
{
    "username": "[email protected]",
    "password": "myPassword",
    "firstName": "Coding",
    "lastName": "Patient",
    "about": " Software Engineer working on Java and open source frameworks"
} 

Response

{
    "message": "Profile created for  :[email protected]",
    "httpStatus": "CREATED",
    "data": null
}
http://localhost:8080/api/v1/auth/signin

Request

{
    "username": "[email protected]",
    "password": "myPassword"
}

Response

{
    "token": "eyJhbGciOiJIUzI1NiJ9
.eyJzdWIiOiJ0ZXN0QHRlc3QuY29tIiwiaWF0IjoxNzEyMzA4MDk1LCJleHAiOjE3MTIzMTE2OTV9
.3El9JpCgcSFYZLXqz11FLJRbQucBMa_3KjCI38hYT0M",
    "detailsImpl": {
        "id": 2,
        "username": "[email protected]",
        "firstName": "Coding",
        "lastName": "Patient",
        "about": " Software Engineer working on Java and open source frameworks",
        "profileImage": null,
        "enabled": true,
        "authorities": null,
        "credentialsNonExpired": true,
        "accountNonExpired": true,
        "accountNonLocked": true
    }
}

The token will be used for accessing secured APIs

Conclusion

We have learned how to implement Spring Security. The main purpose was to implement it without WebSecurityConfigurerAdapter. If You find this useful let me or ping suggestions. you can reach me also at [email protected]. Thanks For reading.

See Also
How To Create CRUD For Views

Spring references from spring.io

Leave a Reply

Your email address will not be published. Required fields are marked *

I’m Sohail

A Java Developer focused on creating efficient, scalable solutions. With expertise in Java and backend development, I specialize in RESTful APIs, Struts1, Struts2, JSF, Hibernate, Spring, Jersey, Oracle ATG and clean, maintainable code. I’m driven by a love for problem-solving and continuous learning.

Let’s connect