登录Web App服务器的用户的API身份验证

时间:2013-12-19 19:08:47

标签: ruby-on-rails api authentication ruby-on-rails-4

我正在构建一个Web应用程序和一个单独的API(以便用户可以使用Ruby on Rails与他人共享他们收集的数据)。用户可以登录Web应用程序并填写应发布到API服务器的数据。

从我读过的所有内容到现在,我想我可以使用基于cookie的身份验证来检查用户是否登录到Web App。现在让我们假设用户想要将数据发布到API服务器。由于用户已通过Web App Server进行身份验证,因此应如何进行发布请求,以便API知道它正在从登录的特定用户获取数据。此外,如果用户想要从API获取数据,对他/她来说是私人的,应该如何为此目的提出请求?

5 个答案:

答案 0 :(得分:12)

您可以考虑使用门卫宝石进行API授权。我考虑过它,但由于复杂性和我的用例缺乏文档而决定反对它。简而言之,我无法让它正常工作。

使用没有设计的warden进行身份验证时有一个很好的article,这可以让您对身份验证系统的移动部分有一个良好的感觉。 Devise不适合API身份验证,事实上Devise最近删除了一个对基于令牌的身份验证API有用的东西,显然API不是他们路线图的一部分!

我使用上面引用的文章中的指南来创建我自己的仅JSON Warden策略,该策略使用OAUTH 2所有者密码凭据授权类型(请参阅RFC 6749)来生成并返回承载令牌以供将来的API使用要求。 API客户端可以轻松创建JSON以执行此类身份验证以获取授权访问令牌。

我将提供一些Rails代码以帮助您从下面开始,但您必须集成到您的特定环境中。不提供保修:)

Warden初始化程序:

# config/initializers/warden.rb
Dir["./app/strategies/warden/*.rb"].each { |file| require file }

Rails.application.config.middleware.insert_after ActionDispatch::ParamsParser, Warden::Manager do |manager|
  manager.default_strategies :null_auth, :oauth_access_token, :oauth_owner_password
  manager.failure_app = UnauthorizedController
end

OAUTH 2密码认证的Warden策略:

# app/strategies/warden/oauth_owner_password_strategy.rb
module Warden
  class OauthOwnerPasswordStrategy < Strategies::Base
    def valid?
      return false if request.get?

      params['grant_type'] == 'password' && params['client_id'] == 'web' && ! params['username'].blank?
    end

    def authenticate!
      user = User.with_login(params['username']).first
      if user.nil? || user.confirmed_at.nil? || ! user.authenticate!(params['password'])
        # delay failures for up to 20ms to thwart timing based attacks
        sleep(SecureRandom.random_number(20) / 1000.0)
        fail! :message => 'strategies.password.failed'
      else
        success! user, store: false
      end

      # ADD HERE: log IP and timestamp of all authentication attempts
    end
  end

  Strategies.add(:oauth_owner_password, OauthOwnerPasswordStrategy)
end

OAUTH 2访问令牌身份验证的Warden策略:

# app/strategies/warden/oauth_access_token_strategy.rb
module Warden
  class OauthAccessTokenStrategy < Strategies::Base
    def valid?
      # must be a bearer token
      return false unless auth_header = request.headers['authorization']
      auth_header.split(' ')[0] == 'Bearer'
    end

    def authenticate!
      # Use a periodic cleaner instead
      # clean out all old tokens. DOES NOT RUN CALLBACKS!
      Token.expired.delete

      # lookup bearer token
      token = Token.active.first(purpose: 'access', token: request.headers['authorization'].split(' ')[1])
      if token && (user = token.user) && user.confirmed_at
        success! user, store: false
      else
        # delay failures for up to 20ms to thwart timing based attacks
        sleep(SecureRandom.random_number(20) / 1000.0)
        fail! message: 'strategies.oauth_access_token.failed'
      end
    end
  end

  Strategies.add(:oauth_access_token, OauthAccessTokenStrategy)
end

空认证策略(在开发中很有用,只需在config / environments / development.rb中设置config.null_auth_user):

# app/strategies/warden/null_auth_strategy.rb
module Warden
  class NullAuthStrategy < Strategies::Base
    def valid?
      ! Rails.configuration.null_auth_user.blank?
    end

    def authenticate!
      user = User.with_login(params["username"]||Rails.configuration.null_auth_user).first
      if user.nil?
        fail! :message => "strategies.password.failed"
      else
        success! user, store: false
      end
    end
  end

  Strategies.add(:null_auth, NullAuthStrategy)
end

JSON客户端的Warden failure应用程序(使用裸机rails控制器):

# app/controllers/unauthorized_controller.rb
class UnauthorizedController < ActionController::Metal

  def self.call(env)
    @respond ||= action(:respond)
    @respond.call(env)
  end

  def respond(env)
    self.status = 401
    self.content_type = 'json'
    self.response_body = { 'errors' => ['Authentication failure']}.to_json
  end
end

在基本API控制器中添加以下内容:

before_filter :authenticate!

protected

    helper_method :warden, :signed_in?, :current_user

    def warden
      request.env['warden']
    end

    def signed_in?
      !current_user.nil?
    end

    def current_user
      @current_user ||= warden.user
    end

    def authenticate!(*args)
      warden.authenticate!(*args)
      # ADD ANY POST AUTHENTICATION SETUP CODE HERE
    end

会话控制器:

class SessionsController < ApiController
  skip_before_filter :authenticate!

  # TODO exceptions and errors should return unauthorized HTTP response.
  # see RFC for details

  def create
    # mandate the password strategy.
    # don't use session store (don't want session cookies on APIs)
    authenticate!(scope: :oauth_owner_password, store: false)

    if signed_in?
      # create access token
      token = Token.create! purpose: 'access',
                            user: current_user,
                            expires_in: Rails.configuration.session_lifetime

       # Ensure response is never cached
       response.headers["Cache-Control"] = "no-store"
       response.headers["Pragma"] = "no-cache"
       response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"

      # send the OAuth response
      render json: {
          access_token: token.token,
          token_type: 'Bearer',
          expires_in: token.expires_in,
          scope: 'user'
      }
    end
  end

  def destroy
    Token.current.delete
    warden.logout
    head :no_content
  end
end

您需要定义自己的用户和令牌模型以分别跟踪用户和承载令牌,令牌模型需要有一个名为active的范围,以将结果集限制为未过期的令牌。令牌生成应使用SecureRandom.urlsafe_base64

答案 1 :(得分:8)

当您说Web应用服务器和单独的API服务器时,每当Web应用服务器上的用户发生更新时,它们都需要相互通信。我只能建议你将它们分解为3个实体作为rails引擎。

  1. 核心:哪个将包含您的所有模型和数据逻辑。
  2. 应用程序:这取决于您的核心引擎,并且面向客户端的代码,主要是控制器和视图。
  3. API:它将再次依赖于您的核心引擎并具有处理逻辑,API控制器可能。
  4. 为何选择核心?因为,当您需要更新业务逻辑时,它只是一个地方:核心引擎。

    现在进一步回答您关于从您的网络应用服务器验证API调用的问题。你需要:

    1. 构建API - Rails CastBuilding Awesome Rails APIS from Collective Idea Blog
    2. 保护API - Rails CastLooking for suggestions for building a secure REST API within Ruby on Rails
    3. 我更喜欢OAuth来保护API调用。要在rails中实施OAuth2,您可以使用doorkeeper
    4. 完成API保护后,您可以在Web应用程序中实现身份验证逻辑。您可以使用OAuth2从API验证您的应用。

      此外,要使用门卫:https://doorkeeper-provider.herokuapp.com/#client-applications

      使您的API仅用于OAuth调用

      P.S。:我更喜欢来自API的json响应,这是我所说的首选趋势。 ;)

      编辑 postman是一个chrome扩展,用于在您为应用程序实际编写实验/假API之前制作它们。它的速度要快得多,因为你知道在一天结束时你最终需要设计什么。

答案 2 :(得分:5)

通常它的工作原理如下。您的应用为每个用户发出一个秘密令牌(例如,它可以是md5哈希,它很长,而且非常随机)。令牌应该由用户保持安全。您可以遵循以下两条规则来实现: - 永远不公开披露令牌(所有请求都应该来自后端,没有AJAX呼叫等) - 所有请求都应通过https进行,因此它们是加密的

使用令牌而不是用户名&amp;的原因密码?如果令牌被泄露,您可以撤销它,用户仍然可以控制他们的帐户。此外,使用基于令牌的身份验证不应该执行某些操作,例如更改与帐户关联的电子邮件或密码。

令牌应作为参数传递给每个请求API。

答案 3 :(得分:0)

如果要构建基于xml的json,请使用rabl gem https://github.com/nesquena/rabl

为了更简单的身份验证,请进行基于会话的身份验证,这些身份验证在控制器中随会话变量提如果你想要用户的整洁,一些功能,那么去authologic gem https://github.com/binarylogic/authlogic

如果您想要完整的用户管理,那么请去设计宝石。

答案 4 :(得分:-3)

Rails使用会话来跟踪用户的状态,该状态存储在用户的cookie中。

可以找到会话文档here

如果您利用Devise等身份验证系统,您可以访问控制器中的current_user方法以及可以利用的大量不同帮助程序,具体取决于您的具体需求

相关问题