在实际的工作环境中,我们经常会遇到使用安全性贼高的要求,因此对于部分的接口,我们需要客户端加密后请求过来,服务端解密后再做处理,然后服务端处理完之后,把数据进行加密后返回给客户端,客户端再进行解密使用,如下图:
首先请求接口的时候,我们传递加密数据过去
那么想这种我们怎么做呢?其实很简单,我们提供一些工具类,把他导入到咱们项目里面去即可
在使用的时候,如果需要把接口返回的内容进行加密,那么我们只需要在接口上添加@Encrypt注解即可,例如:
如果我们希望接受到的参数是经过加密的,那么我们只需要在request body的地方添加@Decrypt注解即可,例如:
这里我们说下核心就是@ControllerAdvice,这里的话,我们可以利用RequestBodyAdviceAdapter和ResponseBodyAdvice来对请求的接口进行拦截,然后在里面添加逻辑即可:
RequestBodyAdviceAdapter 这个是针对于request进行拦截,然后对参数进行修改。 ResponseBodyAdvice 这个是针对于response进行拦截,然后对参数进行修改。
下面我们具体讲解下:
针对request的参数进行解密
首先我们声明一个注解:Decrypt,注册的完整代码示例如下:
package com.mybatis.demo.aesutils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.PARAMETER}) public @interface Decrypt { }
然后我们来编写这个注解的实现
package com.mybatis.demo.aesutils; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.core.MethodParameter; import org.springframework.http.HttpHeaders; 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.RequestBodyAdviceAdapter; import lombok.extern.slf4j.Slf4j; @EnableConfigurationProperties(EncryptProperties.class) @ControllerAdvice @Slf4j public class DecryptRequest extends RequestBodyAdviceAdapter { @Autowired EncryptProperties encryptProperties; @Override public boolean supports(MethodParameter methodParameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) { return methodParameter.hasMethodAnnotation(Decrypt.class) || methodParameter.hasParameterAnnotation(Decrypt.class); } @Override public HttpInputMessage beforeBodyRead(final HttpInputMessage inputMessage, MethodParameter parameter, Type targetType, Class<? extends HttpMessageConverter<?>> converterType) throws IOException { byte[] body = new byte[inputMessage.getBody().available()]; inputMessage.getBody().read(body); try { log.info("准备解密的key是:{}",encryptProperties.getKey()); byte[] decrypt = AESUtils.decrypt(body, encryptProperties.getKey().getBytes()); final ByteArrayInputStream bais = new ByteArrayInputStream(decrypt); return new HttpInputMessage() { @Override public InputStream getBody() throws IOException { return bais; } @Override public HttpHeaders getHeaders() { return inputMessage.getHeaders(); } }; } catch (Exception e) { log.error(e.getMessage(),e); } return super.beforeBodyRead(inputMessage, parameter, targetType, converterType); } }
这里是对称加密,所以这里我们使用的是AES的加密方式,所以需要编写一个AESutils,完整代码如下:
package com.mybatis.demo.aesutils; import java.util.Base64; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; public class AESUtils { private static final String AES_ALGORITHM = "AES/ECB/PKCS5Padding"; // 获取 cipher private static Cipher getCipher(byte[] key, int model) throws Exception { SecretKeySpec secretKeySpec = new SecretKeySpec(key, "AES"); Cipher cipher = Cipher.getInstance(AES_ALGORITHM); cipher.init(model, secretKeySpec); return cipher; } // AES加密 public static String encrypt(byte[] data, byte[] key) throws Exception { Cipher cipher = getCipher(key, Cipher.ENCRYPT_MODE); return Base64.getEncoder().encodeToString(cipher.doFinal(data)); } // AES解密 public static byte[] decrypt(byte[] data, byte[] key) throws Exception { Cipher cipher = getCipher(key, Cipher.DECRYPT_MODE); return cipher.doFinal(Base64.getDecoder().decode(data)); } }
在使用aes进行加密的时候,我们需要自定义一个密钥,因此这里的话,我们使用读取配置文件的方式,编写一个proerpties读取密钥:
package com.mybatis.demo.aesutils; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "spring.encrypt") public class EncryptProperties { private final static String DEFAULT_KEY = "www.80wz.com.com"; private String key = DEFAULT_KEY; public String getKey() { return key; } public void setKey(String key) { this.key = key; } }
这里的话,我们在配置文件中配置的密钥格式如下:
spring.encrypt.key=1234567890123456
只要配置了密钥,就可以读取到,如果没有配置的话,我们这里设置了默认的密钥。
备注:
1、这里的aes密钥长度必须是16位。
以上我们的解密就实现了,在使用的时候,对于request body添加@Decrypt注解就可以达到想要的效果了。
针对response的参数进行加密
对于response的加密,我们这里的处理方式和上面的解密流程是差不多的,首先定义加密的注解:Encrypt,完整代码示例如下:
package com.mybatis.demo.aesutils; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD,ElementType.PARAMETER}) public @interface Encrypt { }
然后我们编写一个@Encrypt注解的实践,完整代码如下:
package com.mybatis.demo.aesutils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; 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.web.bind.annotation.ControllerAdvice; import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; @EnableConfigurationProperties(EncryptProperties.class) @ControllerAdvice @Slf4j public class EncryptResponse implements ResponseBodyAdvice<RespBean> { private ObjectMapper om = new ObjectMapper(); @Autowired EncryptProperties encryptProperties; @Override public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) { return returnType.hasMethodAnnotation(Encrypt.class); } @Override public RespBean beforeBodyWrite(RespBean body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) { log.info("准备加密的key是:{}",encryptProperties.getKey()); byte[] keyBytes = encryptProperties.getKey().getBytes(); try { if (body.getObj() != null) { body.setObj(AESUtils.encrypt(om.writeValueAsBytes(body.getObj()), keyBytes)); } } catch (Exception e) { e.printStackTrace(); } return body; } }
最后我们把这个加密的注解配置到项目中即可:
package com.mybatis.demo.aesutils; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan("com.mybatis.demo") public class EncryptAutoConfiguration { }
最后在使用的时候,我们把@Encrypt这个注解添加到controller对应的接口上即可。
备注:
1、这里示例的注解不是全局生效,主要是由于真实项目中其实不需要所有的接口都进行加密。
2、在每一个接口或者单独的参数上进行加解密配置会更灵活点。
最后按照惯例,附上本案例的源码,登录后即可下载。
还没有评论,来说两句吧...