springmvc 全局编码_SpringMVC请求参数和响应结果全局加密和解密

前提

前段时间在做一个对外的网关项目,涉及到加密和解密模块,这里详细分析解决方案和适用的场景。为了模拟真实的交互场景,先定制一下整个交互流程。第三方传输(包括请求和响应)数据报文包括三个部分:

1、timestamp,long类型,时间戳。

2、data,String类型,实际的业务请求数据转化成的Json字符串再进行加密得到的密文。

3、sign,签名,生成规则算法伪代码是SHA-256(data=xxx&timestamp=11111),防篡改。

为了简单起见,加密和解密采用AES,对称秘钥为"throwable"。上面的场景和加解密例子仅仅是为了模拟真实场景,安全系数低,切勿直接用于生产环境。

现在还有一个地方要考虑,就是无法得知第三方如何提交请求数据,假定都是采用POST的Http请求方法,提交报文的时候指定ContentType为application/json或者application/x-www-form-urlencoded,两种ContentType提交方式的请求体是不相同的:

//application/x-www-form-urlencoded

timestamp=xxxx&data=yyyyyy&sign=zzzzzzz

//application/json

{"timestamp":xxxxxx,"data":"yyyyyyyy","sign":"zzzzzzz"}

最后一个要考虑的地方是,第三方强制要求部分接口需要用明文进行请求,在提供一些接口方法的时候,允许使用明文交互。总结一下就是要做到以下三点:

1、需要加解密的接口请求参数要进行解密,响应结果要进行加密。

2、不需要加解密的接口可以用明文请求。

3、兼容ContentType为application/json或者application/x-www-form-urlencoded两种方式。

上面三种情况要同时兼容算是十分严苛的场景,在生产环境中可能也是极少情况下才遇到,不过还是能找到相对优雅的解决方案。先定义两个特定场景的接口:

1、下单接口(加密)

URL:/order/save

HTTP METHOD:POST

ContentType:application/x-www-form-urlencoded

原始参数:orderId=yyyyyyyyy&userId=xxxxxxxxx&amount=zzzzzzzzz

加密参数:timestamp=xxxx&data=yyyyyy&sign=zzzzzzz

2、订单查询接口(明文)

URL:/order/query

ContentType:application/json

HTTP METHOD:POST

原始参数:{"userId":"xxxxxxxx"}

两个接口的ContentType不相同是为了故意复杂化场景,在下面的可取方案中,做法是把application/x-www-form-urlencoded中的形式如xxx=yyy&aaa=bbb的表单参数和application/json中形式如{"key":"value"}的请求参数统一当做application/json形式的参数处理,这样的话,我们就可以直接在控制器方法中使用@RequestBody。

方案

我们首先基于上面说到的加解密方案,提供一个加解密工具类:

public enum EncryptUtils {

/**

* SINGLETON

*/

SINGLETON;

private static final String SECRET = "throwable";

private static final String CHARSET = "UTF-8";

public String sha(String raw) throws Exception {

MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");

messageDigest.update(raw.getBytes(CHARSET));

return Hex.encodeHexString(messageDigest.digest());

}

private Cipher createAesCipher() throws Exception {

return Cipher.getInstance("AES");

}

public String encryptByAes(String raw) throws Exception {

Cipher aesCipher = createAesCipher();

KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");

keyGenerator.init(128, new SecureRandom(SECRET.getBytes(CHARSET)));

SecretKey secretKey = keyGenerator.generateKey();

SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");

aesCipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);

byte[] bytes = aesCipher.doFinal(raw.getBytes(CHARSET));

return Hex.encodeHexString(bytes);

}

public String decryptByAes(String raw) throws Exception {

byte[] bytes = Hex.decodeHex(raw);

Cipher aesCipher = createAesCipher();

KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");

keyGenerator.init(128, new SecureRandom(SECRET.getBytes(CHARSET)));

SecretKey secretKey = keyGenerator.generateKey();

SecretKeySpec secretKeySpec = new SecretKeySpec(secretKey.getEncoded(), "AES");

aesCipher.init(Cipher.DECRYPT_MODE, secretKeySpec);

return new String(aesCipher.doFinal(bytes), CHARSET);

}

}

注意为了简化加解密操作引入了apache的codec依赖:

commons-codec

commons-codec

1.11

上面的加解密过程中要注意两点:

1、加密后的结果是byte数组,要把二进制转化为十六进制字符串。

2、解密的时候要把原始密文由十六进制转化为二进制的byte数组。

上面两点必须注意,否则会产生乱码,这个和编码相关,具体可以看之前写的一篇博客。

不推荐的方案

其实最暴力的方案是直接定制每个控制器的方法参数类型,因为我们可以和第三方磋商哪些请求路径需要加密,哪些是不需要加密,甚至哪些是application/x-www-form-urlencoded,哪些是application/json的请求,这样我们可以通过大量的硬编码达到最终的目标。举个例子:

@RestController

public class Controller1 {

@Autowired

private ObjectMapper objectMapper;

@PostMapping(value = "/order/save",

consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,

produces = MediaType.APPLICATION_JSON_UTF8_VALUE)

public ResponseEntity saveOrder(@RequestParam(name = "sign") String sign,

@RequestParam(name = "timestamp") Long timestamp,

@RequestParam(name = "data") String data) throws Exception {

EncryptModel model = new EncryptModel();

model.setData(data);

model.setTimestamp(timestamp);

model.setSign(sign);

String inRawSign = String.format("data=%s&timestamp=%d", model.getData(), model.getTimestamp());

String inSign = EncryptUtils.SINGLETON.sha(inRawSign);

if (!inSign.equals(model.getSign())){

throw new IllegalArgumentException("验证参数签名失败!");

}

//这里忽略实际的业务逻辑,简单设置返回的d

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值