基于时间戳防盗链的功能其实每家的CDN都是支持的。主要是通过使用约定的加密字符串来对具有访问有效期的资源链接进行一些加密计算的到一个sign
值,然后访问外链里面带上这个sign
和截止时间戳去访问CDN的节点,CDN的节点会用同样的算法来计算访问链接是否合法,如果不合法则返回403 Forbidden
,否则返回所要访问的资源。
算法说明
基于时间戳的防盗链是通过对时间有关的字符串进行签名,将时间,签名通过一定的方式传递给CDN服务器作为判定依据,CDN边缘节点依据约定的算法判断来访的URL是否有访问权限。
如果通过,执行下一步;如果不通过,响应 HTTP 状态码 403。如果同时配置了Referer方式防盗链,UserAgent防盗链,时间戳防盗链,那么如果有其中一项没有通过,那么即响应403。
签名参数
参数 | 描述 |
---|---|
T | URL过期的时间,把Unix以秒为单位的时间戳,用16进制的小写字母形式表示。比如 2016-06-30 22:57:42 +0800 CST 对应的时间戳是 1467298662 ,表示为16进制就是 57753366 。 |
key | 和CDN约定好的加密字符串 |
path | 访问资源外链的PATH部分,比如如果访问的外链是http://if-pbl.qiniudn.com/golang.png?v=1 那么其中PATH部分就是/golang.png |
签名算法
待签名的原始字符串
s=key+path+T
签名方式
sign=md5(s).to_lower()
,其中to_lower()
表示生成的md5字符串用小写字母表示
签名参数传递方式
例如原始访问外链是:http://if-pbl.qiniudn.com/golang.png?v=1
最终形成的访问外链是:http://if-pbl.qiniudn.com/golang.png?v=1&sign=xxxx&t=xxxx
其中xxxx
对应各自的值。
算法参考实现
package com.qiniulab.cdn;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.apache.commons.codec.binary.Hex;
public class CdnAntiLeech {
/**
* 生成资源基于CDN时间戳防盗链的访问外链
*
* @param 资源原始外链
* @param 结果资源的有效期,单位秒
* @throws MalformedURLException
* @throws UnsupportedEncodingException
* @throws NoSuchAlgorithmException
*/
public static String getAntiLeechAccessUrlBasedOnTimestamp(String url, String encryptKey, int durationInSeconds)
throws MalformedURLException, UnsupportedEncodingException, NoSuchAlgorithmException {
URL urlObj = new URL(url);
String path = urlObj.getPath();
long timestampNow = System.currentTimeMillis() / 1000 + durationInSeconds;
String expireHex = Long.toHexString(timestampNow);
String toSignStr = String.format("%s%s%s", encryptKey, path, expireHex);
String signedStr = md5ToLower(toSignStr);
String signedUrl = null;
if (urlObj.getQuery() != null) {
signedUrl = String.format("%s&sign=%s&t=%s", url, signedStr, expireHex);
} else {
signedUrl = String.format("%s?sign=%s&t=%s", url, signedStr, expireHex);
}
return signedUrl;
}
private static String md5ToLower(String src) throws UnsupportedEncodingException, NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(src.getBytes("utf-8"));
byte[] md5Bytes = digest.digest();
return Hex.encodeHexString(md5Bytes);
}
}
使用方式
// cdn 配置的基于时间戳防盗链的加密字符串,cdn 配置完成后会得到
String encryptKey = "";
// 待加密链接
String fileKey = "xxxx.pdf";
String encodedFileKey;
try {
// 考虑到文件名称会有中文,所以需要做urlencode
encodedFileKey = URLEncoder.encode(fileKey, "utf-8");
String urlToSign = String.format("http://img.abc.com/%s", encodedFileKey);
// 有效期
int duration = 3600;
String signedUrl = CdnAntiLeech.getAntiLeechAccessUrlBasedOnTimestamp(urlToSign, encryptKey, duration);
System.out.println(signedUrl);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}