共计 6496 个字符,预计需要花费 17 分钟才能阅读完成。
基于 Vue3 最新规范,实现后盾前端综合解决方案
1. 开发加解密 starter
为了让咱们开发的这个工具更加通用,也为了温习一下自定义 Spring Boot Starter,这里咱们就将这个工具做成一个 stater,当前在 Spring Boot 我的项目中间接援用就能够。
首先咱们创立一个 Spring Boot 我的项目,引入 spring-boot-starter-web 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<scope>provided</scope>
<version>2.4.3</version>
</dependency>
复制代码
因为咱们这个工具是为 Web 我的项目开发的,当前必然应用在 Web 环境中,所以这里增加依赖时 scope 设置为 provided。
依赖增加实现后,咱们先来定义一个加密工具类备用,加密这块有多种计划能够抉择,对称加密、非对称加密,其中对称加密又能够应用 AES、DES、3DES 等不同算法,这里咱们应用 Java 自带的 Cipher 来实现对称加密,应用 AES 算法:
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));
}
}
复制代码
这个工具类比较简单,不须要多解释。须要阐明的是,加密后的数据可能不具备可读性,因而咱们个别须要对加密后的数据再应用 Base64 算法进行编码,获取可读字符串。换言之,下面的 AES 加密办法的返回值是一个 Base64 编码之后的字符串,AES 解密办法的参数也是一个 Base64 编码之后的字符串,先对该字符串进行解码,而后再解密。
接下来咱们封装一个响应工具类备用,这个大家如果常常看松哥视频曾经很理解了:
public class RespBean {
private Integer status;
private String msg;
private Object obj;
public static RespBean build() {return new RespBean();
}
public static RespBean ok(String msg) {return new RespBean(200, msg, null);
}
public static RespBean ok(String msg, Object obj) {return new RespBean(200, msg, obj);
}
public static RespBean error(String msg) {return new RespBean(500, msg, null);
}
public static RespBean error(String msg, Object obj) {return new RespBean(500, msg, obj);
}
private RespBean() {}
private RespBean(Integer status, String msg, Object obj) {
this.status = status;
this.msg = msg;
this.obj = obj;
}
public Integer getStatus() {return status;}
public RespBean setStatus(Integer status) {
this.status = status;
return this;
}
public String getMsg() {return msg;}
public RespBean setMsg(String msg) {
this.msg = msg;
return this;
}
public Object getObj() {return obj;}
public RespBean setObj(Object obj) {
this.obj = obj;
return this;
}
}
复制代码
接下来咱们定义两个注解 @Decrypt 和 @Encrypt:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.PARAMETER})
public @interface Decrypt {
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Encrypt {
}
复制代码
这两个注解就是两个标记,在当前应用的过程中,哪个接口办法增加了 @Encrypt 注解就对哪个接口的数据加密返回,哪个接口 / 参数增加了 @Decrypt 注解就对哪个接口 / 参数进行解密。这个定义也比较简单,没啥好说的,须要留神的是 @Decrypt 比 @Encrypt 多了一个应用场景就是 @Decrypt 能够用在参数上。
思考到用户可能会本人配置加密的 key,因而咱们再来定义一个 EncryptProperties 类来读取用户配置的 key:
@ConfigurationProperties(prefix = “spring.encrypt”)
public class EncryptProperties {
private final static String DEFAULT_KEY = "www.itboyhub.com";
private String key = DEFAULT_KEY;
public String getKey() {return key;}
public void setKey(String key) {this.key = key;}
}
复制代码
这里我设置了默认的 key 是 www.itboyhub.com,key 是 16 位字符串,松哥这个网站地址刚好满足。当前如果用户想本人配置 key,只须要在 application.properties 中配置 spring.encrypt.key=xxx 即可。
所有筹备工作做完了,接下来就该正式加解密了。
因为松哥这篇文章一个很重要的目标是想和大家分享 ResponseBodyAdvice 和 RequestBodyAdvice 的用法,RequestBodyAdvice 在做解密的时候倒是没啥问题,而 ResponseBodyAdvice 在做加密的时候则会有一些局限,不过影响不大,还是我后面说的,如果想非常灵活的掌控所有,那还是自定义过滤器吧。这里我就先用这两个工具来实现了。
另外还有一点须要留神,ResponseBodyAdvice 在你应用了 @ResponseBody 注解的时候才会失效,RequestBodyAdvice 在你应用了 @RequestBody 注解的时候才会失效,换言之,前后端都是 JSON 交互的时候,这两个才有用。不过一般来说接口加解密的场景也都是前后端拆散的时候才可能有的事。
先来看接口加密:
@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
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) {byte[] keyBytes = encryptProperties.getKey().getBytes();
try {if (body.getMsg()!=null) {body.setMsg(AESUtils.encrypt(body.getMsg().getBytes(),keyBytes));
}
if (body.getObj() != null) {body.setObj(AESUtils.encrypt(om.writeValueAsBytes(body.getObj()), keyBytes));
}
} catch (Exception e) {e.printStackTrace();
}
return body;
}
}
复制代码
咱们自定义 EncryptResponse 类实现 ResponseBodyAdvice 接口,泛型示意接口的返回类型,这里一共要实现两个办法:
supports:这个办法用来判断什么样的接口须要加密,参数 returnType 示意返回类型,咱们这里的判断逻辑就是办法是否含有 @Encrypt 注解,如果有,示意该接口须要加密解决,如果没有,示意该接口不须要加密解决。
beforeBodyWrite:这个办法会在数据响应之前执行,也就是咱们先对响应数据进行二次解决,解决实现后,才会转成 json 返回。咱们这里的解决形式很简略,RespBean 中的 status 是状态码就不必加密了,另外两个字段从新加密后从新设置值即可。
另外须要留神,自定义的 ResponseBodyAdvice 须要用 @ControllerAdvice 注解来标记。
再来看接口解密:
@EnableConfigurationProperties(EncryptProperties.class)
@ControllerAdvice
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 {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) {e.printStackTrace();
}
return super.beforeBodyRead(inputMessage, parameter, targetType, converterType);
}
}
复制代码
首先大家留神,DecryptRequest 类咱们没有间接实现 RequestBodyAdvice 接口,而是继承自 RequestBodyAdviceAdapter 类,该类是 RequestBodyAdvice 接口的子类,并且实现了接口中的一些办法,这样当咱们继承自 RequestBodyAdviceAdapter 时,就只须要依据本人理论需要实现某几个办法即可。
supports:该办法用来判断哪些接口须要解决接口解密,咱们这里的判断逻辑是办法上或者参数上含有 @Decrypt 注解的接口,解决解密问题。
beforeBodyRead:这个办法会在参数转换成具体的对象之前执行,咱们先从流中加载到数据,而后对数据进行解密,解密实现后再从新结构 HttpInputMessage 对象返回。
接下来,咱们再来定义一个自动化配置类,如下:
@Configuration
@ComponentScan(“org.javaboy.encrypt.starter”)
public class EncryptAutoConfiguration {
}
复制代码
这个也没啥好说的,比较简单。
最初,resources 目录下定义 META-INF,而后再定义 spring.factories 文件,内容如下:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.javaboy.encrypt.starter.autoconfig.EncryptAutoConfiguration
复制代码
这样当我的项目启动时,就会主动加载该配置类。
至此,咱们的 starter 就开发实现啦。
download