最近项目上由于集成需求,需要实现单点登录,经过考虑后选择了JWT,RS256公私玥加密方式实现,搜索后发现基于RS256的实现不太多,大多基于HS256对称加密,加密解密用同一SecretKey,泄漏后安全方面彻底崩坏,有些提到RS256的都是一些支离破碎的代码,没有什么参考价值。经过google,加上自己的整理,关键demo代码整理如下:
import junit.framework.TestCase; import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.jose4j.jws.AlgorithmIdentifiers; import org.jose4j.jws.JsonWebSignature; import org.jose4j.jwt.JwtClaims; import org.jose4j.jwt.consumer.JwtConsumer; import org.jose4j.jwt.consumer.JwtConsumerBuilder; import org.jose4j.lang.JoseException; import sun.security.util.DerInputStream; import sun.security.util.DerValue; import java.io.FileInputStream; import java.io.IOException; import java.math.BigInteger; import java.security.*; import java.security.spec.InvalidKeySpecException; import java.security.spec.RSAPrivateCrtKeySpec; import java.security.spec.X509EncodedKeySpec; import java.text.SimpleDateFormat; import java.util.UUID; public class LocalTest extends TestCase{ public void testCreateToken() throws IOException { System.out.println(createToken()); } public void testVerifyToken() throws Exception { String token = createToken(); System.out.println(token); JwtClaims jwtClaims = verifyToken(token); System.out.println(jwtClaims.getClaimValue("name")); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(jwtClaims.getIssuedAt().getValueInMillis())); System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(jwtClaims.getExpirationTime().getValueInMillis())); } /** * 生成jwt,SHA256加密 * @return * @throws IOException */ private String createToken() throws IOException { PrivateKey privateKey = getPrivateKey(getPrivateKeyString()); final JwtClaims claims = new JwtClaims(); claims.setClaim("name", "jack"); claims.setSubject("[email protected]"); claims.setAudience("test");//用于验证签名是否合法,验证方必须包含这些内容才验证通过 claims.setExpirationTimeMinutesInTheFuture(60*24*30); claims.setIssuedAtToNow(); // Generate the payload final JsonWebSignature jws = new JsonWebSignature(); jws.setAlgorithmHeaderValue(AlgorithmIdentifiers.RSA_USING_SHA256); jws.setPayload(claims.toJson()); jws.setKeyIdHeaderValue(UUID.randomUUID().toString()); // Sign using the private key jws.setKey(privateKey); try { return jws.getCompactSerialization(); } catch (JoseException e) { return null; } } /** * 验证jwt * @param token * @return * @throws Exception */ private JwtClaims verifyToken(String token) throws Exception { try { PublicKey publicKey = getPublicKey(getPEMPublicKeyString()); JwtConsumer jwtConsumer = new JwtConsumerBuilder() .setRequireExpirationTime() .setVerificationKey(publicKey) .setExpectedAudience("test")//用于验证签名是否合法,可以设置多个,且可设置必须存在项,如果jwt中不包含这些内容则不通过 .build(); return jwtConsumer.processToClaims(token); } catch (Exception e) { throw new RuntimeException(e); } } private String getPrivateKeyString() throws IOException { // 生成方法:安装openssl,执行 openssl genrsa -out private.pem 2048 return IOUtils.toString(new FileInputStream("/home/leen/.ssh/private.pem")); } private String getPEMPublicKeyString() throws IOException { // 导出公钥方法:生成私钥(private.pem)后,执行 openssl rsa -in private.pem -outform PEM -pubout -out public.pem return IOUtils.toString(new FileInputStream("/home/leen/.ssh/public.pem")); } /** * 获取PublicKey对象 * @param publicKeyBase64 * @return * @throws NoSuchAlgorithmException * @throws InvalidKeySpecException */ private PublicKey getPublicKey(String publicKeyBase64) throws NoSuchAlgorithmException, InvalidKeySpecException { String pem = publicKeyBase64 .replaceAll("\\-*BEGIN.*KEY\\-*", "") .replaceAll("\\-*END.*KEY\\-*", ""); java.security.Security.addProvider( new org.bouncycastle.jce.provider.BouncyCastleProvider() ); X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(Base64.decodeBase64(pem)); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); PublicKey publicKey = keyFactory.generatePublic(pubKeySpec); System.out.println(publicKey); return publicKey; } /** * 获取PrivateKey对象 * @param privateKeyBase64 * @return */ private PrivateKey getPrivateKey(String privateKeyBase64) { String privKeyPEM = privateKeyBase64 .replaceAll("\\-*BEGIN.*KEY\\-*", "") .replaceAll("\\-*END.*KEY\\-*", ""); // Base64 decode the data byte[] encoded = Base64.decodeBase64(privKeyPEM); try { DerInputStream derReader = new DerInputStream(encoded); DerValue[] seq = derReader.getSequence(0); if (seq.length < 9) { throw new GeneralSecurityException("Could not read private key"); } // skip version seq[0]; BigInteger modulus = seq[1].getBigInteger(); BigInteger publicExp = seq[2].getBigInteger(); BigInteger privateExp = seq[3].getBigInteger(); BigInteger primeP = seq[4].getBigInteger(); BigInteger primeQ = seq[5].getBigInteger(); BigInteger expP = seq[6].getBigInteger(); BigInteger expQ = seq[7].getBigInteger(); BigInteger crtCoeff = seq[8].getBigInteger(); RSAPrivateCrtKeySpec keySpec = new RSAPrivateCrtKeySpec(modulus, publicExp, privateExp, primeP, primeQ, expP, expQ, crtCoeff); KeyFactory factory = KeyFactory.getInstance("RSA"); return factory.generatePrivate(keySpec); } catch (IOException | GeneralSecurityException e) { e.printStackTrace(); } return null; } }
pom依赖:
<dependency> <groupId>org.bitbucket.b_c</groupId> <artifactId>jose4j</artifactId> <version>0.6.3</version> </dependency>