[Ruby] 標準ライブラリだけでダイジェスト認証のあるページにアクセスする

  • 2
    いいね
  • 0
    コメント

非常に豊富なクラス・メソッドのあるRubyですが、gemなしでダイジェスト認証をするのが意外と面倒だったので書いて見ます。実はもっと簡単な方法があるのかもしれないです。

大まかな流れ

特にいいメソッドがないので、素直にダイジェスト認証の流れに沿って実装します。
この記事を参考にしました。
Digest認証の挙動

  1. 一旦繋ぐ
  2. 当然弾かれるので、ヘッダからWWW-Authenticateの値を読み出す
  3. 2.の情報を元に、Authorizationを作成。ヘッダに埋めてもう一度送信

実装する

STEP1 WWW-Authenticateの取得

素直にGETなりPOSTを実行します。

app.rb
require 'uri'
require 'net/http'

uri = URI.parse('http://example.com')
http = Net::HTTP.new(uri.host, uri.port)
res1 = http.get(uri.path, '')

auth_info = res1['WWW-Authenticate']

httpのgetpostメソッドはNet::HTTPResponseクラスのオブジェクトを返していて、これはNet::HTTPHeaderを継承しているので、ハッシュを読む要領でヘッダを読み出せます。
Net::HTTPResponseクラスにheaderメソッドがありますが、ヘッダが読めるわけでないので注意しましょう。

STEP2 必要な情報を取得する

WWW-Authenticateが取れたので、必要な情報を抽出します。realmnonceqopを使います。

まずWWW-Authenticateを読み出すと次のような文字列が取れます。
Digest realm="secret", nonce="35BgQjIPBQA=5014d9d750424666921e3e9007bd102dc4d3f2bc", algorithm=MD5, qop="auth"

Digestだけ消して、,で切ってしまうのが良さそうです。

auth_info = res1['WWW-Authenticate'][7..-1].split(', ').map { |v| v.gsub('=').last.gsub('"') }
に最後の行を変えましょう。
ついでに変数に各値を入れておきましょう。

realm = auth_info[0]
nonce = auth_info[1]
qop = auth_info[3]

STEP3 Authorizationを生成する

まず、ユーザ名とrealm、パスワードを:で繋いだものをMD5でハッシュ化します。
require 'digest/md5'を追加しておきましょう。
user_info = Digest::MD5.hexdigest("user:#{realm}:password")
続いてHTTPメソッドとパスを:で繋いでハッシュ化します
http_info = Digest::MD5.hexdigest("get:#{uri.path}")
cnonceと呼ばれるランダムな文字列が必要なので適当に生成します。
cnonce = rand((36 ** 20)...(36 ** 21)).to_s(36)
ncも適当に作成します。
nc = '000000001'
responceと呼ばれるものを作成します。
digest_responce = Digest::MD5.hexdigest("#{user_info}:#{realm}:#{nc}:#{cnonce}:#{qop}:#{http_info}")
Authorizationを作成します。
authorization = %Q(Digest username="#{user_name}", realm="#{realm}", nonce="#{nonce}", uri="#{uri.path}", algorithm=MD5, response="#{digest_responce}", qop=#{qop}, nc=#{nc}, cnonce="#{cnonce}")

ここまでできるとこんな感じになります。

app.rb
require 'uri'
require 'net/http'
require 'digest/md5'

uri = URI.parse('http://example.com')
http = Net::HTTP.new(uri.host, uri.port)
res1 = http.get(uri.path, '')

auth_info = res1['WWW-Authenticate']
realm = auth_info[0]
nonce = auth_info[1]
qop = auth_info[3]

user_info = Digest::MD5.hexdigest("user:#{realm}:password")
http_info = Digest::MD5.hexdigest("get:#{uri.path}")
cnonce = rand((36 ** 20)...(36 ** 21)).to_s(36)
nc = '000000001'
digest_responce = Digest::MD5.hexdigest("#{user_info}:#{realm}:#{nc}:#{cnonce}:#{qop}:#{http_info}")
authorization = %Q(Digest username="#{user_name}", realm="#{realm}", nonce="#{nonce}", uri="#{uri.path}", algorithm=MD5, response="#{digest_responce}", qop=#{qop}, nc=#{nc}, cnonce="#{cnonce}")

STEP3 送信し直す。

あとはAuthorizationヘッダをつけて送るだけです。

res2 = http.post(uri.path, data.to_json, {Authorization: authorization})'
以上

まとめ

標準ライブラリだけでやると結構面倒なので、素直にgem使った方が楽そうです。