在实现之初,就遇到一个麻烦,因为每次调用服务器端都要进行验证,所以设计了一个签名算法,官方的文档如下:
签名算法
云推送服务API使用的签名算法如下:
-
- 获取请求的http method
- 获取请求的url,包括host和sheme,但不包括query_string的部分
- 将所有参数(包括GET或POST的参数,但不包含签名字段)格式化为“key=value”格式,如“k1=v1”、“k2=v2”、“k3=v3”;
- 将格式化好的参数键值对以字典序升序排列后,拼接在一起,如“k1=v1k2=v2k3=v3”,并将http method和url按顺序拼接在这个字符串前面;
- 在拼接好的字符串末尾追加上应用的secret_key,并进行urlencode形成base_string;
- 上述字符串的MD5值即为签名的值:
sign=MD5(urlencode($http_method$url$k1=$v1$k2=$v2$k3=$v3$secret_key));
-
说明:
- $secret_key:通过“开发者中心 -> 管理中心 -> 点击某应用 -> 应用信息详情页” 获得。
举例:
url [POST]:
http://{domain}/rest/2.0/channel/channel?method=token×tamp=1313293563&expires=1313293565&v=1
从官方文档来看,有几点是很重要的
- http method参数是必须的,从rest api中发现,目前也只支持get和post
- host和scheme参数也是必须的,官方文档中说明支持https和http两种
- 签名字段不参加签名字段的生成
- 其他所有的参数按照key=value的方式拼成一个字符串
- 字符串末尾加上sk的值
- 将前面的字符串使用urlencode进行编码
- 将urlencode编码后的字符串生成md5值
这个里面其他的地方都是没什么的,关键是参数的顺序,我们知道,客户端给服务器端提交参数的时候是没有顺序的,当时我在实现的时候发现,有一些方法的签名是对的,有一些被服务器端当成错误的签名了,琢磨不透参数的顺序是怎么样的,最后反复研究官方文档发现,参数的顺序是按照参数的字母顺序排列的。发现了这个,其他的都很简单了,直接上代码。
package com.baidu.push.auth;
/**
* AK和SK是应用访问资源或服务的唯一凭证。 查看AK和SK信息
* @see <a href="http://developer.baidu.com/wiki/index.php?title=docs/cplat/push/guide">官方文档</a>
*
* @author liyazhou@baidu.com
*
*/
public class PushCredentials {
private String accessKey;
private String secretKey;
public PushCredentials(String accessKey, String secretKey) {
this.accessKey = accessKey;
this.secretKey = secretKey;
}
public String getAccessKey() {
return this.accessKey;
}
public String getSecretKey() {
return this.secretKey;
}
}
PushCredentials这个里面是对ak和sk的一个简单封装,没有什么。
package com.baidu.push.auth;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.baidu.push.http.PushHttpRequest;
import com.baidu.push.reqeust.BaiduPushRequest;
import com.baidu.push.util.Constants;
import com.baidu.push.util.PushClientException;
/**
* 生成签名算法
*
* @see <a href="http://developer.baidu.com/wiki/index.php?title=docs/cplat/push/api">官方文档</a>
*
* @author liyazhou@baidu.com
*
*/
public class PushSigner {
private static final Log log = LogFactory.getLog(PushSigner.class);
private static String urlencode(String str) throws UnsupportedEncodingException {
String rc = URLEncoder.encode(str, "utf-8");
return rc.replace("*", "%2A");
}
public static void sign(BaiduPushRequest pushRequest,PushHttpRequest httpRequest, PushCredentials credentials) {
StringBuilder signContents = new StringBuilder();
// generate sign content
if (null == pushRequest.getHttpMethod()) {
throw new PushClientException("Sign failed! Param: httpMethod can not be empty!");
}
signContents.append(pushRequest.getHttpMethod().toString());
if(pushRequest.isNeedSsl()){
signContents.append(Constants.HTTPS);
} else {
signContents.append(Constants.HTTP);
}
signContents.append(Constants.PUSH_URL);
if(pushRequest.getChannelId() == null){
signContents.append(Constants.CHANNEL);
} else {
signContents.append(pushRequest.getChannelId());
}
Map<String,String> sinParameters = httpRequest.getParameters();
for(Map.Entry<String, String> i : sinParameters.entrySet()) {
signContents.append(i.getKey());
signContents.append('=');
signContents.append(i.getValue());
}
signContents.append(credentials.getSecretKey());
log.info("signContents:"+signContents);
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.reset();
//md.update( URLEncoder.encode(sb.toString(), "utf-8").getBytes() );
md.update(urlencode(signContents.toString()).getBytes() );
byte[] md5 = md.digest();
signContents.setLength(0);
for(byte b : md5) {
signContents.append( String.format("%02x", b & 0xff));
}
httpRequest.addParameter("sign", signContents.toString());
} catch (NoSuchAlgorithmException e) {
log.error("error:"+e);
throw new PushClientException("Sign failed! reason:"+e.getMessage());
} catch (UnsupportedEncodingException e) {
log.error("error:"+e);
throw new PushClientException("Sign failed! reason:"+e.getMessage());
}
}
}
PushSigner 这个类也很好理解,如果明白了签名算法,这个就是小case了,把这个留给广大同仁自己琢磨吧。