21、全面解析用户认证系统:登录、休眠与唤醒流程

全面解析用户认证系统:登录、休眠与唤醒流程

在Web应用开发中,用户认证是至关重要的一环,它涉及到用户的登录、登出、账户激活等多个方面。本文将详细介绍一个用户认证系统的实现,包括登录表单的创建、认证流程的处理、用户休眠与唤醒机制等内容。

登录表单与认证流程

首先,我们来看登录表单的创建。使用 SimpleForm 来创建登录表单,代码如下:

= simple_form_for(@form, url: sessions_sign_in_path, as: :session) do |f|
  = f.input :email
  = f.input :password
  = f.button :submit

这里需要对 SimpleForm 进行一些配置,指定表单的 url as 选项,确保表单字段被正确处理。

当用户提交表单时,表单数据会被发送到 SessionsController sign_in 方法。该方法会调用 Session::SignIn 操作来处理登录逻辑:

class SessionsController < ApplicationController
  def sign_in
    run Session::SignIn do |op|
      tyrant.sign_in!(op.model)
      return redirect_to root_path
    end
    render action: :sign_in_form
  end
end

如果操作成功,会使用 tyrant 对象进行登录操作,并将用户重定向到主页;如果操作失败,则会重新显示登录表单。

接下来,我们深入了解 Session::SignIn 操作的实现。该操作包含一个合约,用于验证用户输入的邮箱和密码:

class SignIn < Trailblazer::Operation
  contract do
    property :email, virtual: true
    property :password, virtual: true
    validates :email, :password, presence: true
    validate :password_ok?

    attr_reader :user

    private
    def password_ok?
      return if email.blank? or password.blank?
      @user = User.find_by(email: email)
      errors.add(:password, "Wrong password.") unless @user and Tyrant::Authenticatable.new(@user).digest?(password)
    end
  end

  def process(params)
    validate(params[:session]) do |contract|
      @model = contract.user
    end
  end
end

在合约中,首先对邮箱和密码进行存在性验证,确保用户输入了这两个字段。然后,通过自定义的 password_ok? 方法来验证密码的正确性。该方法会根据用户输入的邮箱从数据库中查找用户对象,并使用 Tyrant::Authenticatable 类的 digest? 方法来验证密码。

process 方法中,会调用 validate 方法来执行验证逻辑。如果验证成功,会将合约中的用户对象赋值给操作的 @model 属性。

全局认证对象 tyrant

为了处理用户的登录和登出操作,我们引入了一个全局的 tyrant 对象。该对象在 ApplicationController 中定义:

class ApplicationController < ActionController::Base
  def tyrant
    Tyrant::Session.new(request.env['warden'])
  end
  helper_method :tyrant
end

tyrant 对象提供了几个公共方法,如 sign_in! sign_out! signed_in? current_user ,用于管理用户的认证状态。这些方法背后使用了全局的 Warden 对象来处理具体的认证逻辑。

SessionsController sign_in 方法中,我们可以看到 tyrant 对象的使用:

run Session::SignIn do |op|
  tyrant.sign_in!(op.model)
  return redirect_to root_path
end

如果 Session::SignIn 操作成功,会调用 tyrant 对象的 sign_in! 方法将用户登录,并将用户重定向到主页。

登录过滤与用户提示

为了避免已登录用户重复登录,我们在 SessionsController 中添加了一个 before_filter

class SessionsController < ApplicationController
  before_filter only: [:sign_in_form, :sign_in] do
    redirect_to root_path if tyrant.signed_in?
  end
end

如果用户已经登录,访问登录表单或登录页面时会被重定向到主页。

为了给用户提供登录状态的提示,我们在导航栏中添加了欢迎消息:

%li
  = link_to "Start discussion!", new_thing_path
- if tyrant.signed_in?
  %li
    = link_to "Hi, #{tyrant.current_user.email}", user_path(tyrant.current_user)
  %li
    = link_to "Sign out", sessions_sign_out_path
- else
  %li
    = link_to "Sign in", sessions_sign_in_form_path
  %li
    = link_to "Sign up", sessions_sign_up_form_path

根据用户的登录状态,显示不同的导航链接。

用户登出操作

用户登出操作非常简单,我们添加了一个路由来处理登出请求:

get "sessions/sign_out"

SessionsController 中,定义了 sign_out 方法:

class SessionsController < ApplicationController
  def sign_out
    run Session::SignOut do
      tyrant.sign_out!
      redirect_to root_path
    end
  end
end

Session::SignOut 操作目前为空,因为登出操作不需要复杂的验证和处理:

class SignOut < Trailblazer::Operation
  def process(params)
  end
end

如果 Session::SignOut 操作成功,会调用 tyrant 对象的 sign_out! 方法将用户登出,并将用户重定向到主页。

登录测试

为了确保登录和登出功能的正确性,我们编写了集成测试。测试代码如下:

class SessionsControllerTest < IntegrationTest
  it do
    visit "sessions/sign_up_form"
    submit_sign_up!("fred@trb.org", "123", "123")
    submit!("fred@trb.org", "123")
    page.must_have_content "Hi, fred@trb.org" # login success.

    # no sign_in screen for logged in.
    visit "/sessions/sign_in_form"
    page.must_have_content "Welcome to Gemgem!"

    click "Sign out"
    page.current_path.must_equal "/"
    page.wont_have_content "Hi, fred@trb.org"
  end
end

测试中,首先进行用户注册和登录操作,然后验证登录成功后页面显示欢迎消息。接着,尝试访问登录页面,验证已登录用户无法再次访问。最后,点击登出链接,验证用户登出后页面不再显示欢迎消息。

用户休眠与唤醒机制

在某些情况下,我们需要将用户标记为休眠状态,并允许他们在需要时唤醒账户。下面我们来详细介绍用户休眠与唤醒机制的实现。

用户休眠

当用户在评论或添加作者时,如果是隐式创建的用户,我们需要将其标记为休眠状态。在 Comment::Create 操作中,添加了一个回调来实现这一功能:

class Comment < ActiveRecord::Base
  class Create < Trailblazer::Operation
    callback do
      on_change :sign_up_sleeping!, property: :user
    end

    def sign_up_sleeping!(comment)
      auth = Tyrant::Authenticatable.new(comment.user.model)
      auth.confirmable!
      auth.sync
    end

    def process(params)
      validate(params[:comment]) do |f|
        dispatch!
        f.save # save comment and user.
      end
    end
  end
end

Thing::Create 操作中,也添加了类似的回调:

class Thing < ActiveRecord::Base
  class Create < Trailblazer::Operation
    callback(:before_save) do
      on_change :upload_image!, property: :file
      collection :users do
        on_add :sign_up_sleeping!
      end
    end

    def sign_up_sleeping!(user)
      return if user.persisted?
      auth = Tyrant::Authenticatable.new(user.model)
      auth.confirmable!
      auth.sync
    end
  end
end

在这两个回调中,会使用 Tyrant::Authenticatable 类的 confirmable! 方法将用户标记为可确认状态(即休眠状态),并通过 sync 方法将状态保存到用户模型中。

用户唤醒

用户可以通过点击邮件中的链接来唤醒休眠账户。我们添加了两个路由来处理唤醒表单和处理唤醒请求:

get "sessions/wake_up_form/:id"
post "sessions/wake_up/:id"

SessionsController 中,使用 before_filter 来验证确认令牌的有效性:

class SessionsController < ApplicationController
  before_filter only: [:wake_up_form] do
    Session::IsConfirmable.reject(params) { redirect_to(root_path) }
  end

  def wake_up_form
    form Session::WakeUp
  end

  def wake_up
    run Session::WakeUp do
      flash[:notice] = "Password changed."
      redirect_to sessions_sign_in_form_path and return
    end
    render :wake_up_form
  end
end

Session::IsConfirmable 操作用于验证确认令牌的有效性:

module Session
  class IsConfirmable < Trailblazer::Operation
    include CRUD
    model User, :find

    def process(params)
      return if Tyrant::Authenticatable.new(model).confirmable?(params[:confirmation_token])
      invalid!
    end
  end
end

Session::WakeUp 操作用于处理用户唤醒请求:

module Session
  class WakeUp < Trailblazer::Operation
    include CRUD
    model User, :find

    contract do
      property :password, virtual: true
      property :confirm_password, virtual: true
      validates :password, :confirm_password, presence: true
      validate :password_ok?

      def password_ok?
        return unless password and confirm_password
        errors.add(:password, "Password mismatch") if password != confirm_password
      end
    end

    def process(params)
      validate(params[:user]) do
        wake_up!
      end
    end

    def wake_up!
      auth = Tyrant::Authenticatable.new(contract.model)
      auth.digest!(contract.password)
      auth.confirmed!
      auth.sync
      contract.save
    end

    attr_reader :confirmation_token
    def setup_params!(params)
      @confirmation_token = params[:confirmation_token]
    end
  end
end

Session::WakeUp 操作的合约中,会验证用户输入的密码和确认密码是否一致。如果验证成功,会调用 wake_up! 方法来设置用户密码并将用户标记为已确认状态(即唤醒状态)。

为了让表单视图能够渲染确认令牌,我们在 Session::WakeUp 操作中添加了 setup_params! 方法,将确认令牌赋值给操作的实例变量,并通过公共读取器暴露给视图。

总结

通过以上步骤,我们实现了一个完整的用户认证系统,包括登录、登出、用户休眠与唤醒等功能。在实现过程中,我们使用了 Trailblazer 框架来组织代码,将业务逻辑封装在操作中,提高了代码的可维护性和可测试性。同时,引入了全局的 tyrant 对象来处理用户的认证状态,避免了操作与底层 HTTP 机制的直接耦合。通过编写集成测试,我们确保了系统的功能正确性。

整个认证系统的流程可以用以下 mermaid 流程图表示:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B(用户访问登录页面):::process
    B --> C{用户提交登录表单}:::decision
    C -->|是| D(调用 Session::SignIn 操作):::process
    D --> E{验证邮箱和密码}:::decision
    E -->|成功| F(调用 tyrant.sign_in! 登录用户):::process
    F --> G(重定向到主页):::process
    E -->|失败| H(重新显示登录表单):::process
    C -->|否| B
    G --> I{用户点击登出链接}:::decision
    I -->|是| J(调用 Session::SignOut 操作):::process
    J --> K(调用 tyrant.sign_out! 登出用户):::process
    K --> L(重定向到主页):::process
    I -->|否| G
    L --> M(用户评论或添加作者):::process
    M --> N(隐式创建用户):::process
    N --> O(标记用户为休眠状态):::process
    O --> P(发送唤醒邮件给用户):::process
    P --> Q{用户点击唤醒链接}:::decision
    Q -->|是| R(调用 Session::IsConfirmable 操作验证令牌):::process
    R --> S{令牌验证成功}:::decision
    S -->|是| T(显示唤醒表单):::process
    T --> U{用户提交唤醒表单}:::decision
    U -->|是| V(调用 Session::WakeUp 操作):::process
    V --> W{验证密码一致性}:::decision
    W -->|成功| X(设置用户密码并唤醒用户):::process
    X --> Y(重定向到登录页面):::process
    W -->|失败| T
    U -->|否| T
    S -->|否| Z(重定向到主页):::process
    Q -->|否| L

这个流程图清晰地展示了用户从登录到登出,再到休眠和唤醒的整个过程,帮助我们更好地理解系统的工作原理。

通过本文的介绍,你可以深入了解用户认证系统的实现细节,并根据实际需求进行扩展和优化。在实际开发中,还可以进一步考虑安全性、性能优化等方面的问题,以提高系统的质量和用户体验。

全面解析用户认证系统:登录、休眠与唤醒流程

技术细节分析

在上述实现的用户认证系统中,有几个关键的技术细节值得深入分析。

表单验证与操作分离

Session::SignIn Session::WakeUp 操作中,我们将表单验证逻辑封装在合约中。这种设计模式使得验证逻辑与业务处理逻辑分离,提高了代码的可维护性和可测试性。例如,在 Session::SignIn 的合约中:

contract do
  property :email, virtual: true
  property :password, virtual: true
  validates :email, :password, presence: true
  validate :password_ok?

  attr_reader :user

  private
  def password_ok?
    return if email.blank? or password.blank?
    @user = User.find_by(email: email)
    errors.add(:password, "Wrong password.") unless @user and Tyrant::Authenticatable.new(@user).digest?(password)
  end
end

这里的 validates 方法用于基本的存在性验证,而自定义的 password_ok? 方法则用于更复杂的密码验证。这种分层验证的方式使得验证逻辑更加清晰。

回调机制的使用

在用户休眠机制中,我们使用了回调机制。在 Comment::Create Thing::Create 操作中,通过 callback 块来触发特定的方法。例如在 Comment::Create 中:

callback do
  on_change :sign_up_sleeping!, property: :user
end

on_change 结合 :property 选项,确保只有当 user 属性发生变化时才会触发 sign_up_sleeping! 方法。这种机制使得代码更加灵活,能够根据不同的业务场景进行定制。

全局认证对象 tyrant 的作用

全局的 tyrant 对象在整个认证系统中起到了关键作用。它封装了与用户认证相关的操作,如 sign_in! sign_out! signed_in? current_user 。通过将这些操作集中在一个对象中,避免了操作与底层 HTTP 机制的直接耦合。例如在 SessionsController sign_in 方法中:

run Session::SignIn do |op|
  tyrant.sign_in!(op.model)
  return redirect_to root_path
end

tyrant 对象的使用使得控制器代码更加简洁,同时也提高了代码的可复用性。

操作步骤总结

为了更清晰地展示整个用户认证系统的操作步骤,我们将其总结如下:

登录流程
  1. 用户访问登录页面。
  2. 用户提交登录表单。
  3. 调用 Session::SignIn 操作。
  4. 验证邮箱和密码:
    • 若成功,调用 tyrant.sign_in! 登录用户,并重定向到主页。
    • 若失败,重新显示登录表单。
登出流程
  1. 用户点击登出链接。
  2. 调用 Session::SignOut 操作。
  3. 调用 tyrant.sign_out! 登出用户,并重定向到主页。
用户休眠流程
  1. 用户评论或添加作者,隐式创建用户。
  2. 标记用户为休眠状态。
  3. 发送唤醒邮件给用户。
用户唤醒流程
  1. 用户点击唤醒链接。
  2. 调用 Session::IsConfirmable 操作验证令牌:
    • 若成功,显示唤醒表单。
    • 若失败,重定向到主页。
  3. 用户提交唤醒表单。
  4. 调用 Session::WakeUp 操作。
  5. 验证密码一致性:
    • 若成功,设置用户密码并唤醒用户,重定向到登录页面。
    • 若失败,重新显示唤醒表单。
代码优化建议

虽然当前的用户认证系统已经实现了基本功能,但仍有一些可以优化的地方。

减少重复代码

Session::SignIn Session::WakeUp 的合约中,都有密码验证的逻辑。可以将这部分逻辑提取到一个单独的模块中,以减少代码重复。例如:

module PasswordValidation
  def password_ok?
    return unless password and confirm_password
    errors.add(:password, "Password mismatch") if password != confirm_password
  end
end

class SignIn < Trailblazer::Operation
  contract do
    include PasswordValidation
    # 其他代码
  end
end

class WakeUp < Trailblazer::Operation
  contract do
    include PasswordValidation
    # 其他代码
  end
end
提高安全性

在实际应用中,需要考虑更多的安全因素。例如,在处理用户密码时,应该使用更安全的加密算法。可以使用 bcrypt 等库来替代现有的密码验证方式。

增强错误处理

在当前的代码中,错误处理相对简单。可以添加更多的错误信息,以便在出现问题时能够更好地调试和定位问题。例如,在 Session::IsConfirmable 操作中,当令牌验证失败时,可以记录详细的错误日志。

总结与展望

通过本文的介绍,我们详细了解了一个完整的用户认证系统的实现,包括登录、登出、用户休眠与唤醒等功能。在实现过程中,我们采用了 Trailblazer 框架,将业务逻辑封装在操作中,提高了代码的可维护性和可测试性。同时,引入了全局的 tyrant 对象来处理用户的认证状态,避免了操作与底层 HTTP 机制的直接耦合。

未来,我们可以进一步扩展这个认证系统,例如添加多因素认证、社交登录等功能。同时,不断优化代码,提高系统的性能和安全性,为用户提供更好的体验。

为了更直观地展示整个系统的功能模块和交互关系,我们可以用以下表格进行总结:
| 功能模块 | 主要操作类 | 关键方法 | 功能描述 |
| — | — | — | — |
| 登录 | Session::SignIn | process password_ok? | 验证用户邮箱和密码,登录用户 |
| 登出 | Session::SignOut | process | 登出用户 |
| 用户休眠 | Comment::Create Thing::Create | sign_up_sleeping! | 标记隐式创建的用户为休眠状态 |
| 用户唤醒 | Session::IsConfirmable Session::WakeUp | process wake_up! | 验证唤醒令牌,设置用户密码并唤醒用户 |

通过这个表格,我们可以更清晰地看到各个功能模块之间的关系和职责分配。

整个用户认证系统的实现是一个复杂而又重要的过程,需要我们不断地优化和完善。希望本文能够为你在开发用户认证系统时提供一些有用的参考和思路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值