diff --git a/pom.xml b/pom.xml index 6f1d1a10..7bf8b93b 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ 2.21.1 0.2.2 1.13 - 0.11.5 + 0.12.5 2021.0.9 0.27.0 1.8.0 diff --git a/src/main/java/jasper/security/Auth.java b/src/main/java/jasper/security/Auth.java index 73df793c..ff7d380e 100644 --- a/src/main/java/jasper/security/Auth.java +++ b/src/main/java/jasper/security/Auth.java @@ -45,6 +45,7 @@ import java.util.Set; import java.util.stream.Stream; +import static io.jsonwebtoken.Jwts.claims; import static jasper.config.JacksonConfiguration.dump; import static jasper.domain.proj.HasOrigin.isSubOrigin; import static jasper.repository.spec.OriginSpec.isOrigin; @@ -936,7 +937,7 @@ public Claims getClaims() { if (auth instanceof JwtAuthentication j) { claims = j.getClaims(); } else { - claims = new DefaultClaims(); + claims = claims().build(); } } return claims; diff --git a/src/main/java/jasper/security/jwt/JwkSigningKeyResolver.java b/src/main/java/jasper/security/jwt/JwkSigningKeyResolver.java index 1b74202a..b26d2c87 100644 --- a/src/main/java/jasper/security/jwt/JwkSigningKeyResolver.java +++ b/src/main/java/jasper/security/jwt/JwkSigningKeyResolver.java @@ -36,7 +36,7 @@ public Key resolveSigningKey(JwsHeader header, Claims claims) { } @Override - public Key resolveSigningKey(JwsHeader header, String plaintext) { + public Key resolveSigningKey(JwsHeader header, byte[] content) { return getKey(header.getKeyId()); } diff --git a/src/main/java/jasper/security/jwt/TokenProviderImpl.java b/src/main/java/jasper/security/jwt/TokenProviderImpl.java index ca88f234..ce6e0a1e 100644 --- a/src/main/java/jasper/security/jwt/TokenProviderImpl.java +++ b/src/main/java/jasper/security/jwt/TokenProviderImpl.java @@ -5,7 +5,6 @@ import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; -import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.UnsupportedJwtException; import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.SignatureException; @@ -68,17 +67,19 @@ public String createToken(Authentication authentication, int validityInSeconds) var validity = new Date(now + 1000L * validityInSeconds); return Jwts .builder() - .setSubject(authentication.getName()) - .setAudience(security.getClientId()) + .subject(authentication.getName()) + .audience() + .add(security.getClientId()) + .and() .claim(security.getAuthoritiesClaim(), authorities) .claim(security.getVerifiedEmailClaim(), true) - .signWith(Keys.hmacShaKeyFor(security.getSecretBytes()), SignatureAlgorithm.HS512) - .setExpiration(validity) + .signWith(Keys.hmacShaKeyFor(security.getSecretBytes()), Jwts.SIG.HS512) + .expiration(validity) .compact(); } public Authentication getAuthentication(String token, String origin) { - var claims = getParser(origin).parseClaimsJws(token).getBody(); + var claims = getParser(origin).parseSignedClaims(token).getPayload(); var principal = getUsername(claims, origin); var user = getUser(principal); logger.debug("Token Auth {} {}", principal, origin); @@ -91,11 +92,11 @@ JwtParser getParser(String origin) { switch (security.getMode()) { case "jwt": var key = Keys.hmacShaKeyFor(security.getSecretBytes()); - jwtParsers.put(origin, Jwts.parserBuilder().setSigningKey(key).build()); + jwtParsers.put(origin, Jwts.parser().verifyWith(key).build()); break; case "jwks": try { - jwtParsers.put(origin, Jwts.parserBuilder().setSigningKeyResolver(new JwkSigningKeyResolver(new URI(security.getJwksUri()), restTemplate)).build()); + jwtParsers.put(origin, Jwts.parser().setSigningKeyResolver(new JwkSigningKeyResolver(new URI(security.getJwksUri()), restTemplate)).build()); } catch (URISyntaxException e) { logger.error("Cannot parse JWKS URI {}", security.getJwksUri()); throw new RuntimeException(e); @@ -149,7 +150,7 @@ String getUsername(Claims claims, String origin) { logger.debug("User tag set by JWT claim {}: {} ({})", security.getUsernameClaim(), principal, origin); } logger.debug("Principal: {}", principal); - if (principal.contains("@")) { + if (principal != null && principal.contains("@")) { var emailDomain = principal.substring(principal.indexOf("@") + 1); principal = principal.substring(0, principal.indexOf("@")); var security = configs.security(origin); @@ -192,8 +193,15 @@ public boolean validateToken(String authToken, String origin) { } if (!StringUtils.hasText(authToken)) return false; try { - var claims = getParser(origin).parseClaimsJws(authToken).getBody(); - if (!security.getClientId().equals(claims.getAudience())) { + var claims = getParser(origin).parseSignedClaims(authToken).getPayload(); + if (isBlank(security.getClientId()) && + claims.getAudience() != null && + (!claims.getAudience().contains("") || !claims.getAudience().isEmpty())) { + securityMetersService.trackTokenInvalidAudience(); + logger.trace(INVALID_JWT_TOKEN + " Invalid Audience"); + } else if (isNotBlank(security.getClientId()) && + (claims.getAudience() == null || !claims.getAudience().contains(security.getClientId()) || claims.getAudience().size() != 1)) { + // TODO: add method to whitelist extra audiences securityMetersService.trackTokenInvalidAudience(); logger.trace(INVALID_JWT_TOKEN + " Invalid Audience"); } else if (isNotBlank(security.getVerifiedEmailClaim()) && claims.getOrDefault(security.getVerifiedEmailClaim(), Boolean.FALSE).equals(false)) { @@ -214,6 +222,7 @@ public boolean validateToken(String authToken, String origin) { } catch (SignatureException e) { securityMetersService.trackTokenInvalidSignature(); logger.trace(INVALID_JWT_TOKEN, e); + } catch (IllegalArgumentException e) { logger.error("Token validation error {}", e.getMessage()); } diff --git a/src/test/java/jasper/security/jwt/TokenProviderImplTest.java b/src/test/java/jasper/security/jwt/TokenProviderImplTest.java index 042401fe..f05407a4 100644 --- a/src/test/java/jasper/security/jwt/TokenProviderImplTest.java +++ b/src/test/java/jasper/security/jwt/TokenProviderImplTest.java @@ -57,15 +57,16 @@ TokenProviderImpl getTokenProvider(String localOrigin) { } Claims getClaims(String sub) { - var claims = new DefaultClaims(); - claims.setSubject(sub); - return claims; + return new DefaultClaims(Map.of( + "sub", sub + )); } Claims getClaims(String sub, String auth) { - var claims = new DefaultClaims(Map.of("auth", auth)); - claims.setSubject(sub); - return claims; + return new DefaultClaims(Map.of( + "auth", auth, + "sub", sub + )); } Props getProps(String localOrigin) { @@ -164,15 +165,15 @@ private String createUnsupportedToken() { } private String createTokenWithDifferentSignature() { - Key otherKey = Keys.hmacShaKeyFor( + var otherKey = Keys.hmacShaKeyFor( Decoders.BASE64.decode("Xfd54a45s65fds737b9aafcb3412e07ed99b267f33413274720ddbb7f6c5e64e9f14075f2d7ed041592f0b7657baf8") ); return Jwts - .builder() - .setSubject("anonymous") + .builder() + .subject("anonymous") .signWith(otherKey, SignatureAlgorithm.HS512) - .setExpiration(new Date(new Date().getTime() + ONE_MINUTE)) + .expiration(new Date(new Date().getTime() + ONE_MINUTE)) .compact(); } diff --git a/src/test/java/jasper/security/jwt/TokenProviderSecurityMetersTests.java b/src/test/java/jasper/security/jwt/TokenProviderSecurityMetersTests.java index 30af1f5d..3566cffa 100644 --- a/src/test/java/jasper/security/jwt/TokenProviderSecurityMetersTests.java +++ b/src/test/java/jasper/security/jwt/TokenProviderSecurityMetersTests.java @@ -155,9 +155,9 @@ private String createTokenWithDifferentSignature() { return Jwts .builder() - .setSubject("anonymous") + .subject("anonymous") .signWith(otherKey, SignatureAlgorithm.HS512) - .setExpiration(new Date(new Date().getTime() + ONE_MINUTE)) + .expiration(new Date(new Date().getTime() + ONE_MINUTE)) .compact(); }