如何在Rails 7应用程序中使用Devise设置用户认证

作者选择了Girls Who Code作为Write for Donations计划的一部分进行捐赠。

介绍

Devise宝石是用于Ruby on Rails应用程序的身份验证解决方案;它帮助您在项目中设置生产就绪的用户身份验证,而无需自己处理所有事务。Devise提供许多有用的功能,如处理用户会话,并使用OmniAuth宝石添加对使用OAuth的第三方登录的支持。Devise还带有用于功能的内置模块,如重置忘记的密码、跟踪登录次数和时间戳、定义超时、锁定帐户等等。

通过使用Devise宝石,用户身份验证变得简单,只需初始化宝石并创建具有所需功能的User模型。如果您要从头开始构建用户身份验证,您将不得不为您想要的所有功能编写代码和测试,并处理处理会话、存储cookie和保持数据安全的所有边缘情况。通过使用Devise宝石,您避免了自己处理所有这些事务,并可以专注于构建您的应用程序。

在本教程中,您将使用Rails创建一个最简Web应用,并安装Devise,这将允许用户创建帐户、登录和退出帐户。

先决条件

要完成本教程,您需要:

步骤 1 —— 创建一个新的 Rails 应用

在这一步中,您将在本地计算机上创建一个新的 Rails 应用并运行它。您将使用 rails 命令行实用程序来初始化项目。

从终端运行以下命令:

  1. rails new blog

rails new 命令将在 blog 目录下创建一个新的 Rails 项目,其中包括许多生成的文件和文件夹。其中之一是 Gemfile,它包含项目的依赖项。您将在 步骤 3 —— 安装和配置 Devise 中配置 Gemfile 来使用 Devise。

注意: 如果您收到一个错误,说 Could not find gem,您可以通过进入项目目录(cd blog)并运行 bundle install 来解决这个问题,这将安装在您的 Gemfile 中列出的所有 gem。

您可以在您喜欢的文本编辑器中打开此目录,或者使用终端导航到它:

  1. cd blog

要启动 Rails 应用程序,请在项目目录中使用 rails server 命令启动开发服务器。

  1. bundle exec rails server

这个命令将启动Rails开发服务器。在浏览器中打开http://localhost:3000以访问Rails欢迎页面。如果您没有提供替代端口号,Rails将使用端口3000来运行应用程序。

**注意:**在命令后添加bundle exec会在当前bundle的上下文中执行它。这意味着只会使用项目特定的Gemfile和其中定义的gem版本。如果您在全局安装了相同gem的不同版本,则这很有用。

您现在已经初始化了一个新的Rails应用程序,在稍后的步骤中将向其中添加用户身份验证。在下一步中,您将使用自定义着陆页替换Rails提供的默认首页,这样一旦您添加了Devise,就更容易浏览示例应用程序。创建新的着陆页后,您将添加用于用户注册和登录到应用程序的链接。

步骤2 —— 创建着陆页

现在您有了一个基本的Rails应用程序,将用自己的着陆页替换Rails提供的默认页面。自定义的着陆页将更容易地在应用程序的根URL上显示用户注册和登录的链接。您将在稍后的步骤中添加登录注册的链接。

要创建您的着陆页,您需要执行以下操作:

  • config/routes.rb 文件中添加路由。
  • 创建一个 HomeController,负责处理这个路由的请求。
  • 创建一个视图文件,在访问该路由时将被渲染。

首先,在创建项目时生成的 routes.rb 文件中添加根路径。

使用 nano 或您喜欢的文本编辑器,打开之前生成的 config/routes.rb 文件:

  1. nano config/routes.rb

添加下划线标记的行:

config/routes.rb
Rails.application.routes.draw do
  root to: "home#index" 
end

root to: 定义了哪个控制器动作将处理根路径的请求 — 在这种情况下,路由将是 http://localhost:3000,这是 Rails 应用的默认端口。该路由的请求将由 home 控制器中的 index 动作处理。这个文件现在不存在,接下来将创建 app/controllers/home_controller.rb 文件。

保存并关闭 config/routes.rb 文件。使用 nano,按下 CTRL+X 退出,Y 保存,ENTER 确认文件名并关闭文件。

接下来,创建 app/controllers/home_controller.rb 文件,并添加以下行:

app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
    render
  end
end

这是一个基本的 HomeController,带有一个 index 方法,它只做一件事:渲染与控制器动作相关联的视图文件。

在这种情况下,视图文件将是app/views/home/index.html.erb文件。您需要创建此文件以及app/views目录中的home目录。

保存并关闭home控制器文件。

接下来,在app/views目录中创建home目录:

  1. mkdir app/views/home/

home目录将保存特定Rails控制器的所有视图。

然后,创建app/views/home/index.html.erb文件并添加以下行:

app/views/home/index.html.erb
<h1>Hello DigitalOcean!</h1>

app/views/home/index.html.erb视图文件Home控制器的index操作将呈现该文件。这是一个HTML文件,您可以在其中嵌入Ruby代码。当为特定控制器操作定义路由时,此视图文件将在用户的浏览器中呈现。

保存并关闭您的文件。

要查看根URL的更改,请在浏览器中打开http://localhost:3000(或者如果已经打开,请刷新页面)。更新后的首页将类似于:

如果需要,您可以进一步自定义此页面,但这就是本教程所需的全部。

现在,您有了一个简单的Rails应用程序及其自己的首页,您将使用Devise gem添加用户身份验证。

步骤 3 — 安装和配置 Devise

在这一步中,您将在Rails应用程序中安装和配置Devise,以便您可以使用该 gem 提供的方法和辅助函数。您将使用 user_signed_in? 方法来检查存储在浏览器 cookie 中的任何已登录用户的会话信息。您还将使用 current_user 辅助函数来获取当前已登录帐户的详细信息。这两种方法都是内置在 Devise 中的,您可以直接在应用程序中使用它们,而无需编写额外的代码。您可以从 Devise 项目的 GitHub 页面了解更多有关这些辅助方法的信息。

安装 Devise 的第一步是将 gem 添加到 Gemfile 中,其中包含运行您的 Ruby 项目所需的所有依赖关系的信息。在这种情况下,当您初始化 Rails 应用程序时,生成的 Gemfile 已经包含了运行 Rails 所需的所有基本 gem。

但在对 Gemfile 进行更改之前,请通过在运行开发服务器的终端中按下 CTRL+C 来停止上一步中启动的开发服务器。

然后,打开要进行编辑的 Gemfile。要添加 Devise gem,请将突出显示的行添加到文件的末尾,但在 developmenttest 组之外:

Gemfile

# ...

# 通过缓存减少启动时间;在config/boot.rb中需要
gem "bootsnap", require: false

# 使用Active Storage变体 [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
# gem "image_processing", "~> 1.2"

gem "devise" 

group :development, :test do
  # 参见 https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
  gem "debug", platforms: %i[ mri mingw x64_mingw ]
end

# ...

保存并关闭文件。

接下来,您将通过在终端中运行bundle install命令来安装新添加的gem。从您的项目目录(blog)中,运行以下命令:

  1. bundle install

此命令将在您的项目中安装Devise gem,这将允许您使用devise命令与rails命令行实用程序,并配置身份验证。

要在您的项目中设置Devise,请运行生成器命令:

  1. bundle exec rails g devise:install

上述命令中的g标志代表generate,用于调用Rails生成器。生成器将创建可作为起点的文件。您可以阅读Rails指南以获取有关Rails生成器的更多信息。

以前的命令将生成多个文件,包括初始化文件和用于 Devise 的 i18n 本地化文件。下面详细解释的初始化文件用于在首次启动应用程序时配置 Devise。i18n 代表 国际化,这是一种标准,可帮助您在不同语言中运行应用程序。

此时,终端还将打印一些说明,如下所示:

Output
=============================================================================== Depending on your application's configuration some manual setup may be required: 1. Ensure you have defined default url options in your environments files. Here is an example of default_url_options appropriate for a development environment in config/environments/development.rb: config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } In production, :host should be set to the actual host of your application. * Required for all applications. * 2. Ensure you have defined root_url to *something* in your config/routes.rb. For example: root to: "home#index" * Not required for API-only Applications * 3. Ensure you have flash messages in app/views/layouts/application.html.erb. For example: <p class="notice"><%= notice %></p> <p class="alert"><%= alert %></p> * Not required for API-only Applications * 4. You can copy Devise views (for customization) to your app by running: rails g devise:views * Not required * ===============================================================================

尽管此教程不要求手动设置,但稍后在此步骤中将为 noticealert 添加闪烁消息。

您已经完成了 Devise 的安装。接下来,您需要在刚刚生成的 Devise 初始化文件中配置一些内容。

当您运行 devise:install 命令时,您生成了 config/initializers/devise.rb,这是 Devise 的初始化文件。每当启动 Rails 应用程序时,Rails 将加载所有的 gem 和插件,然后加载所有的初始化文件。您可以从这些初始化文件中为应用程序的不同部分配置特定的设置。所有这些初始化文件都位于 config/initializers/ 目录中,这也是 Devise gem 创建其初始化文件的位置。

打开 config/initializers/devise.rb 进行编辑。在文件中,找到 Devise.setup 块并添加以下突出显示的行(Devise.setup 块内可能还有其他代码块,但您可以忽略这些):

config/initializers/devise.rb
Devise.setup do |config|
  # ...

  config.navigational_formats = ['*/*', :html, :turbo_stream]

  # ...
end

此行代码将turbo_stream添加为导航格式。 Turbo Streams 是Turbo的一部分,它允许您发送服务器呈现的HTML并在不使用太多JavaScript的情况下呈现页面。您需要添加这个以使 Devise 4.8.1 与 Rails 7 兼容;否则,您将收到undefined method user_url错误。

保存并关闭文件。

接下来,您还将添加在先前打印的说明中突出显示的通知和警告闪现消息。 alertnotice 标签是界面中显示类似“密码错误”等消息的位置。您始终可以在应用程序中实现自定义警报消息(例如,如果您正在使用带有React的Axios拦截器),但在本教程中,您将完成最基本的Devise设置。

打开 app/views/layouts/application.html.erb 进行编辑。在标签右上方添加"notice""alert"消息的标签,就在<%= yield %>上面:

app/views/layouts/application.html.erb
...
<body>
  <p class="notice"><%= notice %></p> 
  <p class="alert"><%= alert %></p> 
  <%= yield %>
</body>

当视图在浏览器中呈现时,<%= yield %>将被视图文件中的内容替换。在您的视图文件中,您只有p标签。此 yield 标记将被替换为该内容。

保存并关闭您的文件。

在这一步中,您已经在项目中安装并配置了Devise。在下一步中,您将使用Devise为应用程序创建用户模型,并设置用户认证。

步骤4 — 使用Devise创建用户模型

您现在可以使用Devise生成用户模型,这将创建必要的模型文件并生成一个迁移,您可以运行该迁移来在应用程序中创建一个users表。每当有人注册时,您都需要在数据库的users表中创建一个新记录。有了用户模型,您可以从前端视图操纵这些数据库记录。

在这一步中,您将生成一个用户模型,检查默认配置,然后运行迁移来更新您的数据库。

由于Rails是一个模型-视图-控制器(MVC)框架,每个数据库表都有一个与之关联的类,可以用来处理表中的数据。在这种情况下,如果您创建一个users表,您可以使用User模型来执行操作,例如User.firstUser.find_by_email("[email protected]")。您可以通过在Rails中创建一个从ApplicationRecord继承的普通类来创建此模型,但是使用Devise生成用户模型会为您提供许多用于身份验证的方法。

要创建您的 Devise 用户,请运行以下生成器命令:

  1. bundle exec rails g devise user

以下输出将打印到屏幕上:

Output
invoke active_record create db/migrate/20220908152949_devise_create_users.rb create app/models/user.rb invoke test_unit create test/models/user_test.rb create test/fixtures/users.yml insert app/models/user.rb route devise_for :users

输出显示 Devise 生成了多个文件,创建了测试,并添加了路由。第一个文件,db/migrate/20220908152949_devise_create_users.rb,是用于在数据库中创建 users 表的迁移文件。Rails 迁移文件描述了需要在数据库中进行的更改。每个迁移的文件名都将包含一个时间戳,以便 Rails 知道以何种顺序进行这些更改。

Devise 还创建了用户模型文件(app/models/user.rb),以及相应的测试。输出的最后一行指示将路由添加到现有的 config/routes.rb 文件中。Devise 自动添加了所有路由,如 /users/sign_up/users/sign_out,使用 devise_for :users 助手。

在运行迁移文件并在数据库中创建 users 表之前,让我们先审查这些生成的文件。这将帮助您了解 Devise 生成的配置,以便您在运行迁移时知道发生了什么。

首先打开迁移文件(db/migrate/20220908152949_devise_create_users.rb)以审查默认代码:

db/migrate/20220908152949_devise_create_users.rb
# frozen_string_literal: true

class DeviseCreateUsers < ActiveRecord::Migration[7.0]
  def change
    create_table :users do |t|
      ## 数据库认证
      t.string :email,              null: false, default: ""
      t.string :encrypted_password, null: false, default: ""

      ## 可恢复
      t.string   :reset_password_token
      t.datetime :reset_password_sent_at

      ## 可记住
      t.datetime :remember_created_at

      ## 可追踪
      # t.integer  :sign_in_count, default: 0, null: false
      # t.datetime :current_sign_in_at
      # t.datetime :last_sign_in_at
      # t.string   :current_sign_in_ip
      # t.string   :last_sign_in_ip

      ## 可确认
      # t.string   :confirmation_token
      # t.datetime :confirmed_at
      # t.datetime :confirmation_sent_at
      # t.string   :unconfirmed_email # 仅当使用重新确认时

Devise 包含许多有用的选项,比如用于密码重置令牌和上次发送令牌时间的字段,等等。还有用于功能的行,如电子邮件确认、登录失败后锁定用户以及甚至跟踪登录详细信息的行。

由于您不需要进行任何更改,请关闭迁移文件。

创建了User模型文件。该文件将位于app/models/目录中。

打开app/models/user.rb模型文件以查看默认代码:

blog/app/models/user.rb
class User < ApplicationRecord
# 包括默认的Devise模块。其他可用的模块包括:
# :confirmable(需确认),:lockable(锁定),:timeoutable(超时),:trackable(跟踪)和 :omniauthable(OmniAuth)
devise :database_authenticatable, :registerable,
       :recoverable, :rememberable, :validatable
end

A few options are added by Devise to configure how the user model works. The basic modules (database_authenticatable, registerable, recoverable, rememberable, and validatable) are already included. There are some additional options commented out, corresponding to the extra features you saw in the migration file. You can add these modules to the model file and configure the migration, depending on how much functionality you need in your application.

以下是基本模块的功能:

  • database_authenticatable:用户可以使用登录名和密码字段进行身份验证。他们的加密密码将存储在数据库中。
  • registerable:用户可以注册自己的帐户,并可以编辑或删除其帐户。
  • recoverable:用户可以重置密码并恢复其帐户,如果他们忘记了凭据。
  • rememberable:此模块通过在浏览器cookie中保存信息来记住用户的会话。
  • validatable:此模块为用户的电子邮件和密码字段提供验证。(例如,您的应用程序要求密码至少为六个字符,即使您没有在模型中定义任何自定义验证。)

这些基本模块已包含在您刚刚生成的User模型中。您可以在Devise GitHub存储库中找到Devise附带的所有模块的完整列表。

您不需要进行任何更改,因此关闭User模型文件。

另一个更新是,config/routes.rb文件已被修改,添加了一个devise_for行用于users

Rails.application.routes.draw do
  devise_for :users

  root "home#index"
end

这是一个有用的方法,定义了所有与用户身份验证相关的必需路由,如/users/sign_in/users/sign_out/users/password/new。 Devise会为您处理所有这些,并且甚至会使路由文件保持清晰。 如果您想了解如何将devise_for :users添加自动转换为所有这些路由,您可以检查Devise GitHub存储库中该方法的源代码。

您无需在此处进行任何更改,因此关闭config/routes.rb文件。

要了解应用程序定义了哪些路由,可以通过运行以下命令列出它们:

  1. bundle exec rails routes

该命令会打印出所有应用程序路由以及正在处理这些路由的控制器。 在身份验证的情况下,所有这些路由都是由Devise创建的,您无需手动添加它们。

输出会很长,但这里是一小段显示部分路由的摘录:

Prefix Verb URI Pattern Controller#Action
new_user_session GET /users/sign_in(.:format) devise/sessions#new
user_session POST /users/sign_in(.:format) devise/sessions#create
destroy_user_session DELETE /users/sign_out(.:format) devise/sessions#destroy
new_user_password GET /users/password/new(.:format) devise/passwords#new
edit_user_password GET /users/password/edit(.:format) devise/passwords#edit
user_password PATCH /users/password(.:format) devise/passwords#update
PUT /users/password(.:format) devise/passwords#update
POST /users/password(.:format) devise/passwords#create
cancel_user_registration GET /users/cancel(.:format) devise/registrations#cancel

输出中列出的路由是在您的路由文件中包含devise_for :users行时由Devise添加的路由。 这些是用于动作如sign insign upreset password等的路由。

现在您已经审查了Devise生成的文件和配置,您可以使用以下命令运行您在此步骤开始时生成的迁移:

  1. bundle exec rails db:migrate

上述命令将在每个迁移文件中进行所有更改以更新您的数据库。更改需要按顺序进行,就像它们在文件中定义的那样。Rails需要知道迁移应该以何种顺序运行,这就是为什么文件的名称中带有时间戳的原因。

屏幕上将打印出如下输出:

Output
== 20220908152949 DeviseCreateUsers: migrating ================================ -- create_table(:users) -> 0.0040s -- add_index(:users, :email, {:unique=>true}) -> 0.0012s -- add_index(:users, :reset_password_token, {:unique=>true}) -> 0.0011s == 20220908152949 DeviseCreateUsers: migrated (0.0074s) =======================

一旦迁移运行完毕,您的数据库就设置好了。您已经完成了在项目中设置用户身份验证所需的所有步骤。

此时,请重新启动您的Rails服务器:

  1. bundle exec rails server

前面提到的初始化文件仅在Rails启动时加载。您需要重新启动服务器,以便Rails可以加载新的Devise初始化文件并设置所有用户身份验证所需的一切。

在浏览器中转到http://localhost:3000/users/sign_up,您将找到一个注册表单,可以通过输入电子邮件和密码来创建帐户。(在下一步中,您将在登录页上添加注册和登录按钮,以便读者更容易导航到此URL。)

为了测试身份验证,请输入一个测试电子邮件地址,例如[email protected],以及一个密码。

一旦您注册,您将被重定向到根页面,其中显示你好,DigitalOcean!以及一条消息,表示您已成功注册,如下所示:

此注册成功通知呈现在您在application.html.erb文件中添加的<p class="notice"><%= notice %></p>标记中。

此时,您已经在项目中使用Devise配置了用户认证,并使用示例帐户进行了注册。您配置了Devise以满足您应用程序的需求,而Devise生成了路由、视图和控制器,以便实现此用户注册体验。

现在您已确认注册流程按预期工作,您的下一步是将此认证添加到您在步骤 2中创建的登录页面。在下一步中,您将链接注册页面与登录页面,以便用户无需导航到特定的 URL 即可注册,就像您在这里所做的一样。

第 5 步 —— 将认证链接到登录页面

你的项目中已经设置好了所有功能,但是你仍然需要将 Devise 创建的页面与你的登录页面连接起来。在上一步中,你手动访问了 /users/sign_up 页面进行登录。在这一步中,你将通过在你的登录页面上添加必要的链接来将所有页面连接起来。你还将根据用户的登录状态有条件地显示用户登录或退出应用的链接。

你将使用 Devise 中的一些辅助方法来完成这个任务。Devise gem 提供了许多辅助方法,你可以直接使用,而不需要自己实现一切。这使得代码更易于阅读和维护。

你将首先添加代码来检查用户是否已登录。如果他们已登录,登录页面将显示他们的电子邮件以及一个用于退出应用的链接。如果用户未登录,登录页面将显示一个链接,指向登录页面。

打开 app/views/home/index.html.erb 文件进行编辑,并添加以下突出显示的代码行:

app/views/home/index.html.erb
<% if user_signed_in? %> 
 <div> Welcome <%= current_user.email %> </div> 
  <%= button_to "Sign out", destroy_user_session_path, method: :delete %> 
<% else %>
  <%= button_to "Sign in", new_user_session_path %> 
<% end %>
<h1>Hello DigitalOcean!</h1>

user_signed_in? 来自于与 Devise 控制器相关的辅助方法。它检查用户是否已登录,并返回一个布尔值 truefalse。你可以利用这个结果在你的应用程序中编写其他功能,比如在用户已登录时显示用户的账户信息。有关这个辅助方法的更多详细信息,你可以查看 Devise GitHub 仓库中的源代码

`current_user` 是一个 Devise 辅助工具,用于访问当前登录到应用程序的用户的详细信息。例如,如果您使用 `[email protected]` 登录,则 `current_user` 辅助工具将返回 `[email protected]` 的用户模型。因此,当使用 `current_user.email` 时,您将得到 `[email protected]` 作为结果。通过使用 Devise,您无需从头开始实现此逻辑,节省了时间和精力。

最后,通过此代码,您向登录页面添加了登录退出按钮。根据`user_signed_in?` 辅助方法的结果,您将显示使用新添加的登录退出按钮的选项。

您正在使用 `button_to` 方法定义一个将用户导向特定路由的按钮。您还在使用辅助方法获取这些路由:`destroy_user_session_path` 解析为 `/users/sign_out`,`new_user_session_path` 解析为 `/users/sign_in`。(您可以通过运行 `bundle exec rails routes` 查看完整的路由 URL 助手列表,如前面的步骤所述。)

保存并关闭文件。

刷新浏览器中的页面以查看更改。

如果您尚未尝试注册您的应用程序,可以访问/users/sign_in路由,方法是单击页面上的登录按钮。从这里,您可以通过点击底部的注册链接来创建一个新帐户。输入一个测试邮箱,例如[email protected]和一个密码。注册完成后,您将再次被带回到登陆页面。现在,登陆页面显示当前已登录用户的电子邮件地址,以及一个退出按钮,如下所示:

您还会收到一条消息,显示您已成功注册

有了这个,您已成功集成了Devise gem并在您的应用程序中设置了用户认证。

结论

在本教程中,您使用Devise为Rails应用程序添加了用户认证。使用Devise的辅助方法,您创建了一个用户可以创建帐户、注册和退出的应用程序。

为了更好地了解Devise和其他辅助方法,可以查看Devise GitHub存储库上的README文件。作为本教程的下一步,您可以尝试根据用户是否已登录有条件地在页面上渲染“Hello World!”问候语,类似于你好用户名

你可以在DigitalOcean社区的GitHub存储库中找到这个项目的代码。

Source:
https://www.digitalocean.com/community/tutorials/how-to-set-up-user-authentication-with-devise-in-a-rails-7-application