在第1篇中实现了收发微信消息,但是没有做验证,本篇将介绍微信如何使用sha签名,对消息进行认证。其中安全相关的概念,如sha1散列值、签名等,可参考web安全(1)。
验证参数
@GetMapping("/handler")
public String handler(@RequestParam Map<String,String> map) {
回顾 第1篇的get验证方法,在map中会收到微信发过来的4个参数,如下:
signature:签名,是微信用timestamp、nonce、你的token三个参数进行sha1算法得出的,用于验证消息来源。
echostr:随机字符串,第1篇演示过,如果signature签名验证成功,返回echostr给微信即可确认。
timestamp:时间戳(秒),可用于阻止重放攻击,下面会讲。
nonce:随机数,增加签名的不可预测性。
验证方法
1)将token、timestamp、nonce三个参数进行字典序排序
2)将三个参数字符串拼接成一个字符串进行sha1加密
3)开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
token的作用
以上3条是微信官网原文,注意:我在web安全(1)中讲过,sha散列值与signature签名是两个不同的东西。消息参数中nonce和timestamp谁都可以生成,如果只对这样的内容进行sha1加密,实际作用只能是消息摘要,无法真正起到签名的作用,黑客可以自己生成nonce和timestamp及其sha1摘要,冒名微信请求我们的服务器。
而token是我们在微信网站中配置的,加入token以后的sha1摘要就真正起到了签名的作用,因为黑客不知道token,要伪造包含你token的假sha1摘要是相当困难的。除非你把token设的很短或很简单。
重放攻击
即使黑客无法自己生成签名,他只要知道了一次正常请求的get参数及签名,就可以反复使用这次传参伪装为真实用户,但他不能擅改时间时间戳,这会导致签名不一致。所以我们可以检查时间戳与当前时间的差值,超过一定时间,就判定是非法请求。
实现代码
添加commons-codec依赖,是apache开源的用于一些加解密之类的算法,下面会用它来实现sha1签名。
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
</dependency>
@GetMapping("/handler")
public String handler(@RequestParam Map<String,String> map) {
//1.将三个参数存入数组
String[] array = {token,map.get("timestamp"),map.get("nonce")};
//调用数组的排序方法,进行排序
Arrays.sort(array);
//2.调用spring的转换方法,将数组中已排序过的三个参数拼接成一个字符串
String s = StringUtils.arrayToDelimitedString(array, "");
//调用commons-codec的sha1加密方法生成签名
String signature = DigestUtils.sha1Hex(s);
//3.与signature对比,确认消息是否源自微信
if(!map.get("signature").equals(signature)){
//验证不成功,随便回一个无关的内容
return "fail";
}
//验证成功
return map.get("echostr");
回复消息和success
以上get验证通过后,每次用post请求业务消息都要先进行以上验证,保证消息真实源自微信,再去做业务处理。但是以后不再有echostr这个参数,而且可以根据业务场景回复特定内容,如第1篇回复hello world。
如果业务处理后不需要任何回复,可回复“success”,通知微信处理成功,否则微信会提示用户错误信息。
消息加密
以上消息都是明文发送的,并没有加密,这是微信默认的设置。假如要对消息进行加密,需要登录账号进行配置,而测试账号并没有相关配置,以后讲正式账号时再详细介绍。