如何在不使用Ruby on Rails身份validation的情况下仅将OmniAuth用于来自不同Apis的授权

我想使用OmniAuth同时从Facebook,Twitter和谷歌检索用户access_token和秘密。

我正在使用Devise进行身份validation,我想知道如何在用户登录时请求密钥并将其存储在数据库中以便以后使用。

添加gem文件

gem 'devise' gem 'omniauth' gem 'omniauth-twitter' gem 'omniauth-facebook' gem 'omniauth-linkedin' 

生成迁移和模型

 rails generate devise:install rails generate devise user rails g migration add_name_to_users name:string rails g model identity user:references provider:string uid:string 

应用程序/模型/ identity.rb

 class Identity < ActiveRecord::Base belongs_to :user validates_presence_of :uid, :provider validates_uniqueness_of :uid, :scope => :provider def self.find_for_oauth(auth) find_or_create_by(uid: auth.uid, provider: auth.provider) end end 

应用程序/配置/初始化/ devise.rb

 Devise.setup do |config| ... config.omniauth :facebook, "KEY", "SECRET" config.omniauth :twitter, "KEY", "SECRET" config.omniauth :linked_in, "KEY", "SECRET" ... end 

配置/环境/ [环境] .RB

  # General Settings config.app_domain = 'somedomain.com' # Email config.action_mailer.delivery_method = :smtp config.action_mailer.perform_deliveries = true config.action_mailer.default_url_options = { host: config.app_domain } config.action_mailer.smtp_settings = { address: 'smtp.gmail.com', port: '587', enable_starttls_auto: true, user_name: 'someuser', password: 'somepass', authentication: :plain, domain: 'somedomain.com' } 

配置/ routes.rb中

 devise_for :users, :controllers => { omniauth_callbacks: 'omniauth_callbacks' } 

应用程序/控制器/ omniauth_callbacks_controller.rb

因此,要将帐户与多个提供程序链接,必须在OAuth回调返回时设置current_user会话,并将其传递给User.find_for_oauth。 这可能听起来很复杂,但是连接不同提供商(例如Facebook)所需的所有内容都是在用户已经登录时redirect_to user_omniauth_authorize_path(:facebook)

 class OmniauthCallbacksController < Devise::OmniauthCallbacksController def self.provides_callback_for(provider) class_eval %Q{ def #{provider} @user = User.find_for_oauth(env["omniauth.auth"], current_user) if @user.persisted? sign_in_and_redirect @user, event: :authentication set_flash_message(:notice, :success, kind: "#{provider}".capitalize) if is_navigational_format? else session["devise.#{provider}_data"] = env["omniauth.auth"] redirect_to new_user_registration_url end end } end [:twitter, :facebook, :linked_in].each do |provider| provides_callback_for provider end def after_sign_in_path_for(resource) if resource.email_verified? super resource else finish_signup_path(resource) end end end 

应用程序/模型/ user.rb

 class User < ActiveRecord::Base TEMP_EMAIL_PREFIX = 'change@me' TEMP_EMAIL_REGEX = /\Achange@me/ # Include default devise modules. Others available are: # :lockable, :timeoutable devise :database_authenticatable, :registerable, :confirmable, :recoverable, :rememberable, :trackable, :validatable, :omniauthable validates_format_of :email, :without => TEMP_EMAIL_REGEX, on: :update def self.find_for_oauth(auth, signed_in_resource = nil) # Get the identity and user if they exist identity = Identity.find_for_oauth(auth) # If a signed_in_resource is provided it always overrides the existing user # to prevent the identity being locked with accidentally created accounts. # Note that this may leave zombie accounts (with no associated identity) which # can be cleaned up at a later date. user = signed_in_resource ? signed_in_resource : identity.user # Create the user if needed if user.nil? # Get the existing user by email if the provider gives us a verified email. # If no verified email was provided we assign a temporary email and ask the # user to verify it on the next step via UsersController.finish_signup email_is_verified = auth.info.email && (auth.info.verified || auth.info.verified_email) email = auth.info.email if email_is_verified user = User.where(:email => email).first if email # Create the user if it's a new registration if user.nil? user = User.new( name: auth.extra.raw_info.name, #username: auth.info.nickname || auth.uid, email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com", password: Devise.friendly_token[0,20] ) user.skip_confirmation! user.save! end end # Associate the identity with the user if needed if identity.user != user identity.user = user identity.save! end user end def email_verified? self.email && self.email !~ TEMP_EMAIL_REGEX end end config/routes.rb match '/users/:id/finish_signup' => 'users#finish_signup', via: [:get, :patch], :as => :finish_signup 

应用程序/控制器/ users_controller.rb

 class UsersController < ApplicationController before_action :set_user, only: [:show, :edit, :update, :destroy] ... # GET /users/:id.:format def show # authorize! :read, @user end # GET /users/:id/edit def edit # authorize! :update, @user end # PATCH/PUT /users/:id.:format def update # authorize! :update, @user respond_to do |format| if @user.update(user_params) sign_in(@user == current_user ? @user : current_user, :bypass => true) format.html { redirect_to @user, notice: 'Your profile was successfully updated.' } format.json { head :no_content } else format.html { render action: 'edit' } format.json { render json: @user.errors, status: :unprocessable_entity } end end end # GET/PATCH /users/:id/finish_signup def finish_signup # authorize! :update, @user if request.patch? && params[:user] #&& params[:user][:email] if @user.update(user_params) @user.skip_reconfirmation! sign_in(@user, :bypass => true) redirect_to @user, notice: 'Your profile was successfully updated.' else @show_errors = true end end end # DELETE /users/:id.:format def destroy # authorize! :delete, @user @user.destroy respond_to do |format| format.html { redirect_to root_url } format.json { head :no_content } end end private def set_user @user = User.find(params[:id]) end def user_params accessible = [ :name, :email ] # extend with your own params accessible << [ :password, :password_confirmation ] unless params[:user][:password].blank? params.require(:user).permit(accessible) end end 

应用程序/视图/用户/ finish_signup.html.erb

 

Add Email

<%= form_for(current_user, :as => 'user', :url => finish_signup_path(current_user), :html => { role: 'form'}) do |f| %> <% if @show_errors && current_user.errors.any? %>
<% current_user.errors.full_messages.each do |msg| %> <%= msg %>
<% end %>
<% end %>
<%= f.label :email %>
<%= f.text_field :email, :autofocus => true, :value => '', class: 'form-control input-lg', placeholder: 'Example: email@me.com' %>

Please confirm your email address. No spam.

<%= f.submit 'Continue', :class => 'btn btn-primary' %>
<% end %>

应用程序/控制器/ application_controller.rb

以下方法是可选的,但如果您希望确保用户在访问特定资源之前提供了所有必要信息,则此方法很有用。

您可以在before_filter中使用它,如下所示:before_filter:ensure_signup_complete,only:[:new,:create,:update,:destroy]

 class ApplicationController < ActionController::Base ... def ensure_signup_complete # Ensure we don't go into an infinite loop return if action_name == 'finish_signup' # Redirect to the 'finish_signup' page if the user # email hasn't been verified yet if current_user && !current_user.email_verified? redirect_to finish_signup_path(current_user) end end end