個人博客地址:阿里云Whois域名解析開發(fā)
微信公眾號:Code技術(shù)資訊,每日為你帶來各種開發(fā)/運維干貨。
由于最近在做一個域名監(jiān)控網(wǎng)站,需要用到whois功能,于是通過調(diào)用阿里云提供的API,用java開發(fā)了一套Whois解析功能,本文主要描述開發(fā)的過程以及遇到的一個大坑!
注:從2018年5月開始,受歐盟最新《通用數(shù)據(jù)保護條例》影響,阿里云Whois已不能查詢到域名注冊人的相關(guān)信息,本文解析Whois結(jié)果時未做修改,僅供參考。
什么是Whois?
阿里云:通過 WHOIS 查詢,可以了解到域名背后的擁有者或擁有機構(gòu),獲取域名注冊聯(lián)系信息,包括注冊人、管理者和技術(shù)聯(lián)系人等信息,還能夠提供該域名的注冊商信息(比如阿里云)、域名狀態(tài)和其他重要日期(注冊日期、過期日期等)。
WHOIS 域名查詢通常被用于多種法律相關(guān)的查詢目的。網(wǎng)絡(luò)管理者通過 WHOIS 數(shù)據(jù)來認(rèn)定和確定問題。
例如,WHOIS 信息可以用來判斷域名用途是否合規(guī),商標(biāo)是否侵權(quán),追蹤生產(chǎn)違法內(nèi)容或參與網(wǎng)絡(luò)詐騙的域名注冊者。
此外,ICANN 的協(xié)議也聲明要保護域名注冊人的利益,禁止他人利用 WHOIS 信息來自動判斷并向特定注冊局或注冊商的用戶名單,推送營銷、詐騙信息或發(fā)送大量垃圾信息等行為。為了避免您的信息被其他人獲取,您還可以開啟域名隱私保護服務(wù)。
作者:簡單來說,就是每個域名在注冊之后都會被記錄下域名擁有者的相關(guān)信息以及域名的注冊信息等等,而whois就是用來查詢域名的這些信息的。(未備案的域名可以開啟域名保護功能,此時whois中是查不到的。)
Whois查詢-中國萬網(wǎng)
我們今天要做的,就是在我們的網(wǎng)站上集成whois功能。
阿里云Whois API
阿里云官方API請點擊:阿里云whois 開發(fā)文檔,下面摘取API部分主要內(nèi)容。
請求方式
http://alidns.aliyuncs.com/?Action=DescribeDomainWhoisInfo
&DomainName=example.com
&<公共請求參數(shù)>
公共請求參數(shù)
公共請求參數(shù)是指每個接口都需要使用到的請求參數(shù)。
| 名稱 | 類型 | 是否必須 | 描述 |
|---|---|---|---|
| Format | String | 否 | 返回值的類型,支持JSON與XML。默認(rèn)為XML |
| Version | String | 是 | API版本號,為日期形式:YYYY-MM-DD,本版本對應(yīng)為2015-01-09
|
| AccessKeyId | String | 是 | 阿里云頒發(fā)給用戶的訪問服務(wù)所用的密鑰ID |
| Signature | String | 是 | 簽名結(jié)果串,關(guān)于簽名的計算方法,請參見 簽名機制。 |
| SignatureMethod | String | 是 | 簽名方式,目前支持HMAC-SHA1 |
| Timestamp | String | 是 | 請求的時間戳。日期格式按照ISO8601標(biāo)準(zhǔn)表示,并需要使用UTC時間。格式為YYYY-MM-DDThh:mm:ssZ 例如,2015-01-09T12:00:00Z(為UTC時間2015年1月9日12點0分0秒) |
| SignatureVersion | String | 是 | 簽名算法版本,目前版本是1.0 |
| SignatureNonce | String | 是 | 唯一隨機數(shù),用于防止網(wǎng)絡(luò)重放攻擊。用戶在不同請求間要使用不同的隨機數(shù)值 |
返回參數(shù)
| 名稱 | 類型 | 描述 |
|---|---|---|
| RequestId | String | 唯一請求識別碼 |
| StatusList | DomainStatusType | 域名狀態(tài)列表 |
| DnsServers | DnsServerType | 域名當(dāng)前使用的DNS列表 |
| RegistrantName | String | 所有者 |
| RegistrantEmail | String | 所有者聯(lián)系郵箱 |
| 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調(diào)用API
1、請求參數(shù)的構(gòu)建
首先創(chuàng)建請求參數(shù)集合,將API中需要的參數(shù)都放進去:
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版本,由于只發(fā)現(xiàn)了這一個可用的版本號,就用了這個版本
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日期,需要進行日期格式的轉(zhuǎn)換:
/**
* 獲取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、數(shù)字簽名(Signature)的生成
數(shù)字簽名是API請求的參數(shù)之一,生成簽名也是訪問API最為復(fù)雜的一步。數(shù)字簽名生成規(guī)則請參考:簽名機制
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版本,由于只發(fā)現(xiàn)了這一個可用的版本號,就用了這個版本
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);
/********** 以下過程為生成數(shù)字簽名過程 **********/
// url參數(shù)序列化
String urlParams = CommonUtils.formDataOrderSerialize(map);
String encodeUrlParam = URLEncoder.encode(urlParams, "utf-8");
// 阿里云算法修正,解決TimeStamp冒號被轉(zhuǎn)義成%253A的問題。
// 這是個大坑,阿里云自己的算法在進行encode字符串時,冒號被錯誤的轉(zhuǎn)換成了%253A,正確應(yīng)為%3A,導(dǎo)致發(fā)送請求時總是提示失??!
// 最終還是靠著本博主堅強的毅力一個字符一個字符對出來的。此處只能將錯就錯,把%3A替換為%253A,這樣才能和阿里云的計算結(jié)果對上號。
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);
表單參數(shù)序列化:
/**
* 表單參數(shù)序列化(依據(jù)阿里云的規(guī)則)
*
* @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、發(fā)送請求
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;
發(fā)送請求并解析返回字符串,最終得到一個包含域名whois新的map,然后處理下,在頁面上展示就OK了。