前言
关于单点登录(SSO)网上也有很多帖子,但是以我个人的角度来看,大多都是一知半解,看完一圈也还是懵的,而且基本上都是理念,实实在在的去实践的基本上没有,而且内容基本上就是一模一样,这篇文章给大家也是提供一个单点登录的解决方案,至少看完和实践完之后,你知道大概怎么去实现,我这种实现方案也只适用于两个服务之间的通信登录,但是我认为哪有那么多的大型服务通信,又不是人人项目皆淘宝
非对称性加密,双向加密(RSA+AES)
本文我们用到了一种构思,在不传递密钥的情况下,完成解密,这被称为"Diffie-Hellman密钥交换算法",也正是因为这个算法的产生,人类终于可以实现非对称加密了,举个例子:
1.B要先生成两把密钥(公钥和私钥)。公钥是公开的,任何人都可以获得,私钥则是保密的。
2.A获取B的公钥,然后用它对信息加密。
3.B得到加密后的信息,用私钥解密。
理论上如果公钥加密的信息只有私钥解得开,那么只要私钥不泄漏,通信就是安全的。
觉得文字不理解的,我提供了一个流程图:
本文单点登录就是基于这个流程实现,下面我们来实操非对称性加密,上代码
AES加密工具类:
public class AESUtil {
//生成AES秘钥,然后Base64编码
public static String genKeyAES() throws Exception{
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey key = keyGen.generateKey();
String base64Str = byte2Base64(key.getEncoded());
return base64Str;
}
//将Base64编码后的AES秘钥转换成SecretKey对象
public static SecretKey loadKeyAES(String base64Key) throws Exception{
byte[] bytes = base642Byte(base64Key);
SecretKeySpec key = new SecretKeySpec(bytes, "AES");
return key;
}
//加密
public static byte[] encryptAES(byte[] source, SecretKey key) throws Exception{
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.ENCRYPT_MODE, key);
return cipher.doFinal(source);
}
//解密
public static byte[] decryptAES(byte[] source, SecretKey key) throws Exception{
Cipher cipher = Cipher.getInstance("AES");
cipher.init(Cipher.DECRYPT_MODE, key);
return cipher.doFinal(source);
}
//字节数组转Base64编码
public static String byte2Base64(byte[] bytes){
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(bytes);
}
//Base64编码转字节数组
public static byte[] base642Byte(String base64Key) throws IOException{
BASE64Decoder decoder = new BASE64Decoder();
return decoder.decodeBuffer(base64Key);
}
}
RSA加密工具类:
public class RSAUtil {
//生成秘钥对
protected static KeyPair getKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
KeyPair keyPair = keyPairGenerator.generateKeyPair();
return keyPair;
}
//获取公钥(Base64编码)
protected static String getPublicKey(KeyPair keyPair){
PublicKey publicKey = keyPair.getPublic();
byte[] bytes = publicKey.getEncoded();
return byte2Base64(bytes);
}
//获取私钥(Base64编码)
protected static String getPrivateKey(KeyPair keyPair){
PrivateKey privateKey = keyPair.getPrivate();
byte[] bytes = privateKey.getEncoded();
return byte2Base64(bytes);
}
//将Base64编码后的公钥转换成PublicKey对象
public static PublicKey string2PublicKey(String pubStr) throws Exception{
byte[] keyBytes = base642Byte(pubStr);
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PublicKey publicKey = keyFactory.generatePublic(keySpec);
return publicKey;
}
//将Base64编码后的私钥转换成PrivateKey对象
public static PrivateKey string2PrivateKey(String priStr) throws Exception{
byte[] keyBytes = base642Byte(priStr);
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
PrivateKey privateKey = keyFactory.generatePrivate(keySpec);
return privateKey;
}
//公钥加密
public static byte[] publicEncrypt(byte[] content, PublicKey publicKey) throws Exception{
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] bytes = cipher.doFinal(content);
return bytes;
}
//私钥解密
public static byte[] privateDecrypt(byte[] content, PrivateKey privateKey) throws Exception{
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] bytes = cipher.doFinal(content);
return bytes;
}
//字节数组转Base64编码
public static String byte2Base64(byte[] bytes){
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(bytes);
}
//Base64编码转字节数组
public static byte[] base642Byte(String base64Key) throws IOException{
BASE64Decoder decoder = new BASE64Decoder();
return decoder.decodeBuffer(base64Key);
}
}
加密解密工具类:
public class ServerEncryptUtil {
public static String encrypt(String content,String secretKey) throws Exception{
//将Base64编码后的Server公钥转换成PublicKey对象
PublicKey serverPublicKey = RSAUtil.string2PublicKey(secretKey);//客户端需要知道服务端的公钥信息
//每次都随机生成AES秘钥
String aesKeyStr = AESUtil.genKeyAES();
SecretKey aesKey = AESUtil.loadKeyAES(aesKeyStr);
//用Server公钥加密AES秘钥
byte[] encryptAesKey = RSAUtil.publicEncrypt(aesKeyStr.getBytes(), serverPublicKey);
//用AES秘钥加密请求内容
byte[] encryptRequest = AESUtil.encryptAES(content.getBytes(), aesKey);
JSONObject result = new JSONObject();
result.put("aesk", RSAUtil.byte2Base64(encryptAesKey).replaceAll("\r\n", ""));
result.put("content", RSAUtil.byte2Base64(encryptRequest).replaceAll("\r\n", ""));
return result.toString();
}
//服务器解密APP的请求内容
public static String decrypt(String content,String secretKe) throws Exception{
JSONObject result = JSONObject.parseObject(content);
String encryptAesKeyStr = (String) result.get("aesk");
String encryptContent = (String) result.get("content");
//将Base64编码后的Server私钥转换成PrivateKey对象
PrivateKey serverPrivateKey = RSAUtil.string2PrivateKey(secretKe);
//用Server私钥解密AES秘钥
byte[] aesKeyBytes = RSAUtil.privateDecrypt(RSAUtil.base642Byte(encryptAesKeyStr), serverPrivateKey);
SecretKey aesKey = AESUtil.loadKeyAES(new String(aesKeyBytes));
//用AES秘钥解密请求内容
byte[] request = AESUtil.decryptAES(RSAUtil.base642Byte(encryptContent), aesKey);
return new String(request);
}
}
Test:
公钥私钥信息
//项目A公钥(Base64编码)
public final static String A_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApkixN3Dc6BLzb/V74VpxRXsSIu9AabGmK4xfcPiIqub0JS99a+P6XAOGuiMT2W4p1C8U9MZDRgHjUOrKGcc5ve9uT+U90LiAgwG58YdrklOTwlGvo6Xh4HQLRXMNoGsn6jLGdOV1RIVfWQ5EWfEB1+5v86QarLyfLIJ4ujVQfafEJ4dCwmoNSJk8xqVBAW9tDZlNOOgaZPJuEXVIFEEjIZCkFkFxkomwVNdp79Xewrj0mCybCDVy6Mcx3jOxY0gGwbGgS3YQxDbOpqYna8rcmf6CVJ2GA75sCU61Y8Of244CR5Rwkspbr1Pbf4UNSbVbpxzI08z1jrJvCVYWNQLMwwIDAQAB";
//项目A私钥(Base64编码)
public final static String A_PRIVATE_KEY = "MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCmSLE3cNzoEvNv9XvhWnFFexIi70BpsaYrjF9w+Iiq5vQlL31r4/pcA4a6IxPZbinULxT0xkNGAeNQ6soZxzm9725P5T3QuICDAbnxh2uSU5PCUa+jpeHgdAtFcw2gayfqMsZ05XVEhV9ZDkRZ8QHX7m/zpBqsvJ8sgni6NVB9p8Qnh0LCag1ImTzGpUEBb20NmU046Bpk8m4RdUgUQSMhkKQWQXGSibBU12nv1d7CuPSYLJsINXLoxzHeM7FjSAbBsaBLdhDENs6mpidrytyZ/oJUnYYDvmwJTrVjw5/bjgJHlHCSyluvU9t/hQ1JtVunHMjTzPWOsm8JVhY1AszDAgMBAAECggEAFTOHhO4a/GwOJeRC20TQ1G8QrOucZt2DtmG7eYf2xPOVhXg8oZj7vuekMe9vBHYLV0Z5gYwV38M13IdTJV5FenYgtocgDpC3sfxyXN1LVejaGhiYMGFiH2AsX7p/rkh7Wl0G+LiY7xeiRJSRGnakKYf5NjNiQ0v5b49jHTrW/G5G579xHOC0EXJezgKKflD+XXhpBjVCMfzkY8tK+ZT5XnUf2U+I7o9u6G3Y3wky2ajR0GyBigLGwI0mVqyA4ie3ggSiMxotfcDwP1HpV/fK//DPpUgBiUgspFt1NtVcNDxeQlqw3UYakfUn5j3inA4C+fZwAe7VNwZkW2uaCl7HIQKBgQDXE49dGomUrvIXeXt/muUw5sy81sufYxUdGm9slfr8DhyEr0KKdrnKFdLtRPdr0mHnp0mlihE+rQQyQg67qys3c4C4TcLq6PHmtkd6K0+rUDYM2BNltjgd42UA86tchZ5Qu1u7dqxmnExYUb/kXbNk/euOcv1iNkdVGq61AZVhfwKBgQDF7G+4q92AQTjYk1dT0Us5oAkGj4GRs5XMeJB+0jhWRhyNvgXRuKkhMgEjbSogAnywQfD93xDw2nZw2TVFqSS5cGCR+KnwTkQcA2vE2IbduDQRTT9JUQas3EJhZ1Cp+xyXfAQorCQW5TSNfb0LskTnV+aJaM/Yxb32NzPlXksuvQKBgDn9FhxeOVYTTUazBG9FTiI/OFh5+XDCAEFWjVBTp9Yp39qOfnxiwnkQJUy/2Y4CrU8ONbciYL/rWkRKtzo2TnKm+7+1h6ZapE42O1NfNh3UhJ417BTyanL0ipkVGdDaXfMacQM8XgNUhOkTMY/bC7FhHQ/NRTAjvlvd09kN0j71AoGAHKaoWZRPgTRv1TInDxQaDqJzDAcUG5JimfHOAP3Pd/W4RnB+iShxG0QQ1B8GXRHfGOjCyQ1Ud3k4cgePZaEhltKEuDzF5Op/g4qfPCSYCVqT9vk2sxdOnxFXbqA1FhYqwmcKdxTMOKA/ZkgQaLQKs26PCc8pX1josc617Xsj6QUCgYA3sHC9I8fan7FAneJvE1Fsc1ZATMuo/yNA8WlASg+OPgeCgIv5AHAvkKqj3ZYapnafmHJj6L6jUL9dFwKqMncAdVOYI/V0oVoa8wgCHQ6b4S55dWMYhd3hSg8+8mUdxw6oOFL7rpRYduX1KxKc8Y4OuB7RsQgdKlT/sEBDEFdS/w==";
//项目B公钥
public final static String B_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAlNdMH/batOwYmSnTDTJhs2dpEgh9sINPzoCH4cKUw9iesR9FYC4ElRTif5EiL3jVrFvHU0piR11MGrpZigslG76fFL7ukf2uFoqVc2klrS+t5yswwcZGvk64CsOEjzcXLdsbAjkvKQyjaiJ+7SyQdT6GsFYsn+VNFCooCfDqUUbxc1qbdM+XFVhTGFPgFHrVXs3rfh6XaloKmiZb5xhUB7S+M4nLvKPHPSrNXh5e3TVQmM03kBcTpQgkjYegNcGsNU2V2UzK6gCFibxCE5BZr5m7QYwUDRivZftmeax9AtXOkliBgIb64gpQ98sa11+EZUhgCSFPkobtd9LRRG/QSQIDAQAB";
//项目B私钥
public final static String B_PRIVATE_KEY = "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCU10wf9tq07BiZKdMNMmGzZ2kSCH2wg0/OgIfhwpTD2J6xH0VgLgSVFOJ/kSIveNWsW8dTSmJHXUwaulmKCyUbvp8Uvu6R/a4WipVzaSWtL63nKzDBxka+TrgKw4SPNxct2xsCOS8pDKNqIn7tLJB1PoawViyf5U0UKigJ8OpRRvFzWpt0z5cVWFMYU+AUetVezet+HpdqWgqaJlvnGFQHtL4zicu8o8c9Ks1eHl7dNVCYzTeQFxOlCCSNh6A1waw1TZXZTMrqAIWJvEITkFmvmbtBjBQNGK9l+2Z5rH0C1c6SWIGAhvriClD3yxrXX4RlSGAJIU+Shu130tFEb9BJAgMBAAECggEANUCyo90WcxTl2Cb2tAoKmr2tAh63mafhreietU+BGnEulgCyOa89RHmscCflK0b4bCqKLmq2rwHacNWro5BJPpv9HtcRVHqHBtmejyRXrpcYiVUSpMHkHcLcZj19+B+l02pJR4peLTmwXhDpiMIhDVHJrt2mKa9I3KYYnTXtIBbNfMFs2MCq8hXS2HOH619p9sTukNVezv8lXI224ZXTNxuY9wxTVhRJqtSy17IARaJbfjZlxTWUVxIMxlZtkUeIPd+qeT7bKsp6TGg6iZO3wfV21LKiiV6XFF6wBTeWr3alCXrfIW94ufDg2OLlKDtyAQTCNAcUPv4lFZhCkCypgQKBgQDXpHLi08+WcpTR+nqeYhPxdlapGavvyxKlkj3OxTkXOrZuW3I9y3NIVwUeDpyy1qdIfWYzg7lPqDW005kE5f+hUb3fxeTrbSOyQ5DJlC0DjI413/SIPuUxq4TdozTr7bL8tJr8VEZ50dUkhbMBd4BV99z8PWAGwztGNEnSKpqELQKBgQCwslsrjCcBCt4Ap7E/9kYqgBqeTP9Hld5GPvzdUfI5xvrRQaGHc9uDtmOMab5gCrfxkQgeYuLFVZRLTOTeexyDL/yfiBSWc6FKKLU4ixxmaGzPxe6ieIGR9Z46L+jk8QFn6zEnlRPvn4TMnK0gU+Y7TiocKq6XFuPYqJ57H0HCDQKBgQCq+a5Ffl8nEC5C1nCGR4k8d/F6HeqfM8nTEUKEQQdlDZ47nbHUoQ0EnEpobHl9qof0B8kSqywwplP+zY6TvbCTPXZoiEVbDKuy2bNgwGV4XOccfDcsF4ItgarU+GQrc8noZ2JDEqbWS7LNRFyLmy5b+yxxqlAKzJygqzCkvDbK+QKBgDngEZCv9U6YGqMoShxjNBRTpWRYX/04j3d+xiBP6QEXtau4zYmqXypDIVQqhpq6qAlNsdUSGqj8IPiRFR3yQSKPIzW1wtiMwzWhI8qDdifs6Ecd084PnpEKXGs+qg/jCMza8ly+rar9GuhSITrHgE/IiG51ZH5ElxiuEkrNsCI5AoGAewKq7xXxOYdTH1oJk4fLn1aCMXqQnZfO5t/TBdCdIX34dFYSK/vdSMx07At5FuiNO0NsMlATOB4HOiOszepJygvHxeyNMwBm5+3emgAd+Uyrt68ZFR8QahFFlS5mt4V53Bjip8XufIp7mPBjHeYkMTwHJTuLQRrYD6v8X7VS7sM=";
加密内容
String content = "{\"code\":\"encrypt\",\"name\":\"admin\",\"id\":1}";
@Test
void contextLoads()throws Exception{
// 项目A >> 使用项目B 公钥加密
String encryptDataA = ServerEncryptUtil.encrypt(content,B_PUBLIC_KEY);
System.out.println("项目A加密后的数据:"+encryptDataA);
// 项目B >> 使用自己的私钥解密
String decryptDataA = ServerEncryptUtil.decrypt(encryptDataA,B_PRIVATE_KEY);
System.out.println("拿到项目A加密的数据,解密:"+decryptDataA);
// 项目B >> 使用项目A 公钥加密
String encryptDataB = ServerEncryptUtil.encrypt(content,A_PUBLIC_KEY);
System.out.println("项目A加密后的数据:"+encryptDataB);
// 项目A >> 使用自己的私钥解密
String decryptDataB = ServerEncryptUtil.decrypt(encryptDataB,A_PRIVATE_KEY);
System.out.println("拿到项目A加密的数据,解密:"+decryptDataB);
}
看到这,相信你应该对流程有一定的了解了,下面说说本文的单点登录解决方案.
单点登录(SSO)解决方案
首先我们先来理一理,一个平台需要从另一个平台,用户是关键,他们一定得有一个共同点,比如Name or Id
如图:
关于这个可以在A项目中做一个用户联动,在A项目中新增用户时,得到B项目中的所有用户数据,可在A项目中选择,筛选A平台已经存在的用户,这样就存在关联关系,二个平台的用户名是相同的,后续就能通过用户名进行登录.
下面直接贴张图来讲解全流程
很多细节图文贴不下,比如某些需求是,跳转到具体页面,但是这个用户没有功能权限,所以平台B在模拟登入时,需要对这些进行处理,双方交互是需要互相制定规则的,传输格式、验证格式等等,具体代码就没贴出来(你懂得),相信有这个思路,应该不难,有问题欢迎留言