diff --git a/src/main/java/com/fanruan/api/security/JwtKit.java b/src/main/java/com/fanruan/api/security/JwtKit.java new file mode 100644 index 0000000..d90cf8c --- /dev/null +++ b/src/main/java/com/fanruan/api/security/JwtKit.java @@ -0,0 +1,213 @@ +package com.fanruan.api.security; + +import com.fanruan.api.log.LogKit; +import com.fr.cert.token.JwtBuilder; +import com.fr.cert.token.Jwts; +import com.fr.cert.token.SignatureAlgorithm; +import com.fr.cert.token.SignatureException; +import com.fr.cert.token.impl.DefaultClaims; +import com.fr.security.KeySecretSeedConfig; +import com.fr.security.SecurityToolbox; + +import java.io.IOException; +import java.security.Key; +import java.util.Date; +import java.util.Map; + +/** + * @author richie + * @version 10.0 + * Created by richie on 2019-09-03 + * 用于生成和解析基于JWT的token + */ +public class JwtKit { + + private static final String JWT_ID = "jwt"; + private static final String ISSUER = "fanruan"; + private static final SignatureAlgorithm DEFAULT_ALGORITHM = SignatureAlgorithm.HS256; + + /** + * 创建完整token + * + * @param issuer jwt签发者 + * @param subject jwt所面向的用户 + * @param audience 接收jwt的一方 + * @param expiration jwt的过期时间,这个过期时间必须要大于签发时间 + * @param notBeforeTime 定义在什么时间之前,该jwt都是不可用的. + * @param issuerAtTime jwt的签发时间 + * @param jwtId jwt的唯一身份标识,主要用来作为一次性token,从而回避重放攻击。 + * @param algorithm 加密算法 + * @param secretKey 秘钥 + * @return 签名后的token + */ + public static String createJWT(String issuer, String subject, String audience, Date expiration, Date notBeforeTime, + Date issuerAtTime, String jwtId, SignatureAlgorithm algorithm, Key secretKey) { + JwtBuilder builder = Jwts.builder() + .setIssuer(issuer) + .setSubject(subject) + .setAudience(audience) + .setExpiration(expiration) + .setNotBefore(notBeforeTime) + .setIssuedAt(issuerAtTime) + .setId(jwtId) + .signWith(algorithm, secretKey); + return builder.compact(); + } + + /** + * 创建默认的token,用不超时 + * + * @param subject 主题 + * @return token + */ + public static String createDefaultJWT(String subject) { + JwtBuilder builder = Jwts.builder() + .setIssuer(ISSUER) + .setIssuedAt(new Date()) + .setSubject(subject) + .setId(JWT_ID) + .signWith(DEFAULT_ALGORITHM, getKeyBytes()); + return builder.compact(); + } + + /** + * 创建默认的token + * + * @param subject 主题 + * @param description 描述 + * @return token + */ + public static String createDefaultJWT(String subject, String description) { + JwtBuilder builder = Jwts.builder() + .setIssuer(ISSUER) + .setIssuedAt(new Date()) + .setSubject(subject) + .setDescription(description) + .setId(JWT_ID) + .signWith(DEFAULT_ALGORITHM, getKeyBytes()); + return builder.compact(); + } + + /** + * 创建默认token + * + * @param subject 主题 + * @param description 描述 + * @param timeout 超时时长 + * @return token + */ + public static String createDefaultJWT(String subject, String description, long timeout) { + Date currentTime = new Date(); + Date timeoutTime = new Date(currentTime.getTime() + timeout); + JwtBuilder builder = Jwts.builder() + .setIssuer(ISSUER) + .setIssuedAt(currentTime) + .setExpiration(timeoutTime) + .setSubject(subject) + .setDescription(description) + .setId(JWT_ID) + .signWith(DEFAULT_ALGORITHM, getKeyBytes()); + return builder.compact(); + } + + /** + * 创建有超时时限的token + * + * @param subject 主题 + * @param timeout 超时时长 + * @return token + */ + public static String createDefaultJWT(String subject, long timeout) { + Date currentTime = new Date(); + Date timeoutTime = new Date(currentTime.getTime() + timeout); + JwtBuilder builder = Jwts.builder() + .setIssuer(ISSUER) + .setIssuedAt(currentTime) + .setExpiration(timeoutTime) + .setSubject(subject) + .setId(JWT_ID) + .signWith(DEFAULT_ALGORITHM, getKeyBytes()); + return builder.compact(); + } + + /** + * 解析token + * + * @param jwt token字符串 + * @return 签名通过之后的结果集 + * @throws SignatureException 签名错误异常 + */ + public static Map parseJWT(String jwt) { + try { + return Jwts.parser().setSigningKey(getKeyBytes()).parseClaimsJws(jwt).getBody(); + } catch (SignatureException e) { + LogKit.warn(e.getMessage()); + } + return new DefaultClaims(); + } + + + /** + * 根据自定义的key,创建token + * + * @param signatureKey 秘钥 + * @param claims token携带的信息 + * @return token + */ + public static String createVariedJWT(String signatureKey, Map claims) { + JwtBuilder builder = Jwts.builder() + .setIssuer(ISSUER) + .setIssuedAt(new Date()) + .setClaims(claims) + .setId(JWT_ID) + .signWith(DEFAULT_ALGORITHM, signatureKey); + return builder.compact(); + } + + /** + * 根据自定义的key,创建token + * + * @param signatureKey 秘钥 + * @param timeout 有效期 + * @param claims token携带的信息 + * @return token + */ + public static String createVariedJWT(String signatureKey, long timeout, Map claims) { + Date currentTime = new Date(); + Date timeoutTime = new Date(currentTime.getTime() + timeout); + JwtBuilder builder = Jwts.builder() + .setIssuer(ISSUER) + .setIssuedAt(new Date()) + .setExpiration(timeoutTime) + .setClaims(claims) + .setId(JWT_ID) + .signWith(DEFAULT_ALGORITHM, signatureKey); + return builder.compact(); + } + + /** + * 解析token, 先对key进行utf-8解码 + * + * @param jwt token字符串 + * @param signatureKey 秘钥 + * @return 签名通过之后的结果集 + * @throws SignatureException 签名错误异常 + */ + public static Map parseJWT(String jwt, String signatureKey) { + try { + return Jwts.parser().setSigningKey(signatureKey).parseClaimsJws(jwt).getBody(); + } catch (SignatureException e) { + LogKit.warn(e.getMessage()); + } + return new DefaultClaims(); + } + + private static byte[] getKeyBytes() { + try { + return SecurityToolbox.base642Byte(KeySecretSeedConfig.getInstance().getTrustSeed()); + } catch (IOException e) { + LogKit.error("key secret seed base64 decode error"); + } + return new byte[0]; + } +} diff --git a/src/test/java/com/fanruan/api/security/JwtKitTest.java b/src/test/java/com/fanruan/api/security/JwtKitTest.java new file mode 100644 index 0000000..abfd086 --- /dev/null +++ b/src/test/java/com/fanruan/api/security/JwtKitTest.java @@ -0,0 +1,77 @@ +package com.fanruan.api.security; + +import com.fanruan.api.Prepare; +import com.fr.cert.token.JwtBuilder; +import com.fr.cert.token.Jwts; +import com.fr.cert.token.SignatureAlgorithm; +import com.fr.security.SecurityToolbox; +import com.fr.stable.CodeUtils; +import org.junit.Assert; +import org.junit.Test; + +import java.io.UnsupportedEncodingException; +import java.util.Calendar; +import java.util.Date; +import java.util.Map; + +/** + * @author richie + * @version 10.0 + * Created by richie on 2019-09-03 + */ +public class JwtKitTest extends Prepare { + + @Test + public void testCreateDefaultJWT() throws Exception { + String tokenCN = JwtKit.createDefaultJWT("你好,我是中国人"); + Map claims = JwtKit.parseJWT(tokenCN); + Assert.assertEquals("你好,我是中国人", CodeUtils.cjkDecode(String.valueOf(claims.get("sub")))); + + String tokenEN = JwtKit.createDefaultJWT("Hello, world"); + Map claims2 = JwtKit.parseJWT(tokenEN); + Assert.assertEquals("Hello, world", CodeUtils.cjkDecode(String.valueOf(claims2.get("sub")))); + } + + @Test + public void testClaims() throws Exception { + String token = JwtKit.createDefaultJWT("千万", "我是千万的爹"); + Map claims2 = JwtKit.parseJWT(token); + String text = CodeUtils.cjkDecode(String.valueOf(claims2.get("description"))); + Assert.assertEquals("我是千万的爹", text); + } + + @Test + public void test1() throws UnsupportedEncodingException { + String token = createToken().compact(); + Assert.assertEquals(Jwts.parser().setSigningKey("abc=啊").parseClaimsJws(token).getBody().getSubject(), "hello.cpt"); + + token = createToken().signWithBase64SecretKey(SignatureAlgorithm.HS256, SecurityToolbox.byte2Base64("abc=啊".getBytes())).compact(); + Assert.assertEquals(Jwts.parser() + .setBase64SigningKey(SecurityToolbox.byte2Base64("abc=啊".getBytes())) + .parseClaimsJws(token).getBody().getSubject(), "hello.cpt"); + + token = createToken() + .signWith(SignatureAlgorithm.HS256, "abc=啊".getBytes("GBK")) + .compact(); + Assert.assertEquals("hello.cpt", Jwts.parser().setSigningKey("abc=啊".getBytes("GBK")).parseClaimsJws(token).getBody().getSubject()); + } + + private JwtBuilder createToken() { + SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256; + + Calendar calendar = Calendar.getInstance(); + calendar.set(2019, Calendar.FEBRUARY, 20, 18, 0, 0); + Date currentTime = calendar.getTime(); + calendar.set(2029, Calendar.FEBRUARY, 20, 18, 0, 0); + Date expirationTime = calendar.getTime(); + return Jwts.builder() + .setHeaderParam("typ", "JWT") + .setIssuer("fanruan") + .setSubject("hello.cpt") + .setExpiration(expirationTime) + .setIssuedAt(currentTime) + .setId("01") + .signWith(signatureAlgorithm, "abc=啊"); + } + +} \ No newline at end of file