传统的资源服务授权流程如下:
传统授权方法的问题是用户每次请求资源服务,资源服务都需要携带令牌访问认证服务去校验令牌的合法性,并根据令牌获取用户的相关信息,性能低下。
为了避免每次资源服务器反复校验令牌的合法性,我们可以利用公钥私钥完成对令牌的加密和解密。公钥私钥授权流程如下:
实际上,认证服务器生成一对公钥和私钥,私钥留给自己,公钥发给其他资源服务器。在发放令牌时,先利用MD5之类的摘要算法生成令牌摘要,并通过私钥对改摘要进行签名(加密)。当客户端携带令牌访问资源时,取下签名,即利用公钥进行解密得到令牌的摘要。再通过采用MD5算法生成令牌摘要,最后,判断两个摘要是否相同,若相同,则令牌有效,否则,令牌无效。
生成密钥证书
使用JDK自带工具keytool生成ssl证书:
keytool -genkeypair -alias changgou -keypass changgou -keyalg RSA -keypass changgou -storepass changgou -keystore C:/Users/Administrator/Desktop/changgou.jks
导出公钥
这里参考大佬的实现
import sun.misc.BASE64Encoder;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.security.*;
import java.security.cert.Certificate;
/**
* 从证书中导出公钥私钥
*/
public class ExportCert {
//导出证书 base64格式
public static void exportCert(KeyStore keystore, String alias, String exportFile) throws Exception {
Certificate cert = keystore.getCertificate(alias);
BASE64Encoder encoder = new BASE64Encoder();
String encoded = encoder.encode(cert.getEncoded());
FileWriter fw = new FileWriter(exportFile);
fw.write("-----BEGIN CERTIFICATE-----");
fw.write(encoded);
fw.write("-----END CERTIFICATE-----");
fw.close();
}
//得到KeyPair
public static KeyPair getKeyPair(KeyStore keystore, String alias, char[] password) {
try {
Key key = keystore.getKey(alias, password);
if (key instanceof PrivateKey) {
Certificate cert = keystore.getCertificate(alias);
PublicKey publicKey = cert.getPublicKey();
return new KeyPair(publicKey, (PrivateKey) key);
}
} catch (UnrecoverableKeyException e) {
} catch (NoSuchAlgorithmException e) {
} catch (KeyStoreException e) {
}
return null;
}
//导出私钥
public static void exportPrivateKey(PrivateKey privateKey,String exportFile) throws Exception {
BASE64Encoder encoder = new BASE64Encoder();
String encoded = encoder.encode(privateKey.getEncoded());
FileWriter fw = new FileWriter(exportFile);
fw.write("-----BEGIN PRIVATE KEY-----");
fw.write(encoded);
fw.write("-----END PRIVATE KEY-----");
fw.close();
}
//导出公钥
public static void exportPublicKey(PublicKey publicKey,String exportFile) throws Exception {
BASE64Encoder encoder = new BASE64Encoder();
String encoded = encoder.encode(publicKey.getEncoded());
FileWriter fw = new FileWriter(exportFile);
fw.write("-----BEGIN PUBLIC KEY-----");
fw.write(encoded);
fw.write("-----END PUBLIC KEY-----");
fw.close();
}
public static void main(String args[]) throws Exception {
String keyStoreType = "JKS";
String keystoreFile = "C:/Users/Administrator/Desktop/changgou.jks";
String password = "changgou";
String alias = "changgou";
KeyStore keystore = KeyStore.getInstance(keyStoreType);
keystore.load(new FileInputStream(new File(keystoreFile)), password.toCharArray());
String exportCertFile = "C:/Users/Administrator/Desktop/cms.cer";
String exportPrivateFile = "C:/Users/Administrator/Desktop/cmsPrivateKey.txt";
String exportPublicFile = "C:/Users/Administrator/Desktop/cmsPublicKey.txt";
ExportCert.exportCert(keystore, alias, exportCertFile);
KeyPair keyPair = ExportCert.getKeyPair(keystore, alias, password.toCharArray());
ExportCert.exportPrivateKey(keyPair.getPrivate(), exportPrivateFile);
ExportCert.exportPublicKey(keyPair.getPublic(), exportPublicFile);
System.out.println("OK");
}
}
JWT签名算法中,常采用的两种加密算法:
签名实际上是一个加密的过程,生成一段标识(也是JWT的一部分)作为接收方验证信息是否被篡改的依据。
RS256 (采用SHA-256 的 RSA 签名) 是一种非对称算法, 它使用公共/私钥对: 标识提供方采用私钥生成签名, JWT 的使用方获取公钥以验证签名。由于公钥 (与私钥相比) 不需要保护, 因此大多数标识提供方使其易于使用方获取和使用 (通常通过一个元数据URL)。
另一方面, HS256 (带有 SHA-256 的 HMAC 是一种对称算法, 双方之间仅共享一个 密钥。由于使用相同的密钥生成签名和验证签名, 因此必须注意确保密钥不被泄密。
在开发应用的时候启用JWT,使用RS256更加安全,你可以控制谁能使用什么类型的密钥。生成令牌的实现:
/**
* 利用私钥创建jwt令牌
*/
@Test
public void testGenerateRSAJwt() {
// 加载证书
ClassPathResource resource = new ClassPathResource("changgou.jks");
// 读取证书
KeyStoreKeyFactory keyStoreKeyFactory = new KeyStoreKeyFactory(resource, "changgou".toCharArray());
// 获取私钥
KeyPair keyPair = keyStoreKeyFactory.getKeyPair("changgou", "changgou".toCharArray());
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 创建令牌,使用RSA算法进行加盐加密
Map<String, Object> payload = new HashMap<>();
payload.put("name", "jack");
payload.put("age", 24);
Jwt jwt = JwtHelper.encode(JSON.toJSONString(payload), new RsaSigner(privateKey));
System.out.println(jwt.getEncoded());
}
利用公钥解析Jwt令牌。
/**
* 利用公钥解析jwt令牌
*/
@Test
public void testCheckRSAJwt(){
String jwtStr = "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoiamFjayIsImFnZSI6MjR9.UTt8SEfzL3T7Xr_zj2ipWdt_ROiw2QOahJVAc5qSEb1keNAGcnKOu2Z-_Oe1kD1fZFIY3Y17QVGIjqONFa54BkXmeb2d6Hfq6pW2lD3ht_1iyVdZ2FPkDHVzyYMvUMgH1V85hJagUsGK0NyWBVIaODUj83Kpdky2DsXqUmkCrNQu03g-kKV3LLQ514EPpkcjAo2CrxbFHhQjOpPjQAd2C_FdJllVnQBgXD8z-h1VBOkhe1N3QH8AZx9PnKZMLEtKsN6U5cSbbj0vf5DKaYiyf5P_5DJ_OdkRcd5XvTZjGmFZA8FL5IYOtax4-ILPbdaMsYh2CYDct2n02crxpThkjw";
String publicKey = "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvFsEiaLvij9C1Mz+oyAmt47whAaRkRu/8kePM+X8760UGU0RMwGti6Z9y3LQ0RvK6I0brXmbGB/RsN38PVnhcP8ZfxGUH26kX0RK+tlrxcrG+HkPYOH4XPAL8Q1lu1n9x3tLcIPxq8ZZtuIyKYEmoLKyMsvTviG5flTpDprT25unWgE4md1kthRWXOnfWHATVY7Y/r4obiOL1mS5bEa/iNKotQNnvIAKtjBM4RlIDWMa6dmz+lHtLtqDD2LF1qwoiSIHI75LQZ/CNYaHCfZSxtOydpNKq8eb1/PGiLNolD4La2zf0/1dlcr5mkesV570NxRmU1tFm8Zd3MZlZmyv9QIDAQAB-----END PUBLIC KEY-----";
Jwt jwt = JwtHelper.decodeAndVerify(jwtStr, new RsaVerifier(publicKey));
System.out.println(jwt.getClaims());
}