Home > Net >  Using a custom JWT Decoder in Spring boot resource server
Using a custom JWT Decoder in Spring boot resource server

Time:07-12

I'm using the Spring boot resource server. The authentication server issues a JWT. This JWT is re-encoded(with AES) with a key and in the Resource server, I should decode the JWT (from AES) before sending it to the JwtAuthenticator.
Now, I have a security configuration.

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

        JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
        jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(new KeycloakRoleConverter());

        http
                .authorizeRequests()
                .antMatchers(HttpMethod.GET, "/users/status/check")
                .hasRole("developer")
                .anyRequest().authenticated()
                .and()
                .oauth2ResourceServer()
                .jwt()
                .decoder(new JWTDecoder())
                .jwtAuthenticationConverter(jwtAuthenticationConverter);
    }

and a JWT Decoder

import com.nimbusds.jwt.JWT;
import com.nimbusds.jwt.JWTParser;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtException;

import java.text.ParseException;

public class JWTDecoder implements JwtDecoder {
    @Override
    public Jwt decode(String token) throws JwtException {


        //decrypt from AES here


        JWT jwt = null;
        try {
            jwt = JWTParser.parse(token);
        } catch (ParseException e) {
            e.printStackTrace();
        }


        return null;
    }
}

What should I do then? The function should return org.springframework.security.oauth2.jwt.Jwt. How can I convert String token to Jwt?
I tried the following, but a problem occurred.

    private Jwt createJwt(String token, JWT parsedJwt) {
        try {
            Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
            Map<String, Object> claims = parsedJwt.getJWTClaimsSet().getClaims();
            return Jwt.withTokenValue(token)
                    .headers(h -> h.putAll(headers))
                    .claims(c -> c.putAll(claims))
                    .build();
        } catch (Exception ex) {
            if (ex.getCause() instanceof ParseException) {
                throw new JwtException("There is a problem parsing the JWT.");
            } else {
                throw new JwtException("There is a problem decoding the JWT.");
            }
        }
    }

The error I received:

java.lang.IllegalArgumentException: timestamps must be of type Instant: java.lang.Long



I'm using Keycloak to generate the JWT. So, the exp field of the token in the jwt.io is "exp": 1657363340,. But after parsing the JWT in my code, It changes to the Date format. So, I changed the exp to Instant and my final method is like the following:
    private Jwt createJwt(String token, JWT parsedJwt) {
        try {
            Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
            Map<String, Object> claims = parsedJwt.getJWTClaimsSet().getClaims();
            Jwt.Builder finalJwt = Jwt.withTokenValue(token)
                    .headers(h -> h.putAll(headers))
                    .claims(c -> c.putAll(claims));
            finalJwt.expiresAt(((Date) claims.get("exp")).toInstant());
            return finalJwt.build();
        } catch (Exception ex) {
            if (ex.getCause() instanceof ParseException) {
                throw new JwtException("There is a problem parsing the JWT: "   ex.getMessage());
            } else {
                throw new JwtException("There is a problem decoding the JWT: "   ex.getMessage());
            }
        }
    }

But the problem exists yet.

CodePudding user response:

It could be due to expiration date of your token is a timestamp, and it should be a number (Long). Or you are trying to parse a timestamp to number Long.

CodePudding user response:

As @Jose told me, I set the value of expiration time with an Instant type of the timestamp. Then, I set it to both the exp and iat fields of the JWT. My final function is like the following:

Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject());
Map<String, Object> claims = new HashMap<>();
for (String key : parsedJwt.getJWTClaimsSet().getClaims().keySet()) {
    Object value = parsedJwt.getJWTClaimsSet().getClaims().get(key);
    if (key.equals("exp") || key.equals("iat")) {
        value = ((Date) value).toInstant();
    }
    claims.put(key, value);
}
return Jwt.withTokenValue(token)
        .headers(h -> h.putAll(headers))
        .claims(c -> c.putAll(claims))
        .build();
  • Related