gcttong00 2014-03-27
本文目标:学习一种比较安全的服务器间互相验证身份的方式。
问题:开发微信公众平台接口,开发者的服务器为了确保请求是否来自微信服务器,应该如何去做?
1)在微信管理页面上填写URL和TOKEN,开发者服务器上也记录同样的TOKEN。
2)微信服务器发送HTTP请求,附带上参数(注意TOKEN是不会被传输的)
参数描述
signature微信加密签名
timestamp时间戳
nonce随机数
echostr随机字符串
其中signature值通过如下摘要运算得出:
1.将token、timestamp、nonce三个参数进行字典序排序
2.将三个参数字符串拼接成一个字符串进行sha1加密(这个加密是不可逆的),并将结果的byte[]转换为16进制字符串
3)开发者服务器接收到signature,timestamp,nonce,echostr参数,跟服务器做同样的摘要运算,得到预期的一个signatrue,然后对比微信服务器发送过来的signature参数,如果相同,证明双方的TOKEN是一致的,开发者服务器确实接收到了来自微信服务器的请求,开发者服务器最后返回echostr,以告诉微信服务器接入成功。具体的开发者服务器校验逻辑代码如下显示。
Java代码收藏代码
packagemessage;
importjava.security.*;
importjava.util.Arrays;
/***
*微信消息接口认证token摘要类
*
*这个摘要类实现为单例,校验一个签名是否合法的例子如下
*<pre>
*WeixinMessageDigestwxDigest=WeixinMessageDigest.getInstance();
*booleanbValid=wxDigest.validate(signature,timestamp,nonce);
*</pre>
*
*
*@authorliguocai
*/
publicfinalclassWeixinMessageDigest{
/**
*单例持有类
*@authorliguocai
*
*/
privatestaticclassSingletonHolder{
staticfinalWeixinMessageDigestINSTANCE=newWeixinMessageDigest();
}
/**
*获取单例
*@return
*/
publicstaticWeixinMessageDigestgetInstance(){
returnSingletonHolder.INSTANCE;
}
privateMessageDigestdigest;
privateWeixinMessageDigest(){
try{
digest=MessageDigest.getInstance("SHA-1");
}catch(Exceptione){
thrownewInternalError("initMessageDigesterror:"+e.getMessage());
}
}
/**
*将字节数组转换成16进制字符串
*@paramb
*@return
*/
privatestaticStringbyte2hex(byte[]b){
StringBuildersbDes=newStringBuilder();
Stringtmp=null;
for(inti=0;i<b.length;i++){
tmp=(Integer.toHexString(b[i]&0xFF));
if(tmp.length()==1){
sbDes.append("0");
}
sbDes.append(tmp);
}
returnsbDes.toString();
}
privateStringencrypt(StringstrSrc){
StringstrDes=null;
byte[]bt=strSrc.getBytes();
digest.update(bt);
strDes=byte2hex(digest.digest());
returnstrDes;
}
/**
*校验请求的签名是否合法
*
*加密/校验流程:
*1.将token、timestamp、nonce三个参数进行字典序排序
*2.将三个参数字符串拼接成一个字符串进行sha1加密
*3.开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
*@paramsignature
*@paramtimestamp
*@paramnonce
*@return
*/
publicbooleanvalidate(Stringsignature,Stringtimestamp,Stringnonce){
//1.将token、timestamp、nonce三个参数进行字典序排序
Stringtoken=getToken();
String[]arrTmp={token,timestamp,nonce};
Arrays.sort(arrTmp);
StringBuffersb=newStringBuffer();
//2.将三个参数字符串拼接成一个字符串进行sha1加密
for(inti=0;i<arrTmp.length;i++){
sb.append(arrTmp[i]);
}
StringexpectedSignature=encrypt(sb.toString());
//3.开发者获得加密后的字符串可与signature对比,标识该请求来源于微信
if(expectedSignature.equals(signature)){
returntrue;
}
returnfalse;
}
privateStringgetToken(){
return"111111";
}
publicstaticvoidmain(String[]args){
Stringsignature="f86944503c10e7caefe35d6bc19a67e6e8d0e564";//加密需要验证的签名
Stringtimestamp="1371608072";//时间戳
Stringnonce="1372170854";//随机数
WeixinMessageDigestwxDigest=WeixinMessageDigest.getInstance();
booleanbValid=wxDigest.validate(signature,timestamp,nonce);
if(bValid){
System.out.println("token验证成功!");
}else{
System.out.println("token验证失败!");
}
}
}
4)这个摘要对比的技术,同样适用于单点登录、服务期间互相调用的身份验证,前提是每台服务器都持有相同的TOKEN。此外,有些细节可以优化,例如通过timestamp对签名做超时的处理,超时的签名默认不通过;请求的参数可以加上IP,USERID等额外信息;返回的echostr可以再次与TOKEN做摘要,可以使微信服务器确保接受来自开发者服务器的响应,但是微信服务器没有这么做,也许它本身已经做了足够安全控制。
微信消息接口文档:
http://mp.weixin.qq.com/wiki/index.php?title=%E6%B6%88%E6%81%AF%E6%8E%A5%E5%8F%A3%E6%8C%87%E5%8D%97