読者です 読者をやめる 読者になる 読者になる

ruby-openidを使ってmixiで認証するテスト

MixiOpenIDプロバイダになったので、遊んでみようと思った。mixiなら大抵アカウント持ってるし、友達に勧める時でもマイミクシィという繋がりがあるので、OpenIDを使ってもらう心理的抵抗が他のプロバイダよりちょっと低いかな、と思う。

まずOpenIDの仕組みがよく分からんのだが、とりあえず認証先URLのhttp://mixi.jp/にアクセスすると、こんなレスポンスヘッダが帰ってくる。

HTTP/1.x 200 OK
Date: Sun, 24 Aug 2008 12:58:04 GMT
Server: Apache
X-Dealer: 034084
Cache-Control: no-cache
Pragma: no-cache
X-XRDS-Location: https://mixi.jp/xrds_server.pl
Vary: Accept-Encoding
Content-Encoding: gzip
Content-Length: 4570
Content-Type: text/html; charset=EUC-JP
Connection: close

X-XRDS-Locationなる見た事のないヘッダが返ってきている。そこに書かれているURLにアクセスしてみると

<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0" xmlns="xri://$xrd*($v*2.0)">
  <XRD>
    <Service priority="0">
      <Type>http://specs.openid.net/auth/2.0/server</Type>
      <Type>http://openid.net/sreg/1.0</Type>
      <Type>http://openid.net/extensions/sreg/1.1</Type>
      <Type>http://openid.net/srv/ax/1.0</Type>
      <URI>https://mixi.jp/openid_server.pl</URI>
    </Service>
  </XRD>
</xrds:XRDS>

こんなXMLが返ってきた。ここに書かれているhttps://mixi.jp/openid_server.plOpenIDログインしようとするとリダイレクトされるURLだ。

このURIに正しい手続きでアクセスすればOpenID利用同意ページが表示される。同意、またはキャンセルをクリックすると元ページにリダイレクトされる。このリダイレクトURLのクエリストリングに認証成功、キャンセル、失敗という情報が付加されて、元サーバに伝わるようだ。

何となく分かったので詳細については後で仕様書を眺めてみる。どういう動きをしているのか分かれば仕様書も読みやすいってもんだ。とりあえず実際にopenid-rubyというライブラリを使って認証するだけのCGIを書いてみた。

#!/usr/local/bin/ruby -Ku

require "cgi"
require "cgi/session"
require "cgi/session/pstore"
require "openid"
require "openid/store/filesystem"
require "erb"


cgi = CGI.new
session = CGI::Session.new(cgi, "database_manager" => CGI::Session::PStore, "tmpdir"=>"./tmp" )
store = OpenID::Store::Filesystem.new('./tmp')
consumer = OpenID::Consumer.new(session, store)


def h str
  ERB::Util.h str
end

def cgi.erb template
  self.out("status" => "OK", "type" => "text/html", "charset" => "utf-8") {
    File.open(template) { |f|
      ERB.new(f.read).result
    }
  }
end

def cgi.redirect url
  puts self.header("status" => "REDIRECT", "Location" => url)
end


flash = ""
selfurl = "http://" + cgi.server_name + ":" + cgi.server_port.to_s + cgi.script_name

if cgi["openid_identifier"].length > 0 
  # cgi['openid_identifier']だと怒られる。理由は前エントリ参照
  id = cgi.params['openid_identifier'][0]  

  # パラメータで認証プロバイダを指定できるが、mixiでやってみるテストなのでmixiに固定する
  id = "mixi.jp"
  
  begin
    openid_auth_req = consumer.begin(id)  
    cgi.redirect openid_auth_req.redirect_url(selfurl , selfurl)
  rescue
    flash = "OpenIDの認証プロバイダが見つからなかったようだ(" + $!.to_s + ")"
    cgi.erb "openid_login.cgi.erb" 
  end

elsif cgi['openid.mode'].length > 0
  params = Hash.new
  cgi.params.each {|key, val| params[key] = val.first }

  openid_res = consumer.complete( params, selfurl )

  case openid_res.status
  when OpenID::Consumer::FAILURE
    flash = "認証できませんでした"
    cgi.erb "openid_login.cgi.erb"
  when OpenID::Consumer::CANCEL
    flash = "えー、ログインしないのー?"
    cgi.erb "openid_login.cgi.erb"
  when OpenID::Consumer::SUCCESS
    session["user"] = openid_res.display_identifier
    cgi.redirect selfurl
  when OpenID::Consumer::SETUP_NEEDED
    flash = "MixiOpenIDでログインしてね"
    cgi.erb "openid_login.cgi.erb"
  else
    flash = "なんかよくわからんエラー?" + openid_res.class.to_s
    cgi.erb "openid_login.cgi.erb"
  end

elsif session["user"].nil?
  flash = "MixiOpenIDでログインするよ"
  cgi.erb "openid_login.cgi.erb"

else
  cgi.erb "openid_auth.cgi.erb"

end

いくつか注意点

  • ruby-openidライブラリはgemを使わずにサーバ上に置いてライブラリパスに追加するだけで動くので、gem使えないショボレンタルサーバでも使える。
  • セッションの保存先はPStoreにする。OpenIDライブラリがRubyオブジェクトをセッションに保存するので。
  • リクエストパラメータでOpenIDプロバイダを指定出来るようにする場合は、cgi['key']で取得してはダメ、cgi.params['key']。内部でstr[0].chrとかやってるから。詳細は前エントリ参照、
  • cgi.erbみたいな特異メソッド作ると俺ってCoolなRubyist?な気分になれる。気分だけ。