对于一些安全级别比较高的项目,测试要求重要业务数据和敏感数据需要进行响应加密处理,等保三级项目需要数据传输加密就包括了请求和相应数据加密,请求数据可能会被拦截篡改对我们服务器造成威胁,所以可以利用网关进行项目中传输数据的统一加密,响应数据被拦截篡改只会影响浏览器展示,不会对服务器造成影响,所以响应加密只需要对重要业务数据和敏感数据进行加密即可,本编文章主要介绍响应加密

既然需要响应加密,那么前端肯定需要存储密钥,所以,要求我们请求加密和响应加密不允许使用相同的密钥对,对于响应加密,服务器端存储公钥和私钥的一半,前端浏览器存储一半,对于有条件的项目可以使用一次性密钥,只不过会对网页加载速度造成影响,这次我们使用固定的密钥对

基本思路为,前端公共组件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 注解即可,会自动对当前方法体返回的数据进行加密