YAMAGUCHI::weblog

土足で窓から失礼いたします。今日からあなたの息子になります。 当年とって92歳、下町の発明王、エジソンです。

WSSE認証を利用したAtom APIとか

動機

いろんなWeb APIを触ってみようと思った。

できた物

WSSE認証

このへんを見ながらちょこちょこと作り始めました。

送信プロトコル

WSSE認証をする場合はまずサービスのエンドポイントに特定のHTTPリクエストを送る必要があります。PythonでHTTPリクエストを送る場合にはhttplibのHTTPConnectionメソッドを使います。
HTTPリクエストのHTTPヘッダにはX-WSSEプロパティにbase64エンコードした各ユーザ名、パスワードなどを送信する必要があります。

  • リクエスト

GET /_atom/blog HTTP/1.1 X-WSSE: UsernameToken Username="user_name", PasswordDigest="fSzKHSagDuwiAWR092gIteMLgwo=", Nonce="MmtxM2Z2aTV0djQx", Created="2005-06-04T13:44:30Z" Host: blog.so-net.ne.jp

プロパティのエンコード方法とかはこんな感じ

Username -- ユーザー名。(はてなフォトライフAPIでははてなアカウントのid)
Nonce -- HTTPリクエスト毎に生成したセキュリティ・トークン*1
Created -- Nonceが作成された日時をISO-8601表記で記述したもの
PasswordDigest -- Nonce, Created, パスワード(はてなアカウントのパスワード)を文字列連結しSHA1アルゴリズムでダイジェスト化して生成された文字列を、Base64エンコードした文字列

WSSEプロパティ作成部分のコードはこんな感じ

def createHeaderToken(userid, password):
    nonce = sha.sha(str(time.time() + random.random())).digest()
    nonce64 = base64.encodestring(nonce).strip()

    created = datetime.datetime.now().isoformat() + 'Z'

    passdigest = sha.sha(nonce + created + password).digest()
    pass64 = base64.encodestring(passdigest).strip()

    wsse = 'UsernameToken Username="%(u)s", PasswordDigest="%(p)s", Nonce="%(n)s", Created="%(c)s"'
    value = dict(u = userid, p = pass64, n = nonce64, c = created)
    
    return wsse % value

その後エンドポイントにリクエストを送ります。その部分はこんな感じ。

def atomRequest(self, wsse, method, endpoint, body, content_type):
    header_info = {'X-WSSE': wsse,
                   'Content-Type': content_type,
                   'Authorization': 'WSSE profile="UsernameToken"',
                   'User-Agent': 'Python WSSE'}
        
    conninfo = urllib.splittype(endpoint)
    conntypeinfo = conninfo[0]
    connhostinfo = urllib.splithost(conninfo[1])
        
    conn = httplib.HTTPConnection(connhostinfo[0])
    conn.request(method, connhostinfo[1], body, header_info)
    r = conn.getresponse()
    if r.status in [200, 201]:
        raise Exception('login failure')
    response = dict(status = r.status,
                    reason = r.reason,
                    data = r.read())
    conn.close()
    return response

基本的な流れとしては

  1. ルートエンドポイントにHTTPリクエストを送る
  2. サービスエンドポイントのURLをHTTPレスポンスによって得る
  3. サービスエンドポイントに各種メソッド(GET/POST/PUT/DELETE)を使って必要なデータを送信する

という感じです。上記のatomRequest()はとりあえずHTTPリクエストを送って、HTTPレスポンスを得る部分でのイディオムとなっています。