在项目开发中,很多场景下我们的接口参数都需要进行加解密处理。
例如我开发的此项目中,参数原文使用AES加密为 params
字段,AES key使用RSA加密为 encKey
字段。
不统一处理
如果不对参数解密进行统一处理
@PostMapping("login")
public ResponseEntity login(HttpServletRequest request){
String params = request.getParameter("params");
String encKey = request.getParameter("encKey");
/* 1.RSA私钥解密出AES的KEY */
PrivateKey privateKey = RSAUtil.getPrivateKey(RSAKeys.PRIVATE_KEY);
aesKey = RSAUtil.privateDecrypt((new BASE64Decoder()).decodeBuffer(encKey), privateKey);
/* 2.AES解密出原始参数 */
String decrypt = AESUtil.decrypt(params, new String(aesKey));
JSONObject originParam = JSONObject.parseObject(decrypt);
}
则需在每个需参数解密的方法进行参数解密,代码冗余度较高
方案1:HttpServletRequestWrapper
- 定义一个
HttpServletRequestWrapper
,并在Wrapper中实现参数解密逻辑
@Slf4j
public class EncHttpServletRequest extends HttpServletRequestWrapper {
private JSONObject originParam;
private String encKey;
private String params;
public EncHttpServletRequest(HttpServletRequest request) throws GlobalException{
super(request);
String encKey = request.getParameter("encKey");
String params = request.getParameter("params");
this.encKey = encKey;
this.params = params;
if(StringUtils.isEmpty(encKey)||StringUtils.isEmpty(params)){
throw new GlobalException(ErrorEnum.ERROR_REQUEST_PARAM);
}
byte[] aesKey;
try{
/* 1.RSA私钥解密出AES的KEY */
PrivateKey privateKey = RSAUtil.getPrivateKey(RSAKeys.PRIVATE_KEY);
aesKey = RSAUtil.privateDecrypt((new BASE64Decoder()).decodeBuffer(encKey), privateKey);
/* 2.AES解密出原始参数 */
String decrypt = AESUtil.decrypt(params, new String(aesKey));
originParam = JSONObject.parseObject(decrypt);
}catch (Exception e){
e.printStackTrace();
throw new GlobalException(ErrorEnum.ERROR_REQUEST_PARAM_DECRYPT);
}
}
}
- 再定义一个Filter,对请求进行Wrap
@Slf4j
@Component
@Order
@WebFilter(urlPatterns = "/*", filterName = "paramsDecryptFilter")
public class ParamsDecryptFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest)servletRequest;
HttpServletResponse rsp = (HttpServletResponse)servletResponse;
// 对需要wrap的请求进行判断转换
if(HttpMethod.POST.name().equals(req.getMethod())){
EncHttpServletRequest encHttpServletRequest = null;
try{
encHttpServletRequest = new EncHttpServletRequest(req);
}catch (GlobalException e){
rsp.setContentType("application/json; charset=utf-8");
try (PrintWriter writer = rsp.getWriter()) {
ResponseResult error = ResponseResult.error(e.getErrorEnum());
String errorResponse = JSONObject.toJSONString(error);
writer.write(errorResponse);
}
return;
}
filterChain.doFilter(encHttpServletRequest,rsp);
}else{
filterChain.doFilter(req,rsp);
}
}
}
这样,在特定情况下HttpServletRequest
就会wrap成 EncHttpServletRequest
,在endpoint中就可以直接使用:
@PostMapping("login")
public ResponseResult login(EncHttpServletRequest request){
JSONObject originParam = request.getOriginParam();
}
但是这种方式对于参数体都没有直接在方法中明确,对于使用swagger或者其他接口文档生成拓展不是很友好。
方案2:AbstractHttpMessageConverter
- 实现
AbstractHttpMessageConverter
可以定义请求类型转换
/**
* 定义加密http请求参数自定义类型转换
* 当请求MediaType为application/x-www-form-urlencoded,@RequestBody参数为DecryptParam类型时自动转换
*/
@Slf4j
public class EncMessageConverter extends AbstractHttpMessageConverter
- 创建参数注解
/**
* 标识加密的参数type
* EncMessageConverter进行自动类型转换
* @see EncMessageConverter
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface EncryptParam {
}
- 在原始参数对象上应用注解
@Data
@EncryptParam
public class LoginDTO {
@NotEmpty
private String username;
@NotEmpty
private String password;
@NotEmpty
private String timestamp;
}
这样一来,满足条件时,参数将进行自动类型转换,并且参数文档也能按照未加密时的原字段生成
@PostMapping("login")
public ResponseResult login(@RequestBody @Valid LoginDTO dto){
}
当然,也可以不使用AbstractHttpMessageConverter
,自行定义注解完成AOP实现