我正在尝试使用omniauth设计创建一个rails 4应用程序:
在这里,我可以使用facebook,linkedin,twitter或google帐户登录。但我的问题是:我的谷歌帐户电子邮件和linkedin电子邮件地址是相同的。并使用谷歌登录然后使用linkedin登录就会给我这个错误:
"Validation failed: Email has already been taken"
这是一个问题,因为设计在电子邮件字段的迁移文件中使用:unique => true
。
有人能提出我很好的想法来处理这个错误吗?
答案 0 :(得分:3)
我认为基本上,如果正确处理所有案例,除非您选择下面的选项1,否则这可能非常复杂(即便如此,我还要考虑哪些问题)。对不起这个答案的长度!
我假设你做过这样的事情:
https://github.com/plataformatec/devise/wiki/OmniAuth:-Overview
到目前为止,它可以帮助您解决您遇到的问题。
我这样做的方式,我有一个拥有多个身份的用户。 Identity存储外部服务的名称,它告诉您的用户ID以及您想要的任何其他内容。这意味着同一个用户可以使用多个身份(Twitter,Facebook ...)登录。你见过:
http://railscasts.com/episodes/235-omniauth-part-1?view=asciicast
http://railscasts.com/episodes/236-omniauth-part-2?view=asciicast
有助于使用用户has_many身份,但仍然无法处理您的案件。
然后要解决您的问题,一个选项是检测您遇到的验证错误:
if @user.errors.added?(:email, :taken)
# do whatever you want - e.g. one of the 4 options below.
end
如果发生,你可以:
只需将身份标识添加到与具有相同电子邮件地址的现有用户关联的身份,然后将其登录。
或
在向现有用户添加身份之前,请询问现有用户帐户的密码(如果该帐户最初是通过系统上的设备注册的),那么您需要转到一些新用户控制器/操作和处理此流程的视图。
或
也许发送电子邮件确认(不是设计标准确认)以确认他们正在将新身份链接到现有帐户。这听起来有点复杂,因为您必须暂时将某个身份存储在某个地方(如果您想要在点击确认链接之前处理它们结束当前会话,可能在数据库中),将其标记为未经证实,直到他们点击电子邮件中的确认链接(您还必须处理生成)。
或
或许强制他们使用具有相同电子邮件地址的其他身份进行身份验证。这比以前的选项更有优势,你可以在会话中保存新的身份信息,并让他们立即使用其他服务进行身份验证,但显然会有一些工作来处理那里的流程。
选项1可能不太安全,因为您相信外部服务已经确认了用户的电子邮件地址 - 这可能是他们所拥有的 - 但您希望确定有人可以使用他们的电子邮件地址注册使用他们的电子邮件地址。然后登录到您的网站,然后攻击者可以使用相同的电子邮件地址注册其他服务。然后,如果您未以某种方式确认他们确实拥有该电子邮件地址(例如,使用选项2,3或4),他们就可以访问您网站上该人的帐户。如果外部服务可以确认他们已经验证了电子邮件地址,那么这个选项应该没问题并且是迄今为止最简单的 - 例如,Facebook有一个字段告诉您该帐户已经过验证(但请参阅下面的评论)关于不需要电子邮件地址的服务。如果他们直接与您注册的帐户合并(听起来不像您的情况),您应该使用设计的标准可确认功能确认他们注册的电子邮件地址。
选项2听起来并不适用于您的情况,因为您没有提及用户可以通过设计直接向您注册;仅使用外部服务登录。这意味着他们在您的网站上没有他们所知道的密码。 (你可能添加了一个假的,以便通过设计验证,除非你已经禁用了它,但除非你以某种方式告诉他们,否则他们不会知道密码,而且它可能会让他们混淆这样做。)
选项3听起来可行,但我还没有尝试过。这对用户来说有点费力。
选项4也听起来可行,但我还没有尝试过。
到目前为止,我已经完成了选项2,因为用户只能直接使用设计或通过1个外部服务注册我的网站。我很快就会添加其他服务,所以我打算使用选项4(可能只有在外部服务说他们还没有确认电子邮件地址,否则选项1)。
选项2,3和4比选项1更多的工作,所以它取决于你是否可以确认外部服务已经验证了电子邮件地址,如果没有,你是多么偏执,攻击者能够访问用户您网站上的帐户。就个人而言,我在偏执狂方面犯了错误!
这也可能会为您提供更多有用信息:
https://github.com/intridea/omniauth/wiki/Managing-Multiple-Providers
但是因为omniauth本身并不关心模型问题,所以它主要回避它,尽管它为你的情况说它可能是足够谨慎的假设它们实际上是相同的同时创建了以前用户的人#34;但是你必须能够像我上面提到的那样信任外部服务。
还有其他一些事情需要考虑,例如某人在Facebook上注册了相同的电子邮件地址,并且已经在您的网站上进行了链接,并且已经在您的网站上进行了登录(一旦您处理了你的问题)然后更改与他们的Facebook帐户相关联的电子邮件,而不是更改。如果您始终使用外部服务中的电子邮件覆盖存储在用户表中的电子邮件,那么如果他们使用linkedin和Facebook登录,它就会不断变化(但这对您来说并不重要)。或者,他们可能有不同的电子邮件地址在Facebook注册并链接并在您的网站上登录(因此您的网站上有2个不同的用户),然后他们将其链接的电子邮件地址更改为与Facebook相同。如果您每次通过外部服务登录时更新用户的电子邮件地址,您就可以使用“34”电子邮件"错误,但在这种情况下,您有2个现有用户要合并,这可能很有趣,这取决于您的数据库中的其他内容与用户相关联...
另外,我不认为Twitter会返回一个电子邮件地址,所以如果同一个人使用twitter和linkedin登录,你就不会发现这一点。此外,我认为电子邮件是Facebook的可选项(您可以使用手机号码),因此Facebook也可能发生同样的事情。我理想的解决方案将允许用户合并任意帐户,显然必须输入确认他们拥有他们合并的帐户所需的任何凭据!我还没有做到这一点,但它已经在我的愿望清单上了!
答案 1 :(得分:1)
我按照这些步骤为我工作正常:
<强> 1。的Gemfile 强>
gem 'omniauth-facebook', '1.4.0'
gem 'omniauth-twitter'
gem 'omniauth-google-oauth2'
<强> 2。配置/ route.rb 强>
devise_for :users, controllers: { omniauth_callbacks: "omniauth_callbacks" }
第3。链接强>
<%= link_to "Sign in with Facebook", user_omniauth_authorize_path(:facebook) %>
<%= link_to "Sign in with twitter", user_omniauth_authorize_path(:twitter) %>
<%= link_to "Sign in with google", user_omniauth_authorize_path(:google_oauth2) %>
<强> 4。控制器/ omniauth_callbacks_controller.rb 强>
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
skip_before_filter :authenticate_user!
def all
user = User.from_omniauth(request.env["omniauth.auth"], current_user)
if user.persisted?
flash[:notice] = "you are successfully logged in!!"
sign_in_and_redirect(user)
else
session["devise.user_attributes"] = user.attributes
redirect_to new_user_registration_url
end
end
def failure
super
end
alias_method :facebook, :all
alias_method :twitter, :all
alias_method :google_oauth2, :all
end
<强> 5。添加必填字段和模型
rails g migration add_social_network_info_columns_to_users name image_url locations
# generate new model Authorization
rails g model Authorization user_id:integer provider uid token secret username
<强> 6。模型/ User.rb 强>
class User < ActiveRecord::Base
require 'securerandom'
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable,
:omniauthable
has_many :authorizations
# omniauth facebook provider
def self.from_omniauth(auth, current_user)
# check for existing authorization
# Find or create Authorization with: provider, uid, token and secret
authorization = Authorization.where(
:provider => auth.provider,
:uid => auth.uid.to_s,
:token => auth.credentials.token,
:secret => auth.credentials.secret
).first_or_initialize
if authorization.user.blank?
user = current_user.nil? ? User.where('email = ?', auth["info"]["email"]).first : current_user
# save user related data in user table
if user.blank?
User.new(
:email => auth.info.email,
:password => Devise.friendly_token[0,10],
:name => auth.info.name,
:locations => auth.info.location,
:image_url => auth.info.image
)
# since twitter don't provide email,
# so you need to skip validation for twitter.
auth.provider == "twitter" ? user.save!(:validate => false) : user.save!
end
# store authorization related data in authorization table
authorization.username = auth.info.nickname
authorization.user_id = user.id
authorization.save!
end
authorization.user
end
end
<强> 6。模型/ Authorization.rb 强>
class Authorization < ActiveRecord::Base
belongs_to :user
end