个人博客地址:阿里云Whois域名解析开发
微信公众号:Code技术资讯,每日为你带来各种开发/运维干货。
由于最近在做一个域名监控网站,需要用到whois功能,于是通过调用阿里云提供的API,用java开发了一套Whois解析功能,本文主要描述开发的过程以及遇到的一个大坑!
注:从2018年5月开始,受欧盟最新《通用数据保护条例》影响,阿里云Whois已不能查询到域名注册人的相关信息,本文解析Whois结果时未做修改,仅供参考。
什么是Whois?
阿里云:通过 WHOIS 查询,可以了解到域名背后的拥有者或拥有机构,获取域名注册联系信息,包括注册人、管理者和技术联系人等信息,还能够提供该域名的注册商信息(比如阿里云)、域名状态和其他重要日期(注册日期、过期日期等)。
WHOIS 域名查询通常被用于多种法律相关的查询目的。网络管理者通过 WHOIS 数据来认定和确定问题。
例如,WHOIS 信息可以用来判断域名用途是否合规,商标是否侵权,追踪生产违法内容或参与网络诈骗的域名注册者。
此外,ICANN 的协议也声明要保护域名注册人的利益,禁止他人利用 WHOIS 信息来自动判断并向特定注册局或注册商的用户名单,推送营销、诈骗信息或发送大量垃圾信息等行为。为了避免您的信息被其他人获取,您还可以开启域名隐私保护服务。
作者:简单来说,就是每个域名在注册之后都会被记录下域名拥有者的相关信息以及域名的注册信息等等,而whois就是用来查询域名的这些信息的。(未备案的域名可以开启域名保护功能,此时whois中是查不到的。)
Whois查询-中国万网
我们今天要做的,就是在我们的网站上集成whois功能。
阿里云Whois API
阿里云官方API请点击:阿里云whois 开发文档,下面摘取API部分主要内容。
请求方式
http://alidns.aliyuncs.com/?Action=DescribeDomainWhoisInfo
&DomainName=example.com
&<公共请求参数>
公共请求参数
公共请求参数是指每个接口都需要使用到的请求参数。
名称 | 类型 | 是否必须 | 描述 |
---|---|---|---|
Format | String | 否 | 返回值的类型,支持JSON与XML。默认为XML |
Version | String | 是 | API版本号,为日期形式:YYYY-MM-DD ,本版本对应为2015-01-09
|
AccessKeyId | String | 是 | 阿里云颁发给用户的访问服务所用的密钥ID |
Signature | String | 是 | 签名结果串,关于签名的计算方法,请参见 签名机制。 |
SignatureMethod | String | 是 | 签名方式,目前支持HMAC-SHA1 |
Timestamp | String | 是 | 请求的时间戳。日期格式按照ISO8601标准表示,并需要使用UTC时间。格式为YYYY-MM-DDThh:mm:ssZ 例如,2015-01-09T12:00:00Z (为UTC时间2015年1月9日12点0分0秒) |
SignatureVersion | String | 是 | 签名算法版本,目前版本是1.0 |
SignatureNonce | String | 是 | 唯一随机数,用于防止网络重放攻击。用户在不同请求间要使用不同的随机数值 |
返回参数
名称 | 类型 | 描述 |
---|---|---|
RequestId | String | 唯一请求识别码 |
StatusList | DomainStatusType | 域名状态列表 |
DnsServers | DnsServerType | 域名当前使用的DNS列表 |
RegistrantName | String | 所有者 |
RegistrantEmail | String | 所有者联系邮箱 |
Registrar | String | 注册商 |
RegistrationDate | String | 注册日期 |
ExpirationDate | String | 到期日期 |
返回示例
XML格式
<DescribeDomainWhoisInfoResponse>
<RequestId>536E9CAD-DB30-4647-AC87-AA5CC38C5382</RequestId>
<RegistrantName>Alibaba Cloud Computing Ltd.</RegistrantName>
<RegistrantEmail>dnsadmin@hk.alibaba-inc.com</RegistrantEmail>
<Registrar>MARKMONITOR INC.</Registrar>
<RegistrationDate>28-sep-2007</RegistrationDate>
<ExpirationDate>28-sep-2016</ExpirationDate>
<StatusList>
<Status>clientDeleteProhibited</Status>
<Status>clientTransferProhibited</Status>
<Status>clientUpdateProhibited</Status>
</StatusList>
<DnsServers>
<DnsServer>A.IANA-SERVERS.NET</DnsServer>
<DnsServer>B.IANA-SERVERS.NET</DnsServer>
</DnsServers>
</DescribeDomainWhoisInfoResponse>
JSON示例
{
"RegistrantName": "Alibaba Cloud Computing Ltd.",
"RegistrantEmail": "dnsadmin@hk.alibaba-inc.com",
"Registrar": "MARKMONITOR INC.",
"RegistrationDate": "28-sep-2007",
"ExpirationDate": "28-sep-2016",
"StatusList": {
"Status": [
"clientDeleteProhibited",
"clientTransferProhibited",
"clientUpdateProhibited"
]
},
"DnsServers": {
"DnsServer": [
"A.IANA-SERVERS.NET",
"B.IANA-SERVERS.NET"
]
}
}
java调用API
1、请求参数的构建
首先创建请求参数集合,将API中需要的参数都放进去:
public class DNSUtils {
private static Logger logger = Logger.getLogger("DNS");
public static Map<String, Object> getWhois(String domainName) throws UnsupportedEncodingException, SignatureException {
//字典序排序
Map<String, Object> map = new TreeMap<String, Object>();
map.put("Action", "DescribeDomainWhoisInfo");
map.put("DomainName", domainName);
map.put("Format", "json");
map.put("Version", "2015-01-09"); // 选择API版本,由于只发现了这一个可用的版本号,就用了这个版本
map.put("SignatureNonce", UUID.randomUUID().toString());
map.put("SignatureVersion", "1.0");
map.put("SignatureMethod", "HMAC-SHA1");
map.put("AccessKeyId", "XXX"); // 此ID为阿里云提供的AccessKeyId
String now = DateUtils.formatISO8601Date(new Date());
map.put("Timestamp", now);
......
}
}
上面用到的AccessKeyId是阿里云所提供,如何获取请点击:如何获取AccessKey ID和AccessKey Secret
Timestamp时间戳使用的是ISO8601日期,需要进行日期格式的转换:
/**
* 获取ISO8601日期
*
* @param date
* @return
*/
public static String formatISO8601Date(Date date) {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
df.setTimeZone(new SimpleTimeZone(0, "GMT"));
return df.format(date);
}
2、数字签名(Signature)的生成
数字签名是API请求的参数之一,生成签名也是访问API最为复杂的一步。数字签名生成规则请参考:签名机制
java生成签名代码:
//字典序排序
Map<String, Object> map = new TreeMap<String, Object>();
map.put("Action", "DescribeDomainWhoisInfo");
map.put("DomainName", domainName);
map.put("Format", "json");
map.put("Version", "2015-01-09"); // 选择API版本,由于只发现了这一个可用的版本号,就用了这个版本
map.put("SignatureNonce", UUID.randomUUID().toString());
map.put("SignatureVersion", "1.0");
map.put("SignatureMethod", "HMAC-SHA1");
map.put("AccessKeyId", "XXX"); // 此ID为阿里云提供的AccessKeyId
String now = DateUtils.formatISO8601Date(new Date());
map.put("Timestamp", now);
/********** 以下过程为生成数字签名过程 **********/
// url参数序列化
String urlParams = CommonUtils.formDataOrderSerialize(map);
String encodeUrlParam = URLEncoder.encode(urlParams, "utf-8");
// 阿里云算法修正,解决TimeStamp冒号被转义成%253A的问题。
// 这是个大坑,阿里云自己的算法在进行encode字符串时,冒号被错误的转换成了%253A,正确应为%3A,导致发送请求时总是提示失败!
// 最终还是靠着本博主坚强的毅力一个字符一个字符对出来的。此处只能将错就错,把%3A替换为%253A,这样才能和阿里云的计算结果对上号。
encodeUrlParam = encodeUrlParam.replaceAll("%3A", "%253A");
logger.info("Whois解析:" + domainName);
String tosign = "POST" + "&" + "%2F" + "&" + encodeUrlParam;
String hmacSHA1 = SignatureUtils.hmacSHA1Base64(tosign, "DxgPChG1Z6D8mVV72Yc2hmhjoKnuoi&").trim();
String signature = URLEncoder.encode(hmacSHA1, "utf-8");
map.put("Signature", signature);
表单参数序列化:
/**
* 表单参数序列化(依据阿里云的规则)
*
* @param formMap
* @return
*/
public static String formDataOrderSerialize(Map<String, Object> formMap) throws UnsupportedEncodingException {
if (formMap != null && formMap.isEmpty()) {
return null;
}
TreeMap<String, Object> orderedMap = new TreeMap<>();
for (String key : formMap.keySet()) {
orderedMap.put(key, formMap.get(key));
}
String result = formDataSerialize(orderedMap);
return result;
}
基于SHA1算法,计算签名HMAC值
public class SignatureUtils {
private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1";
/**
* Computes RFC 2104-compliant HMAC signature. * @param data The data to be
* signed.
*
* @param key The signing key.
* @return The Base64-encoded RFC 2104-compliant HMAC signature.
* @throws java.security.SignatureException when signature generation fails
*/
public static String hmacSHA1Base64(String data, String key)
throws java.security.SignatureException {
String result;
try {
// get an hmac_sha1 key from the raw key bytes
SecretKeySpec signingKey = new SecretKeySpec(key.getBytes(),
HMAC_SHA1_ALGORITHM);
// get an hmac_sha1 Mac instance and initialize with the signing key
Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM);
mac.init(signingKey);
// compute the hmac on input data bytes
byte[] rawHmac = mac.doFinal(data.getBytes());
// base64-encode the hmac
result = Base64.encode(rawHmac);
} catch (Exception e) {
throw new SignatureException("Failed to generate HMAC : "
+ e.getMessage());
}
return result;
}
}
3、发送请求
String responseStr = HttpUtils.sendPost("https://alidns.aliyuncs.com/", CommonUtils.formDataSerialize(map), null);
logger.debug("result:" + responseStr);
HashMap<String, Object> resultMap = new HashMap<>();
try {
JSONObject domainInfoJson = JSON.parseObject(responseStr);
resultMap.put("RegistrantEmail", domainInfoJson.get("RegistrantEmail"));
resultMap.put("RegistrantName", domainInfoJson.get("RegistrantName"));
resultMap.put("Registrar", domainInfoJson.get("Registrar"));
resultMap.put("RegistrationDate", domainInfoJson.get("RegistrationDate").toString());
resultMap.put("ExpirationDate", domainInfoJson.get("ExpirationDate").toString());
} catch (Exception e) {
logger.error("whois解析异常:" + e.getMessage());
}
return resultMap;
发送请求并解析返回字符串,最终得到一个包含域名whois新的map,然后处理下,在页面上展示就OK了。