对于一些安全级别比较高的项目,测试要求重要业务数据和敏感数据需要进行响应加密处理,等保三级项目需要数据传输加密就包括了请求和相应数据加密,请求数据可能会被拦截篡改对我们服务器造成威胁,所以可以利用网关进行项目中传输数据的统一加密,响应数据被拦截篡改只会影响浏览器展示,不会对服务器造成影响,所以响应加密只需要对重要业务数据和敏感数据进行加密即可,本编文章主要介绍响应加密
既然需要响应加密,那么前端肯定需要存储密钥,所以,要求我们请求加密和响应加密不允许使用相同的密钥对,对于响应加密,服务器端存储公钥和私钥的一半,前端浏览器存储一半,对于有条件的项目可以使用一次性密钥,只不过会对网页加载速度造成影响,这次我们使用固定的密钥对
基本思路为,前端公共组件js声明变量存储私钥的前半段,服务器端存储公钥和私钥的后半段,私钥后半段用户登录时可以利用tag标签写入到前台主页,然后针对响应加密的数据将密钥拼接起来对数据解密渲染页面,接下来我们看主要代码.本次采用国密算法,SM2+SM3对响应数据做处理
做数据响应加密处理时,可能我们的项目已经在测试阶段,所以就要求对项目影响最小,所以我们可以写一个jar包,然后项目进行依赖即可
1.实现ResponseBodyAdvice接口
package com.xxx;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
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.stereotype.Component;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import com.alibaba.fastjson.JSON;
import com.xxx.anntation.IsResponseEncrypt;
import com.xxx.base.security.sm3.SM3Utils;
import com.xxx.smcrypto.exception.InvalidKeyException;
import com.xxx.smcrypto.exception.InvalidSourceDataException;
@Component
@ControllerAdvice(basePackages="com.xxx")//需要扫描的包路径
@ConditionalOnProperty(name = "safety.responseIsEncrypt", havingValue = "true")//全局开关
public class EncodeResponseBody implements ResponseBodyAdvice<Object> {
/**
* 生成前段用于加解密的秘钥对
*/
public static final String getClientPri= "CB467B244DAC4A9F8E90DC4F14CB74BA";//后半段
public static final String getClientPub="0451EA5DD060CC5E19091F35C48C129081688F5CFDB481AD5E9D0A024DC829586BC07B928CDB48A64B0795D58C97D26389B957F5B15B808C9EE74F5207AB663F1E";
Sm2Utils utils = new Sm2Utils();
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
return returnType.hasMethodAnnotation(IsResponseEncrypt.class);//只针对使用指定注解的方法进行响应加密,此处返回false则不走此方法
}
@Override
public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType,
Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request,
ServerHttpResponse response) {
String data = JSON.toJSONString(body).toString();
return utils.encryptFromText(getClientPub, data+"|"+SM3Utils.encrypt(data.toString()));
}
}
2.利用tag标签将密钥后半段写入前台
import java.io.IOException;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.tagext.TagSupport;
import com.xxx.EncodeResponseBody;
/**
* @description:响应加密将秘钥通过tag标签传输到前台,前台存储前半段,tag标签输出后半段
*/
public class ResponseEncrypt extends TagSupport{
private static final long serialVersionUID = 8444686156573978251L;
@Override
public int doStartTag() throws JspException {
try {
StringBuilder tmp = new StringBuilder();
tmp.append("<script>")
.append("window.ResponseEncrypt = '"+EncodeResponseBody.getClientPri+"';")//写入秘钥的后半段
.append("</script>").toString();
pageContext.getOut().print(tmp);
} catch (IOException e) {
e.printStackTrace();
}
return super.doStartTag();
}
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE taglib PUBLIC
"-//Sun Microsystems, Inc.//DTD JSP Tag Library 1.1//EN"
"http://java.sun.com/j2ee/dtds/web-jsptaglibrary_1_1.dtd">
<taglib>
<tlibversion>1.0</tlibversion>
<jspversion>1.1</jspversion>
<shortname>extension</shortname>
<uri>http://secure.bodhi.com/taglib</uri>
<tag>
<name>responseEncrypt</name>
<tagclass>com.aostarit.tag.ResponseEncrypt</tagclass>
<bodycontent>scriptless</bodycontent>
</tag>
</taglib>
3.写一个自定义注解
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@Retention(RUNTIME)
@Target(METHOD)
public @interface IsResponseEncrypt {
}
4.最终使用的时候很简单,引入jar包,然后在需要响应加密的方法体上加上@IsResponseEncrypt 注解即可,会自动对当前方法体返回的数据进行加密