- 引入 nimbus-jose-jwt
<dependency> <groupId>com.nimbusds</groupId> <artifactId>nimbus-jose-jwt</artifactId> <version>9.23</version> </dependency>
该包可以使用rsa算法进行jwt加密
- 使用openssl生成密钥
生成RSA加密私钥
openssl genrsa -aes256 -passout pass:123456 -out rsa_aes_private.key 1024
使用RSA私钥生成公钥
openssl rsa -in rsa_aes_private.key -pubout -out rsa_public.key
因为使用 openssl 生成的密钥是 pkcs1格式的密钥,java默认只能使用 pkcs8 格式的密钥,所以需要进行pkcs1到pkcs8转换的转换
openssl pkcs8 -topk8 -in rsa_aes_private.key -inform pem -out pkcs8_rsa_private.key -outform pem
- 在application.yml增加配置
certificate: useKid: k1 certificates: - kid: k1 privateKey: | -----BEGIN ENCRYPTED PRIVATE KEY----- MIIC3TBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQImUIM57O4TH4CAggA MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBDJkNNh8w3fTcQjKP3A6oVHBIIC gC7Nuk2xzW2+CHycQ5InCB76u/C1L6jTKC8M7XgAhacM7WfQHHfJFjMsN9J94vwd 8rDlTPE+nNHmLw386fBFtwDTLC8cuALmcvzH+qxYVXD5ygYGRrclUulOiRwiZ5f4 TjdmHApP15SbglG/B4tV5ERa2nudccXDdg7fAJsqlaZsqLGnPxYBhbUwE428DFjn MkyA2N06AQzyU7aFYeuKGSS5D04HRAyZ/SBVUg4lBXI34TAZGG447LhHxXuorBgH N/JJpHGgQyURmH43HI4bpiPnXHbHTRNYUehQGUI/oNWAZugFLFrXnYl120+wkca+ U8zQu/23uhy+4iCuy5SnNxdOKvSNpBTIh2BEbEm8nmHvbcfg5pcgExb/g7rnFWPP ryNdR42Vm5Wp4xrzFT71WwWSUVkC1N037QH0K09BTcJi/XV6qxxOtLSfq2uzTJQ2 vIs9VGgKy9IAlIa6aur1Th/cpbQ+dz9ld1ZYWHgBxw8hFxZkbu+qZUeAo6c1pHZI rwPvYj06BK5R2xkrMYcJaEasJz0PrvxMzk9+0qSJNdT+y9nzaxLN+/ypldm3DarH ZiyG5QC/TJTWkckM0AIdZujLIs8j3IQc4Sp21zrjFeMBzVd3CJBGgaFAV3o6CaAp 9OJYytj/cNAy1jEfTl7AbaRAbteBbSFQdAsSGqgC0u+JpyncH1r3YoM720HIB7Xa pLyOCA3zWcbKPwHTBlH1x7+ppXy/zvdAwmUlTydD1aaa2i4bv2+ctdjWhMW77Nxp TE3y5Kim8CSW885PgIRxKocU0DgeOEtPmuOxxMjbouSF18mSmZP8NmoiVMpf/cS9 9c4FlRjxWiPoRY+EMWk81cU= -----END ENCRYPTED PRIVATE KEY----- publicKey: | -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC1iRRYvvbs7cPGfpJuND0ArkzE MTzeDG0OuTxcdl+A6yOB6jRRe/9fqRRZJSZOcrvr8I2YKs9n+DGo2nGUrmcRlav5 s77g7jGJmOW6ZvNBBU4g+w9gt4tQPCBcyjSym4HUWz04FT03cfa6Yn7f2xHwjw7j 3DXGZnp+qx3fox5ezwIDAQAB -----END PUBLIC KEY----- - kid: k2 privateKey: | -----BEGIN ENCRYPTED PRIVATE KEY----- MIIC3TBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI1wWdC35fHiMCAggA MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAEqBBAd8fAg67XxjRww/6zZOYiUBIIC gFELh2xUblnGRTGQQ7VaRayAMHd1hNNc2P6Pe0ujXxzf1qF/iE6Rhpk2Ag9I0ky+ siZL8lbh3QzOyQUch+BBYBuUeToSN8jQ+5rTR6Vr8eaC6jRKIZCBdxbbN4r3hSkJ nG+BZzjArO8U0T+QTQ3cp3rpvOsRj5xR09nku3HnaY9vNqWAWd5mjJ+b8lRnh64X qAAmnBUcB+5xuU/BMGpF8k2X/qNVRM/YZzP1/mWO31kw1VMjSP6zdrRVQuoUv2X8 bUlv6kBBaaTPK4qkR6y3I7QMiGxA1UL8qTPtxpOlaWR+3ofIBPk1N35k1sFLRgc/ UhK7q0KphZ75BZGu1PpOu8T0p2fx0BnIQUZKg7+g5oqzNKKo9RlVx87wrJO1Urur VJS+FzK1HoGqLtsco2lzeLqmXqCbC+MXygenJJxOZOKW9/LWQxMP3e72/N0LzdqW 0lbu9f4w86OL7Qwk4zVxCFp9bwDAvf5ZIvfnLI1yl8q7cfny6QKGU+nwENOWn3Px I7Dv7vNIs9K6f6Is8XPnEnBIRi2eUwVHVqeu46DLIIzS+YIvsDfkHp3h7fBh7hYW Iytia/QfKmpyeZp5GCJZM0pLP0qDLspXdm0oBI+WdnbF2i9YUADGdQw1CpgskYnf wBNDdGmkUR4aTDdwvdzPacRDF3ZZg/AaiysuWRIjEsEabwmpi4CmmLiwxwnO5uDn 4iiLC16PUPK+sIBskYd9UgOMyC+qKbzajCZVyRZDpNPZF+jZE+ND3TOtaWHimP9M B3dHj6F+/rHHko9kWsc/V/RaXm//14g8SBn3Hc+vR/IAz9SajMJDRVEmQnWhkfz9 IdLgYdHew8l+HuvjDCXtmBM= -----END ENCRYPTED PRIVATE KEY----- publicKey: | -----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6pyUTOpppX5JOtMbT6CjS4/U8 ZV6Tw9kViTwrILr/AwgUFaaxptaCx+waiQgBJfTE2VVXwXipXpxfYjtgKqVqx4yR mVAxGNZjKIkSzAxjo7er2vP084WG/Sh958MXW8A/K7pDkSALusP8YTveEgtTKfln feBZh04XQmRYhPPCuQIDAQAB -----END PUBLIC KEY-----
以上配置的私钥是进行了转换后的密钥
- 解析配置文件
定义配置对象
package com.olive.jwt; import java.io.Serializable; public class CertVO implements Serializable{ private String kid; private String privateKey; private String publicKey; //省略getter setter }
对应配置对象
package com.olive.jwt; import java.util.List; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; @Component @ConfigurationProperties(prefix="certificate") public class CertificateConfig { private String useKid; private List<CertVO> certificates; //省略getter setter }
- 定义 jwt payload 对象
package com.olive.jwt; import java.io.Serializable; public class JwtPayloadVO implements Serializable{ private String jti; //jwt token id private String tid; //companyId private String cid; //appId private String iss; //token使用方 private String sub; //token主题 格式 tid:cid:uid private Long exp; //过期时间 毫秒 private Long iat; //创建时间 毫秒 private String uid; //user id //省略getter setter }
- 进行jwt 生成与验证
package com.olive.jwt; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import java.text.ParseException; import java.util.Base64; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import com.nimbusds.jose.*; @Component public class CertificateProvider { @Autowired private CertificateConfig certificateConfig; private RSAPrivateKey rsaPrivateKey = null; private Map<String, RSASSAVerifier> verifiers = new HashMap<>(); @PostConstruct public void init() { rsaPrivateKey = this.getPrivateKey(); } public RSAPrivateKey getPrivateKey() { if (rsaPrivateKey != null) { return rsaPrivateKey; } String use = certificateConfig.getUseKid(); if (StringUtils.isEmpty(use)) { System.out.println("certificate kid is required"); return null; } List<CertVO> certVOs = certificateConfig.getCertificates(); if (certVOs==null && certVOs.size()==0) { System.out.println("certificate is required"); return null; } try { for (CertVO certVO : certVOs) { if (use.equals(certVO.getKid())) { // 加载私钥 rsaPrivateKey = this.loadRSARSAPrivateKey(certVO.getPrivateKey()); } RSAPublicKey publicKey = loadRSAPublicKey(certVO.getPublicKey()); verifiers.put(certVO.getKid(), new RSASSAVerifier(publicKey)); } } catch (Exception e) { e.printStackTrace(); } if (rsaPrivateKey != null) { return rsaPrivateKey; } else { System.out.println("getPrivateKey certificate kid is required,certificate kid is required"); return null; } } /** * 加载公钥 * * @param keyStr 公钥字符串 * @return 公钥实体 * @throws NoSuchAlgorithmException KeyFactory中无该算法实现 * @throws InvalidKeySpecException 密钥无法识别 */ private RSAPublicKey loadRSAPublicKey(String keyStr) throws NoSuchAlgorithmException, InvalidKeySpecException { byte[] clear = publicKeyStrToBytes(keyStr); X509EncodedKeySpec keySpec = new X509EncodedKeySpec(clear); KeyFactory fact = KeyFactory.getInstance("RSA"); return (RSAPublicKey) fact.generatePublic(keySpec); } private RSAPrivateKey loadRSARSAPrivateKey(String keyStr) throws Exception { String begin = "-----BEGIN PRIVATE KEY-----"; String end = "-----END PRIVATE KEY-----"; String key = keyStr.replace(begin, "").replace(end, "").replaceAll("\\s", ""); PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(key)); KeyFactory kf = KeyFactory.getInstance("RSA"); return (RSAPrivateKey) kf.generatePrivate(spec); } /** * 公钥 字符串转换成二进制 * @param keyStr 密钥字符串 * @return 密钥/公钥 二进制 */ private byte[] publicKeyStrToBytes(String keyStr) { String begin = "-----BEGIN PUBLIC KEY-----"; String end = "-----END PUBLIC KEY-----"; String key = keyStr.replace(begin, "").replace(end, "").replaceAll("\\s", ""); return Base64.getDecoder().decode(key); } public String generateAccessToken(JwtPayloadVO jwtPayloadVO) { Map<String, Object> playloadMap = new HashMap<>(); playloadMap.put("jti", jwtPayloadVO.getJti()); playloadMap.put("tid", jwtPayloadVO.getTid()); playloadMap.put("cid", jwtPayloadVO.getCid()); playloadMap.put("iss", jwtPayloadVO.getIss()); playloadMap.put("sub", jwtPayloadVO.getSub()); playloadMap.put("exp", jwtPayloadVO.getExp()); playloadMap.put("iat", jwtPayloadVO.getIat()); if (StringUtils.hasLength(jwtPayloadVO.getUid())) { playloadMap.put("uid", jwtPayloadVO.getUid()); } try { // 创建JWS头,设置签名算法和类型 JWSHeader header = new JWSHeader.Builder(JWSAlgorithm.RS256).keyID(certificateConfig.getUseKid()) .type(JOSEObjectType.JWT).build(); JWTClaimsSet claimsSet = JWTClaimsSet.parse(playloadMap); // 创建RSA签名器 JWSSigner signer = new RSASSASigner(rsaPrivateKey, true); SignedJWT signedJWT = new SignedJWT(header, claimsSet); signedJWT.sign(signer); return signedJWT.serialize(); } catch (Exception e) { e.printStackTrace(); } return null; } public JWTClaimsSet verify(String token) throws ParseException, JOSEException { SignedJWT jwt = SignedJWT.parse(token); JWSHeader jwtHeader = jwt.getHeader(); String keyID = jwtHeader.getKeyID(); RSASSAVerifier verifier = verifiers.get(keyID); if (verifier == null) { System.out.println("jwt verify error: kid " + keyID + " mismatch RSASSAVerifier"); return null; } boolean verify = jwt.verify(verifier); if (!verify) { System.out.println("jwt verify fail, invalid token: " + token); return null; } return jwt.getJWTClaimsSet(); } }