Skip to content

Commit

Permalink
springboot3使用nimbus-jose-jwt替换了jjwt, jjwt的实现对于graalvm编译非常不友好。Nop的JwtH…
Browse files Browse the repository at this point in the history
…elper也修改为使用jose-jwt
  • Loading branch information
entropy-cloud committed Sep 15, 2024
1 parent e1e44bd commit a06eccf
Show file tree
Hide file tree
Showing 7 changed files with 139 additions and 109 deletions.
9 changes: 7 additions & 2 deletions nop-auth/nop-auth-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,14 @@

<dependencies>

<!-- <dependency>-->
<!-- <groupId>io.jsonwebtoken</groupId>-->
<!-- <artifactId>jjwt-impl</artifactId>-->
<!-- </dependency>-->

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<groupId>com.nimbusds</groupId>
<artifactId>nimbus-jose-jwt</artifactId>
</dependency>

<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,11 @@
*/
package io.nop.auth.core.jwt;

import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.security.Keys;
import io.nop.api.core.auth.IUserContext;
import io.nop.auth.core.login.AuthToken;
import io.nop.auth.core.login.IAuthTokenProvider;
import io.nop.commons.crypto.HashHelper;
import io.nop.commons.util.StringHelper;

import java.nio.charset.StandardCharsets;
import java.security.Key;

import static io.nop.auth.core.jwt.JwtHelper.ALG_HMAC_SHA256;
Expand Down Expand Up @@ -44,11 +40,9 @@ public void setAlgorithm(String algorithm) {
protected synchronized Key getSignKey() {
if (signKey == null) {
if (StringHelper.isEmpty(encKey)) {
SignatureAlgorithm alg = SignatureAlgorithm.forName(algorithm);
signKey = Keys.secretKeyFor(alg);
signKey = JwtHelper.hmacKey(StringHelper.generateUUID(), "nop");
} else {
signKey = Keys.hmacShaKeyFor(HashHelper.sha256(encKey.getBytes(StandardCharsets.UTF_8),
"nop".getBytes(StandardCharsets.UTF_8)));
signKey = JwtHelper.hmacKey(encKey, "nop");
}
}
return signKey;
Expand Down
180 changes: 103 additions & 77 deletions nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/jwt/JwtHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,113 +7,139 @@
*/
package io.nop.auth.core.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.Jws;
import io.jsonwebtoken.Locator;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.impl.DefaultJwtBuilder;
import io.jsonwebtoken.impl.DefaultJwtParserBuilder;
import io.jsonwebtoken.io.DeserializationException;
import io.jsonwebtoken.io.Deserializer;
import io.jsonwebtoken.io.SerializationException;
import io.jsonwebtoken.io.Serializer;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.JWSVerifier;
import com.nimbusds.jose.KeyLengthException;
import com.nimbusds.jose.crypto.MACSigner;
import com.nimbusds.jose.crypto.MACVerifier;
import com.nimbusds.jose.crypto.RSASSASigner;
import com.nimbusds.jose.crypto.RSASSAVerifier;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import io.nop.api.core.exceptions.NopException;
import io.nop.api.core.time.CoreMetrics;
import io.nop.api.core.util.Guard;
import io.nop.auth.api.AuthApiConstants;
import io.nop.auth.core.login.AuthToken;
import io.nop.commons.util.IoHelper;
import io.nop.core.lang.json.JsonTool;
import io.nop.commons.crypto.HashHelper;
import io.nop.commons.util.StringHelper;

import java.io.OutputStream;
import java.io.Reader;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Key;
import java.security.PrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

import static io.nop.auth.core.AuthCoreErrors.ARG_TOKEN;
import static io.nop.auth.core.AuthCoreErrors.ERR_JWT_INVALID_TOKEN;

public class JwtHelper {
public static final String ALG_HMAC_SHA256 = SignatureAlgorithm.HS256.getValue();


public static final String ALG_HMAC_SHA256 = "HS256";

public static AuthToken parseToken(Key key, String token) {
return parseToken(token, new Locator<Key>() {
@Override
public Key locate(Header header) {
return key;
}
});
return parseToken(token, k -> key);
}

public static AuthToken toAuthToken(String token, Claims claims) {
String sessionId = (String) claims.get(AuthApiConstants.JWT_CLAIMS_SID);
public static SecretKey hmacKey(String password, String salt) {
return new SecretKeySpec(HashHelper.sha256(password.getBytes(StringHelper.CHARSET_UTF8),
salt.getBytes(StandardCharsets.UTF_8)), ALG_HMAC_SHA256);
}

public static AuthToken toAuthToken(String token, JWTClaimsSet claims) {
String sessionId = (String) claims.getClaim(AuthApiConstants.JWT_CLAIMS_SID);
if (sessionId == null)
sessionId = claims.getId();
sessionId = claims.getJWTID();

String subject = claims.getSubject();
long expireAt = claims.getExpiration().getTime();
long expireAt = claims.getExpirationTime().getTime();

int seconds = (int) ((expireAt - claims.getIssuedAt().getTime()) / 1000);
String userName = (String) claims.get(AuthApiConstants.JWT_CLAIMS_USERNAME);
return new AuthToken(token, subject, userName, sessionId, expireAt, seconds, claims);
int seconds = (int) ((expireAt - claims.getIssueTime().getTime()) / 1000);
String userName = (String) claims.getClaim(AuthApiConstants.JWT_CLAIMS_USERNAME);
return new AuthToken(token, subject, userName, sessionId, expireAt, seconds, claims.getClaims());
}

public static AuthToken parseToken(String token, Locator<Key> keyLocator) {
public static AuthToken parseToken(String token, Function<String, Key> keyLocator) {
try {
Jws<Claims> jwt = new DefaultJwtParserBuilder().keyLocator(keyLocator)
.deserializeJsonWith(new Deserializer<Map<String, ?>>() {
@Override
public Map<String, ?> deserialize(byte[] bytes) throws DeserializationException {
return (Map<String, ?>) JsonTool.parse(new String(bytes, StandardCharsets.UTF_8));
}

@Override
public Map<String, ?> deserialize(Reader reader) throws DeserializationException {
try {
String str = IoHelper.readText(reader);
return (Map<String, ?>) JsonTool.parse(str);
} catch (Exception e) {
throw new DeserializationException("deserialize-fail", e);
}
}
}).build().parseClaimsJws(token);

Claims claims = jwt.getBody();
return toAuthToken(token, claims);
} catch (SecurityException e) {
throw new NopException(ERR_JWT_INVALID_TOKEN).param(ARG_TOKEN, token);
} catch (ExpiredJwtException e) {
throw new NopExpiredJwtException(e.getClaims()).authToken(toAuthToken(token, e.getClaims()));
SignedJWT jwt = SignedJWT.parse(token);
Key key = keyLocator.apply(jwt.getHeader().getKeyID());
JWSVerifier verifier = newVerifier(key);
boolean isVerified = jwt.verify(verifier);
JWTClaimsSet claims = jwt.getJWTClaimsSet();
if (!isVerified)
throw new NopException(ERR_JWT_INVALID_TOKEN).param(ARG_TOKEN, token);
AuthToken authToken = toAuthToken(token, claims);
if (authToken.isExpired())
throw new NopExpiredJwtException(claims.getClaims()).authToken(authToken);
return authToken;
} catch (NopException e) {
throw e;
} catch (Exception e) {
throw new NopException(ERR_JWT_INVALID_TOKEN, e).param(ARG_TOKEN, token);
}
}

public static SecretKey newHMACKey(String key) {
byte[] bytes = key.getBytes(StandardCharsets.UTF_8);
return new SecretKeySpec(bytes, ALG_HMAC_SHA256);
}

public static String genToken(Key key, String subject, String userName, String sessionId, long expireSeconds) {
Guard.notEmpty(sessionId, "sessionId");

long begin = CoreMetrics.currentTimeMillis();
Map<String, Object> claims = new HashMap<>();
claims.put(AuthApiConstants.JWT_CLAIMS_USERNAME, userName);
return new DefaultJwtBuilder().addClaims(claims).setId(sessionId).setSubject(subject).setIssuedAt(new Date(begin)).signWith(key)
.setExpiration(new Date(begin + expireSeconds * 1000L))
.serializeToJsonWith(new Serializer<>() {
@Override
public byte[] serialize(Map<String, ?> stringMap) throws SerializationException {
return JsonTool.stringify(stringMap).getBytes(StandardCharsets.UTF_8);
}

@Override
public void serialize(Map<String, ?> stringMap, OutputStream out) throws SerializationException {
try {
out.write(serialize(stringMap));
} catch (Exception e) {
throw new SerializationException("serialize-fail", e);
}
}
}).compact();
try {
JWSSigner signer = newSigner(key);

long begin = CoreMetrics.currentTimeMillis();
JWSHeader header = new JWSHeader(getSignAlgorithm(key));
JWTClaimsSet claimsSet = new JWTClaimsSet.Builder()
.issuer("nop")
.subject(subject)
.expirationTime(new Date(begin + expireSeconds * 1000L))
.issueTime(new Date(begin)).jwtID(sessionId)
.claim(AuthApiConstants.JWT_CLAIMS_USERNAME, userName).build();

// 创建JWT对象
SignedJWT signedJWT = new SignedJWT(header, claimsSet);

// 对JWT进行签名
signedJWT.sign(signer);

// 生成序列化的JWT
String serializedJWT = signedJWT.serialize();

return serializedJWT;
} catch (Exception e) {
throw NopException.adapt(e);
}
}

private static JWSAlgorithm getSignAlgorithm(Key key) {
if (key instanceof PrivateKey)
return JWSAlgorithm.RS256;
return JWSAlgorithm.HS256;
}

private static JWSSigner newSigner(Key key) throws KeyLengthException {
if (key instanceof PrivateKey)
return new RSASSASigner((PrivateKey) key);
if (key instanceof SecretKey)
return new MACSigner((SecretKey) key);
throw new IllegalArgumentException("nop.err.unsupported-key:" + key);
}

private static JWSVerifier newVerifier(Key key) throws JOSEException {
if (key instanceof RSAPublicKey)
return new RSASSAVerifier((RSAPublicKey) key);
if (key instanceof SecretKey)
return new MACVerifier((SecretKey) key);
throw new IllegalArgumentException("nop.err.unsupported-key:" + key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/
package io.nop.auth.core.login;

import io.nop.api.core.time.CoreMetrics;
import io.nop.api.core.util.Guard;

import java.util.Map;
Expand All @@ -25,7 +26,7 @@ public AuthToken(String token, String subject, String userName, String sessionId
long expireAt, int expireSeconds, Map<String, Object> claims) {
this.token = token;
this.subject = subject;
this.sessionId = Guard.notEmpty(sessionId,"sessionId");
this.sessionId = Guard.notEmpty(sessionId, "sessionId");
this.expireAt = expireAt;
this.userName = userName;
this.expireSeconds = expireSeconds;
Expand Down Expand Up @@ -67,4 +68,8 @@ public String getSessionId() {
public long getExpireAt() {
return expireAt;
}

public boolean isExpired() {
return expireAt < CoreMetrics.currentTimeMillis();
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
package io.nop.auth.core.jwt;

import io.nop.api.core.exceptions.NopException;
import io.nop.auth.core.login.AuthToken;
import org.junit.jupiter.api.Test;

import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;

import static io.nop.auth.core.AuthCoreErrors.ERR_JWT_INVALID_TOKEN;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;

public class TestJwtHelper {
@Test
Expand All @@ -19,4 +23,19 @@ public void testPublicKey() throws Exception {
AuthToken parsed = JwtHelper.parseToken(keyPair.getPublic(), token);
assertEquals("abc", parsed.getUserName());
}

@Test
public void testHMAC() {
Key key = JwtHelper.hmacKey("test", "nop");
String token = JwtHelper.genToken(key, "my", "abc", "2", 20000);
AuthToken parsed = JwtHelper.parseToken(key, token);
assertEquals("abc", parsed.getUserName());

try {
JwtHelper.parseToken(JwtHelper.hmacKey("invalid", "abc"), token);
fail();
} catch (NopException e) {
assertEquals(ERR_JWT_INVALID_TOKEN.getErrorCode(), e.getErrorCode());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
*/
package io.nop.auth.sso.login;

import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Locator;
import io.nop.api.core.auth.IUserContext;
import io.nop.api.core.convert.ConvertHelper;
import io.nop.api.core.exceptions.NopException;
Expand All @@ -35,7 +32,6 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.security.Key;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
Expand Down Expand Up @@ -211,12 +207,7 @@ public String generateVerifyCode(String verifySecret) {

@Override
public AuthToken parseAuthToken(String accessToken) {
return JwtHelper.parseToken(accessToken, new Locator<Key>() {
@Override
public Key locate(Header header) {
return keyLocator.getPublicKey(((JwsHeader) header).getKeyId());
}
});
return JwtHelper.parseToken(accessToken, keyId -> keyLocator.getPublicKey(keyId));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,6 @@
*/
package io.nop.auth.sso;

import io.jsonwebtoken.Header;
import io.jsonwebtoken.JwsHeader;
import io.jsonwebtoken.Locator;
import io.nop.api.core.exceptions.NopException;
import io.nop.api.core.json.JSON;
import io.nop.auth.core.jwt.JwtHelper;
Expand All @@ -21,8 +18,6 @@
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;

import java.security.Key;

import static io.nop.auth.core.AuthCoreErrors.ARG_CLAIMS;

@Disabled
Expand All @@ -34,12 +29,7 @@ public void testVerify() {

String token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICIxMEM3YTJkOUZJV0ZVanhzdjEyYjAxT2hPemFnb0t0WkNQNnl0UWJLbDVvIn0.eyJleHAiOjE2NzA2NDI1OTMsImlhdCI6MTY3MDY0MjI5MywianRpIjoiODRiNmEwMTYtZjFjZi00ZmUwLThlNjctOWU3OWYyNzZlMTZjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDQxL3JlYWxtcy9hcHAiLCJhdWQiOiJhY2NvdW50Iiwic3ViIjoiNmFkMzZiODItZWY2Yy00YjI1LWIwNDItMGM4ZjQ1MGY2MjVjIiwidHlwIjoiQmVhcmVyIiwiYXpwIjoidGVzdC1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiMjI2YzRlOGItNDA1ZC00OGMwLTkzN2EtM2M2MjAxMDhlNzE4IiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJhcHBVc2VyIiwib2ZmbGluZV9hY2Nlc3MiLCJ1bWFfYXV0aG9yaXphdGlvbiIsImRlZmF1bHQtcm9sZXMtYXBwIl19LCJyZXNvdXJjZV9hY2Nlc3MiOnsiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJlbWFpbCBwcm9maWxlIiwic2lkIjoiMjI2YzRlOGItNDA1ZC00OGMwLTkzN2EtM2M2MjAxMDhlNzE4IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoidGVzdCBBQkMiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0ZXN0MSIsImdpdmVuX25hbWUiOiJ0ZXN0IiwiZmFtaWx5X25hbWUiOiJBQkMifQ.A2tbYa6sEiCLQ-lgXCZfVpYQF-D9Y3jRVairwTOLozPT3aaiGZaBWU-hTZhWVNHW0nxJHYrUE9AfFVXRhUJ3bWy2sUj_gDTmNjWsG32-9DUn_j8UK1qQnh8S9FB5GGzj4-1FOtIcvQf4byBagUFGj6FzhYu09rLFwF1SZCFQ6SylkEGOuUKxAR_EPy6U5INhFGGf6Q___oN4L2J5uOmq7Baf1VWRe3KFNNPns4rI6xik_3b2ayOyFmk_0uB4SWEmFRgRPaZd62Pi_DhJe2-6FE7QlLtCnynS3aRDkS8Loo-HIOgCV0zH4TqMeGEY6UUDoz4q_CcqSK0BOxd_sASlYg";
try {
JwtHelper.parseToken(token, new Locator<Key>() {
@Override
public Key locate(Header header) {
return keyLocator.getPublicKey(((JwsHeader) header).getKeyId());
}
});
JwtHelper.parseToken(token, keyId -> keyLocator.getPublicKey(keyId));
} catch (NopException e) {
System.out.println(e.getParam(ARG_CLAIMS));
}
Expand Down

0 comments on commit a06eccf

Please sign in to comment.