最近看有小伙伴在弄项目使用密文传输数据,然后我看了下,集成这个大佬的依赖不错,但是我发现他的注解不支持加在类上,我进行了一点改动后完美支持。
项目demo地址 https://github.com/songshijun1995/spring-boot-RSA
大致说下不引入依赖,直接集成的步骤。
- 配置yml文件,公钥和密钥可以用其他软件生成
rsa:
encrypt:
open: true # 是否开启加密 true or false
showLog: true # 是否打印加解密log true or false
# RSA公钥 软件生成
publicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCiquj4HR5VBtSJiKTyr18/vLaO5fJb6L/BrawFR4u+QCP3SF6SEd4pwp5Ev2R5pS34YGU00XFCejPgDfsSnRITanvv5a5wnNLFJMaz7ACxCrZjmW3z+ZppR/I19mJsaTOOopChUMJNBdvxUO13suwYad1Nhk5fmAJn0xQJgeWR/QIDAQAB
# RSA私钥 软件生成
privateKey: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAKKq6PgdHlUG1ImIpPKvXz+8to7l8lvov8GtrAVHi75AI/dIXpIR3inCnkS/ZHmlLfhgZTTRcUJ6M+AN+xKdEhNqe+/lrnCc0sUkxrPsALEKtmOZbfP5mmlH8jX2YmxpM46ikKFQwk0F2/FQ7Xey7Bhp3U2GTl+YAmfTFAmB5ZH9AgMBAAECgYANiipQFKRksWfZds08AgrslDmh1VQCAHKNnXYXDmh8Unxr5dMxV1llonRoBoJHec9EwElMRy6lOOS+fotqdjZ90yTGTl5wPLX7Yan+OfeOJCkDdPhIurcPPi0RYXKSRags/SX3mvkhauxGIUY0xPDJJDJ/Kaby7HrT9PQELSbV5QJBAN3zio37hJxH5G7T2WMNbxA5sB4Ey5rOc+f7c5fARFs9QC2vbIyHP73pR9igZ4Pm8OiMcuWLrK0kvZfuCuFruBcCQQC7ny61wekt6bfCCfK0mtZt+SEEINqDsXICIyzgEShfURWoIPKlUQseqhtLg7vkPErkBoA8I3y9ozejwV8zIT8LAkEAhAaCvMKIt43sTCCoh0tObZBjOvgPRR7Zw3zH3dT41G0y5/oZz94EBKvnmOyRptyRIUOqdPEI3lWkkeN/hWfWMQJBAKOXVUwHqsBss9vNfsD47RTwj1ghKUaAlu7EKuGoNDJ/6ckyCUAZ3P88xRXf5BlKdOZDwNYu/xn+0YnIFrDnQScCQFobGRWnckBrm/GZ59/9vl5H1Z2MSbuDHgJBuxe6cq5RUhxFMW/KJ4hgfeiwp7xfpt162yMGlcqPNHyCTf6bVnA=
- 新建两个工具类
package com.rsa.util;
import org.apache.commons.codec.binary.Base64;
public class Base64Util {
public Base64Util() {
}
public static byte[] decode(String base64) throws Exception {
return Base64.decodeBase64(base64);
}
public static String encode(byte[] bytes) throws Exception {
return new String(Base64.encodeBase64(bytes));
}
}
package com.rsa.util;
import javax.crypto.Cipher;
import java.io.ByteArrayOutputStream;
import java.security.Key;
import java.security.KeyFactory;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
public class RSAUtil {
public static final String KEY_ALGORITHM = "RSA";
private static final int MAX_ENCRYPT_BLOCK = 117;
private static final int MAX_DECRYPT_BLOCK = 256;
public RSAUtil() {
}
public static byte[] encrypt(byte[] data, String publicKey) throws Exception {
byte[] keyBytes = Base64Util.decode(publicKey);
X509EncodedKeySpec x509KeySpec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key publicK = keyFactory.generatePublic(x509KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.ENCRYPT_MODE, publicK);
int inputLen = data.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段加密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_ENCRYPT_BLOCK) {
cache = cipher.doFinal(data, offSet, MAX_ENCRYPT_BLOCK);
} else {
cache = cipher.doFinal(data, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_ENCRYPT_BLOCK;
}
byte[] encryptedData = out.toByteArray();
out.close();
return encryptedData;
}
public static byte[] decrypt(byte[] text, String privateKey) throws Exception {
byte[] keyBytes = Base64Util.decode(privateKey);
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
Key privateK = keyFactory.generatePrivate(pkcs8KeySpec);
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateK);
int inputLen = text.length;
ByteArrayOutputStream out = new ByteArrayOutputStream();
int offSet = 0;
byte[] cache;
int i = 0;
// 对数据分段解密
while (inputLen - offSet > 0) {
if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
cache = cipher.doFinal(text, offSet, MAX_DECRYPT_BLOCK);
} else {
cache = cipher.doFinal(text, offSet, inputLen - offSet);
}
out.write(cache, 0, cache.length);
i++;
offSet = i * MAX_DECRYPT_BLOCK;
}
byte[] decryptedData = out.toByteArray();
out.close();
return decryptedData;
}
}
- 新建SecretKeyConfig配置文件,加载yml里的配置
package com.rsa.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
@Data
@Configuration
@ConfigurationProperties(prefix = "rsa.encrypt")
public class SecretKeyConfig {
private String privateKey;
private String publicKey;
private String charset = "UTF-8";
private boolean open = true;
private boolean showLog = false;
}
- 添加三个注解,EnableSecurity、Decrypt、Encrypt
package com.rsa.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import com.rsa.advice.EncryptRequestBodyAdvice;
import com.rsa.advice.EncryptResponseBodyAdvice;
import com.rsa.config.SecretKeyConfig;
import org.springframework.context.annotation.Import;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Import({SecretKeyConfig.class, EncryptResponseBodyAdvice.class, EncryptRequestBodyAdvice.class})
public @interface EnableSecurity {
}
package com.rsa.annotation;
import java.lang.annotation.*;
//加密注解
@Target({ElementType.TYPE,ElementType.METHOD}) // 可以作用在类上和方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时起作用
@Documented
public @interface Encrypt {
}
package com.rsa.annotation;
import java.lang.annotation.*;
//解密注解
@Target({ElementType.TYPE,ElementType.METHOD}) // 可以作用在类上和方法上
@Retention(RetentionPolicy.RUNTIME) // 运行时起作用
@Documented
public @interface Decrypt {
}
- 新建DecryptHttpInputMessage类
package com.rsa.advice;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.stream.Collectors;
import com.rsa.util.Base64Util;
import com.rsa.util.RSAUtil;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpInputMessage;
public class DecryptHttpInputMessage implements HttpInputMessage {
private final HttpHeaders headers;
private final InputStream body;
public DecryptHttpInputMessage(HttpInputMessage inputMessage, String privateKey, String charset, boolean showLog) throws Exception {
if (StringUtils.isEmpty(privateKey)) {
throw new IllegalArgumentException("privateKey is null");
} else {
this.headers = inputMessage.getHeaders();
String content = (String)(new BufferedReader(new InputStreamReader(inputMessage.getBody()))).lines().collect(Collectors.joining(System.lineSeparator()));
String decryptBody;
Logger log = LoggerFactory.getLogger(this.getClass());
if (content.startsWith("{")) {
log.info("Unencrypted without decryption:{}", content);
decryptBody = content;
} else {
StringBuilder json = new StringBuilder();
content = content.replaceAll(" ", "+");
if (!StringUtils.isEmpty(content)) {
String[] contents = content.split("\\|");
String[] var9 = contents;
int var10 = contents.length;
for(int var11 = 0; var11 < var10; ++var11) {
String value = var9[var11];
value = new String(RSAUtil.decrypt(Base64Util.decode(value), privateKey), charset);
json.append(value);
}
}
decryptBody = json.toString();
if (showLog) {
log.info("Encrypted data received:{},After decryption:{}", content, decryptBody);
}
}
this.body = new ByteArrayInputStream(decryptBody.getBytes());
}
}
public InputStream getBody() {
return this.body;
}
public HttpHeaders getHeaders() {
return this.headers;
}
}
- 新建EncryptRequestBodyAdvice类
package com.rsa.advice;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.Objects;
import com.rsa.annotation.Decrypt;
import com.rsa.annotation.Encrypt;
import com.rsa.config.SecretKeyConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.RequestBodyAdvice;
@ControllerAdvice
public class EncryptRequestBodyAdvice implements RequestBodyAdvice {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private boolean encrypt;
@Autowired
private SecretKeyConfig secretKeyConfig;
public EncryptRequestBodyAdvice() {
}
public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
Annotation[] annotations = methodParameter.getDeclaringClass().getAnnotations();
if (annotations.length > 0 && this.secretKeyConfig.isOpen()) {
for (Annotation annotation : annotations) {
if (annotation instanceof Encrypt) {
return this.encrypt = true;
}
}
}
return this.encrypt = Objects.requireNonNull(methodParameter.getMethod()).isAnnotationPresent(Decrypt.class) && this.secretKeyConfig.isOpen();
}
public Object handleEmptyBody(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
public HttpInputMessage beforeBodyRead(HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
if (this.encrypt) {
try {
return new DecryptHttpInputMessage(inputMessage, this.secretKeyConfig.getPrivateKey(), this.secretKeyConfig.getCharset(), this.secretKeyConfig.isShowLog());
} catch (Exception var6) {
this.log.error("Decryption failed", var6);
}
}
return inputMessage;
}
public Object afterBodyRead(Object body, HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) {
return body;
}
}
- 新建EncryptResponseBodyAdvice类
package com.rsa.advice;
import com.alibaba.fastjson.JSON;
import com.rsa.annotation.Encrypt;
import com.rsa.config.SecretKeyConfig;
import com.rsa.util.Base64Util;
import com.rsa.util.RSAUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import java.lang.annotation.Annotation;
import java.util.Objects;
@ControllerAdvice
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
private final Logger log = LoggerFactory.getLogger(this.getClass());
private static final ThreadLocal<Boolean> encryptLocal = new ThreadLocal<>();
private boolean encrypt;
@Autowired
private SecretKeyConfig secretKeyConfig;
public EncryptResponseBodyAdvice() {
}
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
Annotation[] annotations = returnType.getDeclaringClass().getAnnotations();
if (annotations.length > 0 && this.secretKeyConfig.isOpen()) {
for (Annotation annotation : annotations) {
if (annotation instanceof Encrypt) {
return this.encrypt = true;
}
}
}
return this.encrypt = Objects.requireNonNull(returnType.getMethod()).isAnnotationPresent(Encrypt.class) && this.secretKeyConfig.isOpen();
}
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
Boolean status = encryptLocal.get();
if (null != status && !status) {
encryptLocal.remove();
} else {
if (this.encrypt) {
String publicKey = this.secretKeyConfig.getPublicKey();
try {
String content = JSON.toJSONString(body);
if (!StringUtils.hasText(publicKey)) {
throw new NullPointerException("Please configure rsa.encrypt.publicKey parameter!");
}
byte[] data = content.getBytes();
byte[] encodedData = RSAUtil.encrypt(data, publicKey);
String result = Base64Util.encode(encodedData);
if (this.secretKeyConfig.isShowLog()) {
this.log.info("Pre-encrypted data:{},After encryption:{}", content, result);
}
return result;
} catch (Exception var13) {
this.log.error("Encrypted data exception", var13);
}
}
}
return body;
}
}
- 在启动类上加@EnableSecurity注解
- 新建测试类
package com.rsa.controller;
import com.alibaba.fastjson.JSON;
import com.rsa.annotation.Decrypt;
import com.rsa.annotation.Encrypt;
import com.rsa.entities.TestBean;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
//@Encrypt
//@Decrypt
@RestController
public class TestController {
@Encrypt
@GetMapping("/encryption")
public TestBean encryption(){
TestBean testBean = new TestBean();
testBean.setUsername("小明觉得不错");
testBean.setAge(18);
return testBean;
}
@Decrypt
@PostMapping("/decryption")
public TestBean Decryption(@RequestBody String testBean) {
log.info("testBean : [{}]", testBean);
return JSON.parseObject(testBean, TestBean.class);
}
}
启动项目,访问swagger地址进行测试