diff --git a/nop-auth/nop-auth-core/pom.xml b/nop-auth/nop-auth-core/pom.xml index 8e3fd294a..8edbe0844 100644 --- a/nop-auth/nop-auth-core/pom.xml +++ b/nop-auth/nop-auth-core/pom.xml @@ -13,9 +13,14 @@ + + + + + - io.jsonwebtoken - jjwt-impl + com.nimbusds + nimbus-jose-jwt diff --git a/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/jwt/JwtAuthTokenProvider.java b/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/jwt/JwtAuthTokenProvider.java index ca5e65954..c0ea5d1e7 100644 --- a/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/jwt/JwtAuthTokenProvider.java +++ b/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/jwt/JwtAuthTokenProvider.java @@ -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; @@ -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; diff --git a/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/jwt/JwtHelper.java b/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/jwt/JwtHelper.java index 8fc0311c1..bf37e8256 100644 --- a/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/jwt/JwtHelper.java +++ b/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/jwt/JwtHelper.java @@ -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() { - @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 keyLocator) { + public static AuthToken parseToken(String token, Function keyLocator) { try { - Jws jwt = new DefaultJwtParserBuilder().keyLocator(keyLocator) - .deserializeJsonWith(new Deserializer>() { - @Override - public Map deserialize(byte[] bytes) throws DeserializationException { - return (Map) JsonTool.parse(new String(bytes, StandardCharsets.UTF_8)); - } - - @Override - public Map deserialize(Reader reader) throws DeserializationException { - try { - String str = IoHelper.readText(reader); - return (Map) 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 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 stringMap) throws SerializationException { - return JsonTool.stringify(stringMap).getBytes(StandardCharsets.UTF_8); - } - - @Override - public void serialize(Map 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); } } diff --git a/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/login/AuthToken.java b/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/login/AuthToken.java index 755af0d73..fdda60987 100644 --- a/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/login/AuthToken.java +++ b/nop-auth/nop-auth-core/src/main/java/io/nop/auth/core/login/AuthToken.java @@ -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; @@ -25,7 +26,7 @@ public AuthToken(String token, String subject, String userName, String sessionId long expireAt, int expireSeconds, Map 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; @@ -67,4 +68,8 @@ public String getSessionId() { public long getExpireAt() { return expireAt; } + + public boolean isExpired() { + return expireAt < CoreMetrics.currentTimeMillis(); + } } diff --git a/nop-auth/nop-auth-core/src/test/java/io/nop/auth/core/jwt/TestJwtHelper.java b/nop-auth/nop-auth-core/src/test/java/io/nop/auth/core/jwt/TestJwtHelper.java index 55e42f0b1..cf9de9f74 100644 --- a/nop-auth/nop-auth-core/src/test/java/io/nop/auth/core/jwt/TestJwtHelper.java +++ b/nop-auth/nop-auth-core/src/test/java/io/nop/auth/core/jwt/TestJwtHelper.java @@ -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 @@ -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()); + } + } } diff --git a/nop-auth/nop-auth-sso/src/main/java/io/nop/auth/sso/login/OAuthLoginServiceImpl.java b/nop-auth/nop-auth-sso/src/main/java/io/nop/auth/sso/login/OAuthLoginServiceImpl.java index 1892b0caa..3cffcf477 100644 --- a/nop-auth/nop-auth-sso/src/main/java/io/nop/auth/sso/login/OAuthLoginServiceImpl.java +++ b/nop-auth/nop-auth-sso/src/main/java/io/nop/auth/sso/login/OAuthLoginServiceImpl.java @@ -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; @@ -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; @@ -211,12 +207,7 @@ public String generateVerifyCode(String verifySecret) { @Override public AuthToken parseAuthToken(String accessToken) { - return JwtHelper.parseToken(accessToken, new Locator() { - @Override - public Key locate(Header header) { - return keyLocator.getPublicKey(((JwsHeader) header).getKeyId()); - } - }); + return JwtHelper.parseToken(accessToken, keyId -> keyLocator.getPublicKey(keyId)); } @Override diff --git a/nop-auth/nop-auth-sso/src/test/java/io/nop/auth/sso/TestJwtHelper.java b/nop-auth/nop-auth-sso/src/test/java/io/nop/auth/sso/TestJwtHelper.java index caa1f3958..5ede8766f 100644 --- a/nop-auth/nop-auth-sso/src/test/java/io/nop/auth/sso/TestJwtHelper.java +++ b/nop-auth/nop-auth-sso/src/test/java/io/nop/auth/sso/TestJwtHelper.java @@ -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; @@ -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 @@ -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() { - @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)); }