Gem 怎么在 Grape 中集成 Devise 的验证?

qichunren · 2012年01月09日 · 最后由 a805717453 回复于 2014年03月27日 · 9740 次阅读

我想给 ruby-china 做一个 iOS 客户端。我是使用了 intridea 做的 grape https://github.com/intridea/grape 这个 gem 来构建 API 支持的,由于 ruby-china 是使用了 devise 1.5.2 https://github.com/plataformatec/devise 这个 gem 来做用户注册和登录验证的,感觉它封装很厉害,或者我没有搞懂,我都动都动不了啊。

现在我怎么样将 devise 的验证支持加到 api 中去呢?

我了解到 Devise 对 http header basic auth 和 token_authentication_key 都是支持的,介但是 ruby-china 的 devise 配置文件中是没有开启的,虽然代码注释中是打开的。

http://ruby-china.org/topics/612

这是我提交的一点代码,对其中的空的方法寻求帮助。

https://github.com/qichunren/ruby-china/commit/b7bb58b7f7d6191aac922b3c80fd8b1b494e9f24

这是测试验证的代码:

require "rubygems"
require 'active_support'
require 'net/http'

Net::HTTP.start('localhost', 3001) {|http|

  basic =  { "HTTP_AUTHORIZATION" => "Basic #{ActiveSupport::Base64.encode64("your_login_id:your_password")}" }

  req = Net::HTTP::Get.new('/api/v1/users/me')
  #req.basic_auth 'login_id', 'password'
  response = http.request(req)
  print response.body
}

我的想法是像 rubygems github 的 api 那样,写个 filter 来判断 http header 中有没 Authorization,如果有 User.find_by_token_authentication_key,如果 match 就 赋值 current_user,大概意思是这样

config.ru

require "grape"
require "pry"
class API < Grape::API
  version 'v1', :using => :header, :format => :json
  helpers do
    def current_user
      #binding.pry
      @current_user ||= (env["HTTP_AUTHORIZATION"] == "xxx" ? "vkill" : nil)
    end
    def authenticate!
      error!('401 Unauthorized', 401) unless current_user
    end
  end
  resource :account do
    get '/private' do
      authenticate!
      @current_user
    end
  end
end

run API

curl test

➜  ~  curl -H 'Authorization:xxx'  http://127.0.0.1:9292/v1/account/private
"vkill"#
➜  ~  curl -H 'Authorization:yyy'  http://127.0.0.1:9292/v1/account/private
401 Unauthorized#

我是用 devise 的 single access token, 做 xAuth

devise  :token_authenticatable
config.token_authentication_key = :single_access_token

然后拿客户端传入的 env['HTTP_X_USER_ACCESS_TOKEN'] 和 devise 的 authentication_token 做比较

#3 楼 @allenwei 你可以再说详细一点吗?

#2 楼 @vkill 你的代码我看懂了,就是一个 key 来判断登录的,现在怎么样与 ruby-china 的 devise 集成呢?这是问题的关键啊。

#5 楼 @qichunren 你写的 api 不是 mount 到 ruby-china 吗?如果是 mount 到 ruby-china 的话,那是完全脱离 ruby-china 原来的后台的,那么也就是说和原来的 devise 没一点点关系,你只用 User 里的 token_authentication_key 这个字段就是了,不知道我说清楚没有呢?

  1. 加一个 devise 的 module token_authenticatable
  2. 加一个 before filter 到 user model, before_save :ensure_authentication_token, 这个是 devise 提供的 3.在 devise 的 initializer 里面加入 config.token_authentication_key = :single_access_token
  3. 定义一些 helper
helpers do
  def warden
    env['warden'].params[:single_access_token] = env["HTTP_X_USER_ACCESS_TOKEN"]
    env['warden']
  end

  def current_user
    @current_user ||= env["HTTP_X_USER_ACCESS_TOKEN"].nil? ? nil : User.find_by_authentication_token(env["HTTP_X_USER_ACCESS_TOKEN"])
  end

  def authenticate!
    unless current_user
      logger.debug "authenticate fail with HTTP_X_USER_ACCESS_TOKEN #{env['HTTP_X_USER_ACCESS_TOKEN']} "
      raise_401
    end
  end
end
  1. 定义一个 api,让用户得到 token post "/login" do warden.logout if user = warden.authenticate(:scope => :user) user.ensure_authentication_token! {:user => {:display_name => user.display_name, :single_access_token => user.authentication_token, :updated_at => user.updated_at, :id => user.id }} else raise_401 end end

#7 楼 @allenwei 非常感谢你的帮助,现在可以使用 single_access_token 得到用前登录用户访问授权的 api 了,但是,你说的定义一个 api,让用户得到 token 这一步我还是不能搞定,我是这样做的: curl -d "user[login]=mylogin&user[password]=mypassword" http://localhost:3000/api/v1/login
返回结果是执行 post "/login"中的 else. 验证不成功,得不到 authentication_token. 我能肯定我输入的用户名和密码是正确的。

warden.authenticate(:scope => :user) 方法我看源代码,没有看明白

#8 楼 @qichunren 不知道具体的问题是什么,我也是看了半天源码研究出来的,你可以再看看 warden 的代码 `

#8 楼 @qichunren 在 warden.logout 后面加一行 warden.params[:controller] = "sessions" 试试

试了,还是搞不出来,授权验证通不过,取 authentication_token 这一步放到 rails app 中去搞算了,其它的 api 放在 grape 中来实现。

#11 楼 @qichunren 没帮到你,最近比较忙,改天有时间,我写个 demo 出来

@allenwei 最近有时间写一个关于这个的 demo 吗?呵呵

@qichunren devise 和 Grape 集成的问题,解决了吗?呵呵~~偶也遇到了。

解决方法如下

post "/login" do
  request.env['devise.allow_params_authentication'] = true
  if user = warden.authenticate(:scope => :user)
    user.ensure_authentication_token!
    {:user => {:display_name => user.display_name, :single_access_token => user.authentication_token, :updated_at => user.updated_at, :id => user.id }}
  else
    raise_401
  end
end
16 楼 已删除
需要 登录 后方可回复, 如果你还没有账号请 注册新账号