我正在构建一个Web应用程序和一个单独的API(以便用户可以使用Ruby on Rails与他人共享他们收集的数据).用户可以登录Web应用程序并填写应发布到API服务器的数据.
从我读过的所有内容到现在,我想我可以使用基于cookie的身份验证来检查用户是否登录到Web App.现在让我们假设用户想要将数据发布到API服务器.由于用户已通过Web App Server进行身份验证,因此应如何进行发布请求,以便API知道它正在从登录的特定用户获取数据.此外,如果用户想要从API获取数据,对他/她来说是私人的,应该如何为此目的提出请求?
您可以考虑使用门卫宝石进行API授权.我考虑过它,但由于复杂性和我的用例缺乏文档而决定反对它.简单地说,我无法让它正常工作.
有一篇关于使用warden而没有设计的认证的好文章应该让你对认证系统的移动部分有一个良好的感觉.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.null_auth_user
在config/environments/development.rb中设置):
# 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故障应用程序(使用裸机轨道控制器):
# 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
当您说Web应用服务器和单独的API服务器时,每当Web应用服务器上的用户发生更新时,它们都需要相互通信.我可以建议你将它们分解为3个实体作为rails引擎.
核心:哪个将包含您的所有模型和数据逻辑.
应用程序:这将取决于您的核心引擎并具有面向客户端的代码,主要是控制器和视图.
API:这将再次依赖于您的核心引擎并具有处理逻辑,API控制器可能.
为何选择Core?因为,当您需要更新业务逻辑时,它只是一个地方:核心引擎.
现在,进一步回答您关于从Web应用服务器验证API调用的问题.你需要:
从Collective Idea博客构建API - Rails Cast和构建令人敬畏的Rails APIS.
保护API - Rails Cast并寻找在Ruby on Rails中构建安全REST API的建议.
我更喜欢OAuth来保护API调用.要在rails中实现OAuth2,您可以使用门卫.
完成API保护后,您可以在Web应用程序中实现身份验证逻辑.您可以使用OAuth2从API验证您的应用.
此外,要使用门卫使您的API仅可用于OAuth呼叫:https://doorkeeper-provider.herokuapp.com/#client-applications
PS:我更喜欢来自API的json响应,这是我所说的首选趋势.;)
编辑- 邮递员是一款Chrome扩展进行实验性/假的API之前,你实际上是为你的应用程序写出来.它的速度要快得多,因为你知道在一天结束时你最终需要设计什么.
通常它的工作原理如下.您的应用为每个用户发出一个秘密令牌(例如,它可以是md5哈希,它很长,而且非常随机).令牌应该由用户保持安全.您可以通过以下两条规则来实现: - 永远不公开披露令牌(所有请求都应该来自后端,没有AJAX呼叫等) - 所有请求都应该通过https进行,因此它们是加密的
使用令牌而不是用户名和密码的原因?如果令牌被泄露,您可以撤销它,用户仍然可以控制他们的帐户.此外,使用基于令牌的身份验证,不应该执行某些操作,例如更改与帐户关联的电子邮件或密码.
令牌应作为参数传递给每个请求到您的API.