如何在Rails中为非注册用户实现延迟令牌认证?

时间:2013-05-29 04:24:38

标签: ruby-on-rails ruby email authentication devise

更新

我进一步澄清了我在本帖末尾列出的问题。

问题摘要:

我正在尝试通过包含令牌身份验证的电子邮件URL在Devise中实现懒惰(又称软注册)注册。在我的网站上,User has_many :paymentsPayment belongs_to :user。当User创建新Payment时,它包含属性:email,该属性代表新的非注册用户(我将其称为“访客”)。我使用ActionMailer向这位新访客发送电子邮件。

在这封发送的电子邮件中,我希望包含一个带有令牌身份验证的URL(例如http://localhost/index?auth_token=TOKENVALUE),以便访客可以查看和编辑需要身份验证的视图,并专门为他们自定义(基于他们的:email)。访客还应该能够注册为User - 因为我已经拥有他们的电子邮件地址,他们只需要提供密码。

我的进展到目前为止:

  • 我已经实现了某人使用Devise注册该网站的能力,并创建了相关的视图,模型和控制器以对我的Payment模型进行更改
  • 我已经设置了ActionMailer并使用它来发送电子邮件,但尚未设置令牌身份验证,因为我不知道如何根据上面的用例进行操作

相关资源:

/app/models/payment.rb

class Payment < ActiveRecord::Base
  attr_accessible :amount, :description, :email, :frequency, :paid, :user_id
  belongs_to :user

  validates :email, :presence => true, :format => { :with => /.+@.+\..+/i }
  validates :amount, :presence => true
  validates :description, :presence => true
end

/app/models/user.rb

class User < ActiveRecord::Base
  devise :database_authenticatable, :registerable,
         :recoverable, :rememberable, :trackable, :validatable, :confirmable

  # I will also need to add ":token_authenticatable"

  attr_accessible :email, :password, :password_confirmation, :remember_me
  has_many :payments
end

的问题:

  1. 如何让我的User表与创建的新Payments保持同步,以便:email表中创建的任何新Payment自动添加到User表格为:email
  2. 在问题1之后,创建的新User应包含令牌但不包含密码,因为用户尚未注册该站点。我应该使用空白密码,随机生成的字符串或其他来创建新的User吗?在任何一种情况下,我认为他们需要从他们的令牌认证URL进入注册页面,以防止其他人在他们的电子邮件用户名下注册(例如懒惰注册)
  3. 在问题2之后,我认为我需要区分Guest和普通用户,因为一些视图会根据用户类型而改变。除了添加一个有0或1的列来描述两种用户类型之外,是否有一种首选方法?
  4. 如果可能,我的偏好是使用Devise,因为我使用了许多功能。我是RoR的新手,感谢您提出的任何建议!

    编辑:以下是我用于解决上述问题#1的代码,在我的付款控制器中,以防其他人有用

    def create
        @payment = current_user.payments.build(params[:payment])
    
        #Here is the code I added to keep User :email in sync with Payment :email, without token authentication implemented yet
        unless User.find_by_email(params[:payment][:email].downcase)  
          u = User.new({:email => params[:payment][:email].downcase, :password => nil, :password_confirmation => nil })
          u.skip_confirmation!
          u.save(:validate => false)  #skip validation
        end
    
        respond_to do |format|
          if @payment.save
            format.html { redirect_to payments_url, :flash => { notice: 'Payment was successfully created.' } }
            format.json { render json: @payment, status: :created, location: @payment }
          else
            format.html { render action: "new" }
            format.json { render json: @payment.errors, status: :unprocessable_entity }
          end
        end
      end
    

1 个答案:

答案 0 :(得分:2)

当你知道如何很好地使用它们以及你想要做的事情符合框架的设计时,像Devise这样的框架是很棒的。当你开始尝试“弯曲”他们做他们不打算做的事情时,或许他们试图在不完全理解他们如何在内部工作的情况下修补它们,根据我的经验,你只是弄得一团糟。我去过那儿。通常你会被简化为“黑客直到它工作”......这实际上意味着“黑客攻击直到看起来有效,除了十几个微妙的错误,只会在以后出现”。

在这样的情况下,我倾向于“滚动我自己的”登录和身份验证代码,也许会查看Devise源代码以获取创意。

(事实上,我今天在新的Rails应用程序上做到了这一点......花了几个小时的时间,现在一次又一次地检查Devise源代码,编写我的项目实际需要的200行代码。相比之下,设计自己有大约2500行。)

您询问了有关代码“整体策略和结构”的想法。如果您决定实施自己的自定义登录/身份验证代码,它可能会是这样的:

  1. 将一些信息存储在session哈希中,以确定用户是否已登录,以及访客/用户。确保您的会话存储在数据库中,而不是存储在cookie中。
  2. 将一些方法添加到ApplicationController(或混合到Module的{​​{1}})以封装对此登录信息的访问权。
  3. 在需要登录的所有页面上放置ApplicationController,如果用户未登录则会重定向。
  4. 添加一个控制器,通过密码或令牌对用户进行身份验证,并允许他们登录/注销。
  5. 对于密码存储/验证,请使用before_filter gem。在您的用户模型上,您需要一个bcrypt-ruby字符串字段,可能还需要一些方法:

    password_hash
  6. 对于基于令牌的身份验证,请将require 'bcrypt' def password # BCrypt handles generation and storage of salts @password ||= ::BCrypt::Password.new(password_hash) end def password=(password) @password = ::BCrypt::Password.create(password) self.password_hash = @password end def authenticate_by_password(password) self.password == password end 字符串字段添加到您的用户模型(或Guest或其他任何内容......)。使它成为一个独特的领域。您可以使用类似的东西生成唯一的令牌(从Devise借来并稍微修改一下):

    login_token
  7. 然后当用户点击您设置require 'securerandom' def generate_login_token loop do token = SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz') unless User.where(:login_token => token).exists? self.login_token = token self.save! return end end end 的{​​{1}}操作时,请执行以下操作:

    login
  8. 确保在params[:token]字段上放置数据库索引。

    还有别的吗?