Rails 7 Devise认证系统搭建指南:实现安全的用户登录与注册

作者选择将Girls Who Code作为Write for Donations计划的捐赠对象。

介绍

Devise gem是Ruby on Rails应用程序的身份验证解决方案;它帮助您在项目中设置可用于生产环境的用户身份验证,而无需自己完成所有工作。Devise提供许多有用的功能,如处理用户会话以及使用OmniAuth gem支持通过OAuth进行第三方登录。Devise还配备了用于重置密码、跟踪登录次数和时间戳、定义超时时间、锁定账户等功能的内置模块。

使用 Devise 让用户身份验证变得简单,只需要初始化 gem 并创建一个带有所需特性的用户模型即可。如果你从零开始构建用户身份验证,你需要编写代码和测试所有所需的功能,并处理会话管理、存储 cookie 和保证数据安全的各种极限情况。通过使用 Devise gem,你避免了自己处理所有这些问题,并可以专注于构建你的应用程序。

在这个教程中,您将使用Rails创建一个简约的网络应用,并安装Devise,使用户能够创建账户、登录和注销账户。

先决条件

完成本教程,您需要准备以下物品:

  • A local development environment for Ruby on Rails. For Linux, you can follow our tutorial How To Install Ruby on Rails with rvm on Ubuntu 20.04 or use the rvm product docs for installing on Mac or Windows. You can also refer to the Ruby project docs to install Ruby directly in your system. To install the Rails gem, you can use the official Rails documentation. The Devise gem requires Ruby version 2.1.0 or greater; this tutorial was tested using Ruby version 3.0.2 and Rails version 7.0.3.
  • Node.js installed on your machine. A few Rails features, such as the Asset Pipeline, depend on a JavaScript Runtime. Node.js provides this functionality. For Ubuntu, install Node.js using the official PPA, as explained in option 2 of How To Install Node.js on Ubuntu 20.04. For Mac OS, follow our guide How To Install Node.js and Create a Local Development Environment on MacOS.
  • Familiarity with Ruby and the Ruby on Rails framework. You can check out the first few tutorials from our series Rails on Containers, or you can use the official Rails Guides.

第一步 – 创建一个新的Rails应用程序

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

从终端运行以下命令。

  1. rails new blog

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

Note

注意:如果出现“找不到宝石”错误,可以通过切换到项目目录(cd 写作)并运行 bundle install 解决问题,该命令会安装 Gemfile 中列出的所有宝石。

你可以在你喜欢的文本编辑器中打开这个目录,或者使用终端导航到它。

  1. cd blog

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

  1. bundle exec rails server

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

Note

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

你现在初始化了一个新的Rails应用程序,稍后将在其中添加用户认证。在下一步中,您将用自定义的首页取代Rails提供的默认首页,这样在添加Devise后,浏览示例应用程序将更加方便。创建新的首页后,您将为用户添加注册和登录应用程序的链接。

第二步 – 创建一个落地页

既然你已经有一个基本的Rails应用程序,你将会用自己的登录页替换掉Rails默认提供的页面。一个自定义的登录页会更方便地在应用程序的根URL上展示用户注册和登录的链接。你将在后续步骤中添加登录和注册的链接。

要创建你的登陆页,你需要做以下几个步骤:

  • Add the route in the config/routes.rb file.
  • Create a HomeController which will handle the requests to this route.
  • Create a view file to be rendered when you hit the route.

当你创建项目时生成的routes.rb文件,你需要将根路径添加到其中。

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

  1. nano config/routes.rb

添加下面的这行文字:

config/routes.rb:
配置路由.rb。

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

根路由:定义了哪个控制器操作将处理对根路径的请求 – 在这种情况下,路由将是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 可以用以下方式进行汉语释义:首页控制器.rb

class HomeController < ApplicationController
  def index
    render
  end
end

以下是一个基本的HomeController,它有一个名为index的方法,它只完成一个任务:渲染与控制器动作相关联的视图文件。

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

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

接下来,在app/views目录中创建主目录。

  1. mkdir app/views/home/

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

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

app/views/home/index.html.erb 可以被表达为:应用/视图/主页/索引.html.erb
<h1>你好,Silicon Cloud!</h1>

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

保存并关闭文件。

要查看根网址的变化,请在您的浏览器中打开http://localhost:3000(如果已经打开,请刷新页面)。更新后的首页将类似于这样:

应用程序的主页,呈现index.html.erb文件

如果需要的话,你可以进一步个性化这个页面,但这对于这个教程来说已经足够了。

现在你有一个简单的Rails应用程序,其中包含自己的登录页面,你将使用Devise gem添加用户认证。

步骤 3 — 安装和配置 Devise

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

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

在对Gemfile进行更改之前,请停止上一步骤中启动的开发服务器,方法是在运行它的终端上按下CTRL+C。

然后,打开Gemfile进行编辑。要添加Devise gem,将下面突出的行添加到文件末尾,但在development和test组之外。

Gemfile (Gem文件)

# ...

# 通过缓存减少启动时间;在 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,它可以让你在rails命令行工具中使用devise命令,并配置认证功能。

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

  1. bundle exec rails g devise:install

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

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

在这个时候,一些指令也会在终端中打印出来,如下所示:

输出

根据您的应用程序配置,可能需要一些手动设置:

  1. 确保您在环境文件中定义了默认URL选项。以下是在config/environments/development.rb中适用于开发环境的default_url_options示例:
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }

在生产环境中,:host应该设置为应用程序的实际主机。

* 所有应用程序都需要。*

  1. 确保您在config/routes.rb中定义了root_url。例如:
root to: "home#index"

* 仅API应用程序不需要 *

  1. 确保您在app/views/layouts/application.html.erb中有闪现消息。例如:
<p class="notice"><%= notice %></p>
<p class="alert"><%= alert %></p>

* 仅API应用程序不需要 *

  1. 您可以通过运行以下命令将Devise视图(用于自定义)复制到您的应用程序中:
rails g devise:views

* 不是必需的 *

虽然本教程不需要手动设置,但你稍后在这个步骤中将添加通知和警告的闪现信息。

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

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

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

配置/初始设备.rb

Devise.setup do |config|
  # ...

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

  # ...
end

这行代码将turbo_stream作为导航格式添加进来。Turbo流是Turbo的一部分,它使您能够发送服务器渲染的HTML并且无需使用太多JavaScript渲染页面。在Rails 7中,为了使Devise 4.8.1正常工作,您需要添加此代码;否则,您将会遇到一个"undefined method user_url"错误。

保存并关闭文件。

接下来,您还需要添加之前打印的指令中突出显示的通知和警告闪烁信息。警告和通知的标签是用户界面中出现的类似"密码错误"之类的消息的位置。您可以在应用程序中始终实现自定义警报消息(例如,如果您正在使用React作为前端的Axios拦截器),但是对于本教程,您将完成最基本的Devise设置。

打开app/views/layouts/application.html.erb进行编辑。在body标签内的<%= yield %>的上方添加"通知"和"警告"消息的标签。

应用程序.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.first或User.find_by_email("sammy@example.com")这样的操作。你可以通过在Rails中创建一个继承自ApplicationRecord的普通类来创建这个模型,但使用Devise生成用户模型会给你提供许多用于身份验证的方法。

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

bundle exec rails g devise user

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

输出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是用于在数据库中创建用户表的迁移文件。Rails迁移文件描述了需要在数据库中进行的更改。每个迁移的文件名都包含一个时间戳,这样Rails就知道按照哪个顺序进行这些更改。

Devise还创建了用户模型文件(app/models/user.rb),同时也为该文件编写了测试。输出的最后一行表示已将路由添加到现有的config/routes.rb文件中。Devise会通过devise_for :users助手自动添加所有的路由,如/users/sign_up和/users/sign_out。

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

从打开迁移文件(db/migrate/20220908152949_devise_create_users.rb)开始,查看默认代码。

db/migrate/20220908152949_devise_create_users.rb 的英文原意是什么?
# 冻结字符串字面量为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包括许多有用的选项,例如重置密码令牌的字段以及发送上一个令牌的时间等等。此外,还有用于电子邮件确认、在登录尝试失败后锁定用户的功能以及跟踪登录详细信息的行。

既然不需要进行任何更改,就关闭迁移文件吧。

Devise还生成了User模型文件,该文件将位于app/models/目录下。

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

博客 / 应用程序 / 模型 / 用户.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
devise :database_authenticatable, :registerable,
       :recoverable, :rememberable, :validatable
end

Devise添加了一些选项来配置用户模型的工作方式。基本模块(database_authenticatable、registerable、recoverable、rememberable和validatable)已经包含在内。还有一些额外的选项以注释的形式存在,与迁移文件中看到的额外功能相对应。根据您在应用程序中所需要的功能,可以将这些模块添加到模型文件并配置迁移。

这是基础模块的功能:

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

刚刚生成的用户模型中包含了这些基本模块。你可以在 Devise 的 GitHub 仓库中找到附带的所有模块的完整列表。

你不需要进行任何改动,所以关闭用户模型文件。

另一个更新是对config/routes.rb文件进行了修改,为用户添加了一行devise_for。

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创建的,你无需手动添加。

输出会很长,但下面是一小部分路线的片段。

前缀 动词 URI模式 控制器#动作
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 添加的路由。这些路由用于登录、注册、重置密码等操作。

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

  1. bundle exec rails db:migrate

上述命令将把每个迁移文件中的所有更改应用到数据库中。这些更改需要按照文件中定义的顺序依次进行。Rails需要知道迁移应该按照什么顺序运行,这就是为什么文件名中包含时间戳的原因。

输出像下面这样的内容将会打印到屏幕上:

输出== 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,您将找到一个注册表格,通过输入电子邮件和密码来创建一个账户。(在下一步中,您将在登录页面上添加注册和登录按钮,以便读者更容易导航到该网址。)

Devise生成的注册页面

为了测试身份验证,请输入一个测试电子邮件,例如sammy@example.com,并输入一个密码。

一旦您注册成功后,您将被重定向至主页面,页面上会显示"你好,Silicon Cloud!"以及一条成功注册的消息。

屏幕截图显示用户注册后被重定向到的登录页面

这个注册成功的通知是在您在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> 欢迎 <%= current_user.email %> </div>
  <%= button_to "退出登录", destroy_user_session_path, method: :delete %>
<% else %>
  <%= button_to "登录", new_user_session_path %>
<% end %>
<h1>你好,Silicon Cloud!</h1>

user_signed_in?是来自与Devise控制器相关的辅助方法。它检查用户是否已登录并返回布尔值true或false。您可以使用此结果来为您的应用程序编写其他功能,例如,如果用户已登录,则显示其帐户信息。有关此辅助方法的更多详细信息,请查看Devise GitHub存储库中的源代码。

current_user是一个Devise辅助方法,用于访问当前已登录到应用程序的用户的详细信息。例如,如果您使用sammy@example.com进行登录,那么current_user辅助方法将返回sammy@example.com的用户模型。因此,当使用current_user.email时,您将获得sammy@example.com作为结果。通过使用Devise,您避免了从头开始实现此逻辑,节省了时间和精力。

最后,通过这段代码,您在登录页面上添加了"登录"和"退出登录"按钮。根据用户注册情况的辅助方法的结果,您将通过新添加的"登录"和"退出登录"按钮显示相应的选项,即可选择"登录"或"退出登录"。

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

保存并关闭文件。

请在浏览器中刷新页面以查看变更。

如果您还没有尝试过注册应用程序,您可以点击页面上的登录按钮,访问/users/sign_in路由。从这里,您可以继续点击底部的注册链接来创建一个新账户。输入测试邮箱,如sammy@example.com,和密码。注册成功后,您会再次回到登录页面。现在,登录页面会显示当前登录用户的邮箱地址,以及一个退出登录按钮,如下所示:

用户登录后的登录页面

您还会收到一条消息,上面写着"您已成功注册"。

并且通过这样,您已经成功地集成了Devise gem并在您的应用程序中设置了用户认证。

结论

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

要更好地了解Devise和其他辅助方法,可以查看Devise GitHub存储库中的README文件。作为本教程的下一步,您可以尝试根据用户是否登录来有条件地渲染页面上的"Hello World!"问候语,比如根据用户名显示"Hello username"。

您可以在Silicon Cloud社区的GitHub代码库中找到这个项目的代码。