Search code examples
javaspring-bootspring-security

Extended (JWT) OncePerRequestFilter cannot be invoked. because @Autowire is failing sothat this.Class is returning null


I'm currently building a standard JwtAuthorizationFilter. I extend the OncePerRequestFilter class for this. Furthermore I have a JwtUtils class, which contains all JWT methods. For example, one method validates the JWT bearer token. However, I keep getting the error that this method (and all others) cannot be invoked because this.jwtUtils is null.

So bassicly I am trying to autowire the JwtUtils classe. But Spring is not giving any instance. Instead it is giving null

Thats my error message

Cannot invoke ... jwt.JwtUtils.validateJwtToken(String)" because "this.jwtUtils" is null

JwtAuthorizationFilter class (the error is throwing here)

@Slf4j
public class JwtAuthorizationFilter 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 = JwtUtils.resolveToken(request);
      System.out.println("before if " + jwt);
      if (jwtUtils.validateJwtToken(jwt)) { // ERROR !
        String username = jwtUtils.getUserNameFromJwtToken(jwt);
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);
        UsernamePasswordAuthenticationToken authentication =
            new UsernamePasswordAuthenticationToken(
                userDetails, null, userDetails.getAuthorities());
        authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

        SecurityContextHolder.getContext().setAuthentication(authentication);
      }

    } catch (Exception e) {
      JwtAuthorizationFilter.log.error("Cannot set user authentication: {}", e.getMessage());
    }
    filterChain.doFilter(request, response);
  }
}

JwtUtils class

import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.UnsupportedJwtException;
import io.jsonwebtoken.security.Keys;
import io.jsonwebtoken.security.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;

@Slf4j
@Component
public class JwtUtils {

  @Value("${ggg.app.jwtSecret}")
  private String jwtSecret;

  @Value("${ggg.app.jwtExpirationMs}")
  private long jwtExpirationMs;

  @Value("${ggg.app.jwtRefreshExpirationMs}")
  private long jwtRefreshExpirationMs;

  static String resolveToken(HttpServletRequest req) {
    String bearerToken = req.getHeader("Authorization");
    if (bearerToken != null && bearerToken.startsWith("Bearer ")) return bearerToken.substring(7);
    return null;
  }

  public String generateJwtAccessToken(String username) {
    return generateTokenFromUsername(username, jwtExpirationMs);
  }

  public String generateJwtRefreshToken(String username) {
    return generateTokenFromUsername(username, jwtRefreshExpirationMs);
  }

  private String generateTokenFromUsername(String username, Long expiration) {
    return Jwts.builder()
        .setSubject(username)
        .setIssuedAt(new Date())
        .setExpiration(new Date((new Date()).getTime() + expiration))
        .signWith(secretKey())
        .compact();
  }

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

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

  private SecretKey secretKey() {
    return Keys.hmacShaKeyFor(jwtSecret.getBytes());
  }
}

WebSecurityConfig

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

  @Autowired private UserDetailsServiceImpl userDetailsService;

  @Bean
  private static PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(12);
  }

  @Bean
  private static JwtAuthorizationFilter authenticationJwtTokenFilter() {
    return new JwtAuthorizationFilter();
  }

  //  @Override
  //  public void configure(AuthenticationManagerBuilder authenticationManagerBuilder)
  //      throws Exception {
  //    authenticationManagerBuilder
  //        .userDetailsService(userDetailsService)
  //        .passwordEncoder(WebSecurityConfig.passwordEncoder());
  //  }

  @Bean
  @Override
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {

    // Disable CSRF (cross site request forgery)
    http.cors()
        .and()
        .csrf()
        .disable()
        // No session will be created or used by spring security
        .sessionManagement()
        .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
        .and()
        .authorizeRequests()
        .antMatchers("/api/v1/auth/**", "/confirm-account")
        .permitAll()
        .anyRequest()
        .authenticated();

    // Apply JWT
    http.addFilterBefore(
        WebSecurityConfig.authenticationJwtTokenFilter(),
        UsernamePasswordAuthenticationFilter.class);
  }
}

Problem solver ?

 @Bean
  public JwtAuthorizationFilter authenticationJwtTokenFilter() {
    return new JwtAuthorizationFilter();
  }

I found out that the private static JwtAuthorizationFilter authenticationJwtTokenFilter() method is calling the issue. If i change that to a public method it is working, although passwordEncoder is privat static and is used in my auth service . Unfortunately it is my plugin which is causing that problem. It changes the code automatically. Does somebody know what to do ?

AuthService class

@Service
public class AuthService implements AuthServiceRepository {

  @Autowired private RoleRepository roleRepository;
  @Autowired private UserRepository userRepository;
  @Autowired private PasswordEncoder encoder;
  @Autowired private JwtUtils jwtUtils;
  @Autowired private AuthenticationManager authenticationManager;
  @Autowired private UserService userService;
  @Autowired private EmailService emailService;
  @Autowired private ConfirmationTokenRepository confirmationTokenRepository;

@Override
  public JwtResponse signUpUser(SignUpRequest signUpRequest, String siteURL)
      throws MessagingException, UnsupportedEncodingException {
    String accessToken = jwtUtils.generateJwtAccessToken(signUpRequest.getUsername());
    String refreshToken = jwtUtils.generateJwtRefreshToken(signUpRequest.getUsername());
    Set<Role> roles = SignUpRequest.getRoles(signUpRequest, roleRepository);
    AppUser user =
        new AppUser(
            signUpRequest.getUsername(),
            signUpRequest.getEmail(),
                encoder.encode(signUpRequest.getPassword()));
    user.setRoles(roles);

    user.setEnabled(false);
    AppUser newUser = userService.saveUser(user);
    ConfirmationToken confirmationToken = new ConfirmationToken(user);
    confirmationTokenRepository.save(confirmationToken);
    emailService.sendVerificationEmail(user, siteURL, confirmationToken.getConfirmationToken());
    List<String> userRoles =
        newUser.getRoles().stream().map(role -> role.getName().name()).collect(Collectors.toList());

    return new JwtResponse(
        accessToken,
        refreshToken,
        user.getId(),
        signUpRequest.getUsername(),
        signUpRequest.getEmail(),
        userRoles);
  } ... }

Solution

  • That's because JwtUtils and WebSecurityConfig are not registered as Spring Beans. Try to add a @Component annotation to the classes.

    More about dependency injection in Spring Boot: https://docs.spring.io/spring-boot/docs/current/reference/html/using.html#using.spring-beans-and-dependency-injection