抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

系统加密服务-后台解密

涉及的问题

  • 要知道解密是否成功

  • 对于AJAX传过来的PYLOAD 载荷的流的形式的数据解密并重构可重复读取的流

  • 要对后端透明后端不需要改动任何代码

    解密通过重写HttpServletRequestWrapper 实现

    构建可重复读取的的request流需要 spring-test jar支持使用DelegatingServletInputStream 实现

  • 构建ParameterRequestWrapper

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    public class ParameterRequestWrapper extends HttpServletRequestWrapper {
    private static final Logger logger = LoggerFactory.getLogger(ParameterRequestWrapper.class);
    private Map<String, String[]> parameters = new LinkedHashMap<String, String[]>();
    //是否可重复读取流
    private boolean isReadInputStream = false;
    //pyload parameter 主体
    private String parameterBody = null;
    //解密状态
    private boolean decryptionState = false;


    /**
    * input stream 的buffer
    *
    */


    public ParameterRequestWrapper(HttpServletRequest request) throws UnsupportedEncodingException {
    super(request);
    //request 解密
    RequestEnriry requestEnriry = ParameterUtils.decrypt(request);
    if (null != requestEnriry) {
    //获取解密后的对象
    Map<String, String[]> parameterMap = requestEnriry.getParameterMap();
    //流是否被读取了
    isReadInputStream = requestEnriry.isReadInputStream();
    if (isReadInputStream) {
    parameterBody = requestEnriry.getParameterBody();
    }
    //解密是否成功
    decryptionState = requestEnriry.isPass();
    if (null != parameterMap && !parameterMap.isEmpty()) {
    parameters = parameterMap;
    }
    }
    }


    @Override
    public String getParameter(String key) {
    String[] values = parameters.get(key);
    return StringUtils.arrayToString(values);
    }

    @Override
    public Map<String, String[]> getParameterMap() {
    return parameters;
    }

    @Override
    public Enumeration<String> getParameterNames() {
    return new Vector<String>(parameters.keySet()).elements();
    }

    @Override
    public String[] getParameterValues(String name) {
    String[] result = null;
    Object value = parameters.get(name);
    if (value == null) {
    result = null;
    } else if (value instanceof String[]) {
    result = (String[]) value;
    } else if (value instanceof String) {
    result = new String[]{(String) value};
    } else {
    result = new String[]{value.toString()};
    }
    return result;
    }

    @Override
    public ServletInputStream getInputStream() throws IOException {
    if (isReadInputStream) {
    if (null != parameterBody) {
    final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(parameterBody.getBytes());
    //构建可重复读取的流
    return new DelegatingServletInputStream(byteArrayInputStream);
    }
    } else {
    return super.getInputStream();
    }
    return null;
    }

    public boolean isDecryptionState() {
    return decryptionState;
    }

    public void setDecryptionState(boolean decryptionState) {
    this.decryptionState = decryptionState;
    }
  • 构建filter

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    public class ParametersFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
    throws ServletException, IOException {
    //强制指定编码,解决解密后乱码问题
    request.setCharacterEncoding("UTF-8");
    ParameterRequestWrapper parameterRequestWrapper = new ParameterRequestWrapper(request);
    //获取加密状态
    boolean isDecryptionState = parameterRequestWrapper.isDecryptionState();
    if (isDecryptionState) {
    chain.doFilter(parameterRequestWrapper, response);
    } else {
    //返回加密失败的状态,可以在页面处理
    response.setStatus(911);
    }
    }
    }
  • 在web.xml 设置filter

    需要RequestContextListener 支持,在web.xml 中配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
!-- 参数过滤器 -->
<filter>
<filter-name>ParametersFilter</filter-name>
<filter-class>com.xxx.common.security.web.filter.ParametersFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>ParametersFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<!-- request固化器 -->
<listener>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>

这样就配置完了

对于普通的parameterMap参数解密

  • 先检查参数名称是否是加密的key 我们可以指定一个不容易重名的例如“$@$.ecryptedData“
  • 如果加密了 就就行解密 解密完成后通过fastJson将JSON串转换为MAP
  • 检查是否存在我们JS中定义的时间戳 如果不存在 则判断解密失败

    代码片段如下
    参数解密

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public void decrypt(HttpServletRequest request, RequestEnriry requestEnriry) {
    //检查是否是form表单提交的
    if (check(request)) {
    Map<String, String[]> parameterMap = requestEnriry.getParameterMap();
    //检查是否加密
    boolean isEncrypt = isEncrypt(parameterMap);
    if (isEncrypt) {
    requestEnriry.setEncrypt(isEncrypt);
    //获取加密的参数
    String encParameter = getEncryptedParameter(parameterMap);
    //解密数据
    String decParameter = decryptData(encParameter, getSecretKey(request));
    if (StringUtils.isNotEmpty(decParameter)) {
    //参数转换
    Map<String, String[]> decParameterMap = encParameterConver(decParameter);
    //将参数封装到实体中 requestEnriry.putParameterMap(decParameterMap);
    //设置传过来的时间戳 requestEnriry.setTimestamp(getEncryptedTimestamp(decParameterMap));
    }
    }
    }
    }

    检查参数是否加密

    1
    2
    3
    4
    5
    6
    7
    8
    9
     public boolean isEncrypt(Map parameterMap) {
    Map<String, String[]> parameterMap = requestEnriry.getParameterMap();
    if (null != parameterMap && !parameterMap.isEmpty()) {
    if (null != parameterMap && !parameterMap.isEmpty() && parameterMap.containsKey("$@$.ecryptedData")) {
    flag = true;
    }
    }
    return flag;
    }

    获取加密的参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public String getEncryptedParameter(Map<String, String[]> parameterMap) {
    String ecryptedParam = null;
    if (null != parameterMap && !parameterMap.isEmpty()) {
    String[] parameterArray = parameterMap.get("$@$.ecryptedData");
    if (null != parameterArray && parameterArray.length > 0) {
    ecryptedParam = parameterArray[0];
    }
    }
    return ecryptedParam;
    }

检查是否需要解密操作

1
2
3
4
5
6
7
public boolean check(HttpServletRequest request) {
Map parameterMap = request.getParameterMap();
if (null != parameterMap && !parameterMap.isEmpty()) {
return true;
}
return false;
}

参数转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Map<String, String[]> encParameterConver(String decryptionJson) {
Map<String, String[]> mappingMap = new LinkedHashMap<String, String[]>();
if (null != mappingMap && StringUtils.isNotEmpty(decryptionJson)) {
Map<String, String[]> parameterMap = null;
parameterMap = ParameterUtils.jsonToMap(decryptionJson);
if (null != parameterMap && !parameterMap.isEmpty()) {
Set<String> keys = parameterMap.keySet();
for (String key : keys) {
if (StringUtils.isNotEmpty(key)) {
String[] value = parameterMap.get(key);
if (null != value) {
value = ParameterUtils.decodeURI(value);
}
if (null != value) {
mappingMap.put(key, value);
}
}
}
}
}
return mappingMap;
}

获取时间戳

1
2
3
4
5
6
7
8
9
10
public String getEncryptedTimestamp(Map<String, String[]> parameterMap) {
String timestamp = null;
if (null != parameterMap && !parameterMap.isEmpty()) {
String[] valueArray = parameterMap.get("$@$.tmp");
if (null != valueArray && valueArray.length > 0) {
timestamp = valueArray[0];
}
}
return timestamp;
}

对于AJAX PYLOAD 载荷的参数解密

  • 跟普通的一样解密一样只是有几点区别
    • pyload需要有contentType
    • contentType 不能包含multipart/form-data 即不支持文件上传
    • pyload 需要吧解析的参数还原为原始的字符串 可能是JSON字符串或者是URL参数

      代码片段如下
      参数解密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public void decrypt(HttpServletRequest request, RequestEnriry requestEnriry) {
//检查是否需要解密
if (check(request)) {
//获取pyload 参数
String pyloadParameter = getPyloadParameter(request);
//设置流读取状态为true
requestEnriry.setReadInputStream(true);
if (StringUtils.isNotEmpty(pyloadParameter)) {
requestEnriry.setParameterBody(pyloadParameter);
//将pyload参数解析
Map<String, String[]> parameterMap = ParameterUtils.getUrlParams(pyloadParameter);
//检查是否加密
boolean isEncrypt = isEncrypt(parameterMap);
if (isEncrypt) {
requestEnriry.setEncrypt(isEncrypt);
String encParameter = getEncryptedParameter(parameterMap);
if (StringUtils.isNotEmpty(encParameter)) {
String decParameter = decryptData(encParameter, getSecretKey(request));
requestEnriry.setParameterBody(decParameter);
Map<String, String[]> map = ParameterUtils.jsonToMap(decParameter);
if (null != map && !map.isEmpty()) {
requestEnriry.setTimestamp(getEncryptedTimestamp(map));
requestEnriry.putParameterMap(map);
}
}
}
}
}

}

检查是否是pyload形式

1
2
3
4
5
6
7
public boolean check(HttpServletRequest request) {
String contentType = getContentType(request);
if (StringUtils.isNotEmpty(contentType) && !contentType.contains("multipart/form-data")) {
return true;
}
return false;
}

获取pyload 参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public String getPyloadParameter(HttpServletRequest request) {
String ecryptedParam = null;
InputStream inputStream = null;
try {
inputStream = request.getInputStream();
} catch (IOException e) {
logger.error("Error reading the request body…", e);
}
if (null != inputStream) {
StringBuilder stringBuilder = new StringBuilder();
if (inputStream != null) {
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
char[] charBuffer = new char[CHAR_BUFFER_LENGTH];
int bytesRead;
while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
stringBuilder.append(charBuffer, BUFFER_START_POSITION, bytesRead);
}
} catch (IOException e) {
logger.error("Fail to read input stream", e);
}
} else {
stringBuilder.append("");
}
ecryptedParam = stringBuilder.toString();
}
return ecryptedParam;
}

其他公共类 RequestEnriry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
public class RequestEnriry {
private Map<String, String[]> parameterMap = new HashMap<String, String[]>();

private String parameterBody;

private boolean isEncrypt = false;

private boolean isReadInputStream = false;

private String timestamp = null;

public RequestEnriry() {

}

public RequestEnriry(Map<String, String[]> requestParameterMap) {
if (null != requestParameterMap && !requestParameterMap.isEmpty()) {
parameterMap.putAll(requestParameterMap);
}

}


public void handle() {
parameterMap.remove(SecurityConstant.ECRYPTED_PARAM_NAME);
}

public boolean isPass() {
boolean isPass = false;
if (isEncrypt) {
if (StringUtils.isNotEmpty(timestamp)) {
isPass = true;
}
} else {
isPass = true;
}
return isPass;
}

public Map<String, String[]> getParameterMap() {
return parameterMap;
}

public void setParameterMap(Map<String, String[]> parameterMap) {
this.parameterMap = parameterMap;
}

public void putParameterMap(Map<String, String[]> subParameterMap) {
parameterMap.putAll(subParameterMap);

}

public String getParameterBody() {
return parameterBody;
}

public void setParameterBody(String parameterBody) {
this.parameterBody = parameterBody;
}

public boolean isEncrypt() {
return isEncrypt;
}

public void setEncrypt(boolean encrypt) {
isEncrypt = encrypt;
}

public String getTimestamp() {
return timestamp;
}

public void setTimestamp(String timestamp) {
this.timestamp = timestamp;
}

public boolean isReadInputStream() {
return isReadInputStream;
}

public void setReadInputStream(boolean readInputStream) {
isReadInputStream = readInputStream;
}
}

到这一步已经全部完成了,核心思想和代码已经完成

评论