阿里云Whois域名解析開發(fā)

個人博客地址:阿里云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了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 點擊查看原文 Web SDK 開發(fā)手冊 SDK 概述 網(wǎng)易云信 SDK 為 Web 應(yīng)用提供一個完善的 IM 系統(tǒng)...
    layjoy閱讀 14,314評論 0 15
  • 轉(zhuǎn)載自公眾號:freebuf 前言 前段時間,看了一本書名為《Kali Linux 滲透測試的藝術(shù)》,我發(fā)現(xiàn)書中第...
    蒼簡閱讀 2,052評論 0 8
  • 來源:《Detecting APT Malware Infections Basedon Malicious DN...
    Threathunter閱讀 11,062評論 3 9
  • 去年有段時間得空,就把谷歌GAE的API權(quán)威指南看了一遍,收獲頗豐,特別是在自己幾乎獨立開發(fā)了公司的云數(shù)據(jù)中心之后...
    騎單車的勛爵閱讀 21,116評論 0 41
  • 一輩子那么長,我才不會只喜歡你一個人呢!一輩子那么長,沒想到我真的只喜歡你一個人! “我喜歡你”“可我不喜歡你”“...
    曦輪閱讀 248評論 0 0

友情鏈接更多精彩內(nèi)容