2011年10月10日

Rails2+deviseで認証が必要なページにJSONでやりとりする

今作ってるやつに対してブラウザ以外からアクセスする方法を調べてたのでメモ。

今作ってるやつはRails2なんだけどもdeviseを使ってるから多分Rails3でも同じ感じでできる。

今回はRESTっぽくしたかったのでauthentication_tokenを使う形でやってみました。

まず、Userモデルがそれに対応してなかったので deviseのオプション?の token_authenticatable ってやつをつけてみる。 ってことで

devise :registerable, :database_authenticatable, :recoverable,
       :rememberable, :trackable, :validatable

devise :registerable, :database_authenticatable, :recoverable,
       :rememberable, :trackable, :validatable, :token_authenticatable

に変更

それから、authentication_tokenというカラムを追加するため

$ script/generate migration AddAuthenciationTokenToUsers authentication_token:string

する。 本当はここでこいつにインデックスを張った方がいいみたいだけど面倒だから放置。

んで、マイグレーションを実行

$ rake db:migrate

これでauthentication_tokenというカラムができる。

この属性にそのtokenを入れるには ensure_authentication_tokenを使い 入れ直すには reset_authenciation_tokenを使う。

なんだけどこの二つはインスタンスに値を設定するだけなのでその後に自分でsaveする必要がある。 それが面倒な場合は ensure_authentication_token! や reset_authentication_token! を使う。

適当に使ってみるとこんな感じ。

>> user = User.last
=> #<User id: 1, name: "test", authentication_token: nil>
>> user.ensure_authentication_token!
=> true
>> user.authentication_token
=> "I6C8S-yY4oabGNmigpoU"
>> user.reset_authentication_token!
=> true
>> user.authentication_token
=> "4B9W1ssXNfoJgrDgvSXw"

詳しくは http://rdoc.info/github/plataformatec/devise/master/Devise/Models/TokenAuthenticatable あたりを見るといいと思うよ。 !のメソッドはsave(:validate => false)で保存しているから validationチェックは走らないのが気になるでござる。

んで、こいつをうまく使えばXMLでやりとりが出来るわけだね?

ってことで上で取得したauthentication_tokenを使ってやりとりをしてみる。

まずauth_tokenを投げるJSONファイルを用意。

{
  "auth_token": "4B9W1ssXNfoJgrDgvSXw"
}

こんだけ。こいつを「auth.json」として保存しておく。

$ curl -X GET -H 'Content-Type: application/json' -d @auth_token.json http://localhost:3000/items.json

こうすると普通ならJSONが取得できるはず。 なんだけど、自分の環境の場合これは別のところにredirectさせるように書いていたので

<html><body>You are being <a href="http://localhost:3000/2011/10/10">redirected</a>.</body></html>

こんなんが帰ってきてたり。 なんかHTMLだけど、-vオプションつけてヘッダを見てみたら302になってるからとりあえずいいや。

そんで、今度は投稿するためのところを書く。

newアクションは(若干コード削ってるけど)

def new
  @object = Item.new(params[:item])

  respond_to do |format|
    format.html
    format.xml  { render :xml => @object.to_xml }
    format.json { render :json => @object.to_json }
  end
end

こんな感じ。

そんでnewをJSONで取得するために投げるJSONを用意。

{
  "item": {
    "date": "2011/10/10"
  },
  "auth_token": "4B9W1ssXNfoJgrDgvSXw"
}

自分のやつだとitem[date]ってパラメータを飛ばすようにしているのでこんな感じになった。 普通は上のauth_token.jsonで十分

こいつをget_new.jsonとして保存して

$ curl -X GET -H 'Content-Type: application/json' -d @get_new.json http://localhost:3000/items/new.json

すると

{"item":{"name":null,"comment":null,"created_at":null,"updated_at":null"date":"2011-10-10","user_id":0}}

こんな感じで帰ってくる。

これでどんな属性があるのかわかるので、これを元にpostするJSONを作る。

{"item":
        {
        "name":"json_test",
        "comment":null,
        "created_at":null,
        "updated_at":null,
        "date":"2011-10-10"
        },
 "auth_token": "4B9W1ssXNfoJgrDgvSXw"
}

こういう感じ。 user_idは自動で入れるようにしているから無視しているけど 本当ならnew.jsonの中身に入れちゃいけない気がする。 to_jsonするところで何かしたらいいんだろうけど今回は気にしない。

ちなみにXMLでこのJSONと同じパラメータをそのまま表現する方法は見付からなかったので今回は断念。

んで、こいつを受け取るアクションがcreateなんだけどもそれは

def create
  @object = Item.new(params[:item])

  respond_to do |format|
    if @object.save
      flash[:notice] = I18n.t('flash.notice.created', :model => I18n.t("activerecord.models.item"))
      format.html { redirect_to(@object) }
      format.xml  { render :xml => @object, :status => :created, :location => @object }
      format.json { render :json => @object.to_json, :status => :created, :location => @object }
    else
      format.html { render :action => "new" }
      format.xml  { render :xml => @object.errors, :status => :unprocessable_entity }
      format.json { render :json => @object.errors, :status => :unprocessable_entity }
    end
  end
end

んでこれをpostする。今回は-vもつけるか。

$ curl -v -X POST -H 'Content-Type: application/json' -d @post.json "http://localhost:3000/items.json"
* About to connect() to localhost port 3000 (#0)
*   Trying 127.0.0.1... connected
* Connected to localhost (127.0.0.1) port 3000 (#0)
> POST /items.json HTTP/1.1
> Host: localhost:3000
> Accept: */*
> Content-Type: application/json
> Content-Length: 249
>
< HTTP/1.1 201 Created
< Connection: close
< Date: Mon, 10 Oct 2011 11:54:36 GMT
< Location: http://localhost:3000/items/5
< Content-Type: application/json; charset=utf-8
< X-Runtime: 46
< Content-Length: 212
< Set-Cookie: _hoge_session=BAh7BzoPc2Vzc2lvbl9pZCIlNTUwNDBkYWMxNzIyOGZhM2FkMDRiNWJkNDA2Yjg2Y2UiGXdhcmRlbi51c2VyLnVzZXIua2V5WwdjCVVzZXJpBg%3D%3D--44aa4479150458b4f99cabe676d6f33e2d1b4a10; path=/; HttpOnly
< Cache-Control: no-cache
<
* Closing connection #0
{"item":{"name":"json_test","comment":null,"created_at":"2011-10-10T20:54:36+09:00","updated_at":"2011-10-10T20:54:36+09:00","id":5,"date":"2011-10-10","user_id":1}}

こんな感じ。若干情報抜いているけど。

更新と削除はいいよね。 更新は今のと同じようなJSONを http://localhost:3000/items/5 にPUTできるようにeditやらupdateやらのアクションを書き換えて

$ curl -X PUT -H 'Content-Type: application/json' -d @put.json "http://localhost:3000/items/5.json"

とかしたらいいだけだし、 削除も多分

$ curl -X DELETE -H 'Content-Type: application/json' -d @auth_token.json "http://localhost:3000/items/5.json"

したらいいだけだし。

で、やり残したことがあって、 それは、ログイン、ログアウトをJSONでやりとりして なおかつ、ログイン成功した際にはauth_tokenを受け取るようにするってところ。 今日は疲れたからそれはまた今度。

タグ:rails2 API JSON Devise
posted by 麦汁 at 21:25 | Comment(0) | TrackBack(0) | rails | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:


この記事へのトラックバック