两种模式
简介
前面章节介绍的加密模式中,有 common
和 session-key
,先说结论, common
更方便, session-key
更安全。
common模式
commom
模式,需要前后端都保存一份相同的密钥或密钥对进行加解密。(接口加密不限于前后端,也可以是两台服务器通信,下面以前后端举例)
举例:
- SecureApi 设置加密算法为
AES
,生成一个密钥,可以等待组件自动生成或使用CipherUtils
生成,也可以使用在线网站生成,然后保存在SecureApiPropertiesConfig
的key
字段中,前端也保存此密钥,前端向后端发送使用该密钥加密后的数据,后端收到后用该密钥解密。缺点:
- 密钥存在前端有泄露风险(客户端被黑、被F12调试)
- 密钥无法动态生成,需前后端商量好各自写死(明文密钥在网络中传输危险性大于存储在前端,需要接口加密的业务也绝对不会允许你在网络中传输明文密钥)
优点:
- 实现简单,无需关心密钥协商问题
- 性能高,密钥在前后端各自保存一份,无需额外的网络通信来协商密钥
session-key模式
session-key
模式需要同时使用 RSA
和 对称加密算法
配合实现,由前端为每次请求生成新的 对称加密密钥
(也可以每隔一段时间更新密钥),然后使用此密钥加密数据,再使用 RSA
公钥加密此密钥,把加密后的数据和密钥都传输给后端,后端使用 RSA
私钥解密得到 会话密钥
,使用 会话密钥
解密数据,返回值就比较简单了,只需要使用 会话密钥
加密返回值给前端,前端使用 会话密钥
解密就可以了。
举例:
- 后端使用 SecureApi 设置加密模式为
session-key
,指定加密算法为RSA
,指定会话密钥加密算法为AES
,生成RSA
密钥对,保存在SecureApiPropertiesConfig
的publicKey
和privateKey
字段中- 第一次通信:前端请求后端的
RSA
公钥- 第二次通信:前端使用后端
RSA
公钥加密任意数据,把加密后的数据传输给后端,后端使用自己的RSA
私钥解密,如果能解密成功,告诉前端校验成功,如果解密失败,除了代码原因,说明第一次通信公钥传输过程中被篡改,需要重新生成RSA
密钥对重新验证- 验证成功后,后续的的接口加密方式就是:前端为请求生成
会话密钥
,使用会话密钥
加密数据,再使用后端RSA
公钥加密会话密钥
,把加密后的数据和密钥都传输给后端,后端可以写一个拦截器,把会话密钥
保存到SecureApiPropertiesConfig
的key
字段中(无需自行解密),SecureApi 会自动使用RSA
私钥解密得到会话密钥
,后续 SecureApi 也会自动使用会话密钥
解密和加密数据了- 你可以为每个请求生成一个不同的会话密钥,也可以定时更新密钥,后端的 RSA 密钥对也可以定期重新更新,以增强安全性
缺点:
- 可以看出,流程比较复杂
- 每次请求生成新密钥并且后端使用
RSA
私钥解密密钥显然会损失更多的性能优点:
- 安全、安全、还是 TMD 安全
借助 SecureApi 实现 session-key
模式只需要下面简简单单的两个文件,代码示例:
@Bean
public SecureApiPropertiesConfig secureApiPropertiesConfig() {
SecureApiPropertiesConfig secureApiPropertiesConfig = new SecureApiPropertiesConfig();
secureApiPropertiesConfig.setEnabled(true);
// 设置模式为 session-key
secureApiPropertiesConfig.setMode(SecureApiProperties.Mode.SESSION_KEY);
// 设置会话密钥算法
secureApiPropertiesConfig.setSessionKeyCipherAlgorithm(CipherAlgorithmEnum.AES_ECB_PKCS5);
// session-key 模式必须设置加密算法为 RSA
secureApiPropertiesConfig.setCipherAlgorithmEnum(CipherAlgorithmEnum.RSA_ECB_SHA256);
// 密钥可以不设置,组件会自动生成一个,并打印在控制台,如果需要手动生成,只需要使用组件提供的CipherUtils
CipherUtils cipherUtils = new CipherUtils(CipherAlgorithmEnum.RSA_ECB_SHA256);
// getRandomRsaKeyPair("1")可传入seed参数,在测试时可用于控制每次生成的密钥相同
RsaKeyPair randomRsaKeyPair = cipherUtils.getRandomRsaKeyPair();
// 把生成的密钥对设置到secureApiPropertiesConfig
secureApiPropertiesConfig.setPublicKey(randomRsaKeyPair.getPublicKey());
secureApiPropertiesConfig.setPrivateKey(randomRsaKeyPair.getPrivateKey());
return secureApiPropertiesConfig;
}
在拦截器中把每次请求的 会话密钥
设置到 secureApiPropertiesConfig
的 key
值即可,后续操作全部由 SecureApi 自动完成
@Component
public class SecureKeyInterceptor implements HandlerInterceptor {
private static final Logger log = LoggerFactory.getLogger(SecureKeyInterceptor.class);
@Autowired
private SecureApiPropertiesConfig secureApiPropertiesConfig;
@Override
public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) {
// 这里只需要在拦截器中修改secureApiPropertiesConfig的key值即可,后续操作全部由 SecureApi 自动完成
if (secureApiPropertiesConfig.getMode().equals(SecureApiProperties.Mode.SESSION_KEY)) {
String sessionKey = request.getHeader("sessionKey");
log.info("前端传来会话密钥:{}", sessionKey);
secureApiPropertiesConfig.setKey(sessionKey);
}
return true;
}
}