1

I am working on a Spring Boot project implementing JWT authentication but I am finding some difficulties. It is based on an example found into a Udemy course that I am trying to adapt to my specific use case. It is composed by the following two microservices:

  1. GET-USER-WS: this is the microservice that obtains user information from the database.

  2. AuthServerJWT: this microservice calls the GET-USER-WS in order to obtain user information and build the JWT token that will be used by other microservices.

Basically this second AuthServerJWT contains the following JwtAuthenticationRestController controller class:

@RestController
//@CrossOrigin(origins = "http://localhost:4200")
public class JwtAuthenticationRestController  {

    @Value("${sicurezza.header}")
    private String tokenHeader;

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Autowired
    @Qualifier("customUserDetailsService")
    private UserDetailsService userDetailsService;
    
    private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationRestController.class);

    @PostMapping(value = "${sicurezza.uri}")
    public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtTokenRequest authenticationRequest)
            throws AuthenticationException {
        logger.info("Autenticazione e Generazione Token");

        authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());

        final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());

        final String token = jwtTokenUtil.generateToken(userDetails);
        
        logger.warn(String.format("Token %s", token));

        return ResponseEntity.ok(new JwtTokenResponse(token));
    }

    @RequestMapping(value = "${sicurezza.uri}", method = RequestMethod.GET)
    public ResponseEntity<?> refreshAndGetAuthenticationToken(HttpServletRequest request) 
            throws Exception 
    {
        String authToken = request.getHeader(tokenHeader);
        
        if (authToken == null || authToken.length() < 7)
        {
            throw new Exception("Token assente o non valido!");
        }
        
        final String token = authToken.substring(7);
        
        if (jwtTokenUtil.canTokenBeRefreshed(token)) 
        {
            String refreshedToken = jwtTokenUtil.refreshToken(token);
            
            return ResponseEntity.ok(new JwtTokenResponse(refreshedToken));
        } 
        else 
        {
            return ResponseEntity.badRequest().body(null);
        }
    }

    @ExceptionHandler({ AuthenticationException.class })
    public ResponseEntity<String> handleAuthenticationException(AuthenticationException e) 
    {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
    }

    private void authenticate(String username, String password) 
    {
        Objects.requireNonNull(username);
        Objects.requireNonNull(password);

        try {   
            /// ???
            authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
        } 
        catch (DisabledException e) 
        {
            logger.warn("UTENTE DISABILITATO");
            throw new AuthenticationException("UTENTE DISABILITATO", e);
        } 
        catch (BadCredentialsException e) 
        {
            logger.warn("CREDENZIALI NON VALIDE");
            throw new AuthenticationException("CREDENZIALI NON VALIDE", e);
        }
    }
}

AS you can see it contains two method: one to create a brand new JWT token and the other one to refresh an existing JWT token. Consider the createAuthenticationToken() used to create a new token.

At this line:

final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());

it call the loadUserByUsername() method defined in my CustomUserDetailsService. This method basically retrieve the user information calling a service of the **GET-USER-WS and build a UserDetails object (that is a Spring Security object, this one: org.springframework.security.core.userdetails.UserDetails).

This is the code of this method:

@Override
public UserDetails loadUserByUsername(String UserId) throws UsernameNotFoundException {
    
    String ErrMsg = "";
    
    if (UserId == null || UserId.length() < 2) {
        ErrMsg = "Nome utente assente o non valido";
        
        logger.warn(ErrMsg);
        
        throw new UsernameNotFoundException(ErrMsg); 
    } 
    
    User user = this.GetHttpValue(UserId);
    
    if (user == null) {
        ErrMsg = String.format("User %s not found!!", UserId);
        
        logger.warn(ErrMsg);
        
        throw new UsernameNotFoundException(ErrMsg);
    }
    
    UserBuilder builder = null;
    
    builder = org.springframework.security.core.userdetails.User.withUsername(Integer.toString(user.getId()));
    builder.password(user.getPswd());
    
    String[] operations = user.getUserTypes().stream()
            .map(UserType::getOperations)
            .flatMap(Set::stream)
            .map(Operation::getName)
            .distinct()
            .toArray(String[]::new);
    
    builder.authorities(operations);
    
    
    return builder.build();
    
}

This line simply execute the GetHttpValue() method used to perform the call with the RestTemplate in order to retrieve an User object containing the details of the user:

User user = this.GetHttpValue(UserId);

So, come back to the first createAuthenticationToken() of the previous JwtAuthenticationRestController controller class. My problem is that it retrieve this UserDetails userDetails object, at this line:

final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());

using the deubugger this is the content of my userDetails instance:

org.springframework.security.core.userdetails.User [Username=50, Password=[PROTECTED], Enabled=true, AccountNonExpired=true, credentialsNonExpired=true, AccountNonLocked=true, Granted Authorities=[ADMIN]]

it seems to me correct (except the Username field that contains the ID of the user and not the username...I will change in a second time, it should not be the problem).

Then it perform this line in order to generate the JWT token starting from this userDetails object:

final String token = jwtTokenUtil.generateToken(userDetails);

This is the code of the generateToken() method defined into a class named JwtTokenUtil:

public String generateToken(UserDetails userDetails) {
    Map<String, Object> claims = new HashMap<>();
    return doGenerateToken(claims, userDetails);
}

It first create an empty HashMap and then call the doGenerateToken(), this is the code:

private String doGenerateToken(Map<String, Object> claims, UserDetails userDetails) {
    final Date createdDate = clock.now();
    final Date expirationDate = calculateExpirationDate(createdDate);

    return Jwts.builder()
            .setClaims(claims)
            .setSubject(userDetails.getUsername())
            .claim("authorities", userDetails.getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()))
            .setIssuedAt(createdDate)
            .setExpiration(expirationDate)
            .signWith(SignatureAlgorithm.HS512, jwtConfig.getSecret().getBytes())
            .compact();
}

It correctly create the expirationDate date. Then at the end it throws the following exception (using the deubbugger it seems to me that it is thrown on the compact() execution): The problem is that when this line is executed I am obtaining the following exception:

java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter
    at io.jsonwebtoken.impl.Base64Codec.encode(Base64Codec.java:21) ~[jjwt-0.9.1.jar:0.9.1]
    at io.jsonwebtoken.impl.Base64UrlCodec.encode(Base64UrlCodec.java:22) ~[jjwt-0.9.1.jar:0.9.1]
    at io.jsonwebtoken.impl.DefaultJwtBuilder.base64UrlEncode(DefaultJwtBuilder.java:349) ~[jjwt-0.9.1.jar:0.9.1]
    at io.jsonwebtoken.impl.DefaultJwtBuilder.compact(DefaultJwtBuilder.java:295) ~[jjwt-0.9.1.jar:0.9.1]
    at com.easydefi.authserverjwt.security.JwtTokenUtil.doGenerateToken(JwtTokenUtil.java:87) ~[classes/:na]
    at com.easydefi.authserverjwt.security.JwtTokenUtil.generateToken(JwtTokenUtil.java:72) ~[classes/:na]
    at com.easydefi.authserverjwt.controller.JwtAuthenticationRestController.createAuthenticationToken(JwtAuthenticationRestController.java:60) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:78) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:567) ~[na:na]
    at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1067) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:681) ~[tomcat-embed-core-9.0.54.jar:4.0.FR]
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883) ~[spring-webmvc-5.3.12.jar:5.3.12]
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:764) ~[tomcat-embed-core-9.0.54.jar:4.0.FR]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:204) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183) ~[spring-security-web-5.5.3.jar:5.5.3]
    at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271) ~[spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201) ~[spring-web-5.3.12.jar:5.3.12]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.12.jar:5.3.12]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:540) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:895) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1722) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) ~[tomcat-embed-core-9.0.54.jar:9.0.54]
    at java.base/java.lang.Thread.run(Thread.java:831) ~[na:na]

Why? What am I missing? How can I try to solve this issue?

AndreaNobili
  • 40,955
  • 107
  • 324
  • 596
  • 5
    You are missing JAXB the API for XML binding. This used to be included in the JDK but it was removed in Java 11 and beyond and is now an external library. How are you including it in your application? – swpalmer Nov 17 '21 at 17:29
  • 1
    `javax.xml.bind.*` was removed in Java 9 (or 10). You either need to upgrade to a version of the JWT library that uses `java.util.Base64` (assuming such version exists), or you need to add a dependency on JAXB. – Mark Rotteveel Nov 17 '21 at 17:30
  • 1
    See https://stackoverflow.com/questions/9566620/include-jaxb-using-maven – swpalmer Nov 17 '21 at 17:31
  • @swpalmer Where is it used? I can't see method call to instance belonging to this dependency and I have no compilation error. What am I missing? – AndreaNobili Nov 17 '21 at 17:42
  • @MarkRotteveel where am I using this dependency? I can't find it. Do you mean that it is the Jwts class that it using? Have I to use a new version of this? or what am I missing? – AndreaNobili Nov 17 '21 at 17:45
  • 3
    That io.jsonwebtoken library uses it as shown in the stacktrace of `java.lang.NoClassDefFoundError: javax/xml/bind/DatatypeConverter`. That `DatatypeConverter` is part of JAXB. In the past JAXB was included in the JRE. This changed with Java 9 or 10, so you need to add an explicit dependency on JAXB for this version of the library to work. – Mark Rotteveel Nov 17 '21 at 17:50
  • 1
    Spring security comes included with the JWT library Nimbus, so no need to pull in any additional jwtlibrary – Toerktumlare Nov 18 '21 at 00:18

1 Answers1

8

I don't know why I am facing this problem, when I run the code in postgres it works fine but I get thiss error with cassandra.

I have just added those into my pom.xml

<dependency>
        <groupId>javax.xml.bind</groupId>
        <artifactId>jaxb-api</artifactId>
        <version>2.2.7</version>
    </dependency>
    <dependency>
        <groupId>com.sun.xml.bind</groupId>
        <artifactId>jaxb-impl</artifactId>
        <version>2.2.5-b10</version>
    </dependency>

I got them from here: Include JAXB using Maven