0.动机
在[BootrAils],直到最近,我们才对任何新的Rails应用程序中的体面的默认认证感到不安--直到Rodauth出现在人们的视线之下。
在Ruby-on-Rails世界中没有 "Active Auth",这意味着如果你想在你的应用程序中添加身份验证,你必须依靠一个gem--或者自己建立它。
对于那些已经了解这个领域的人来说,这是一个在互联网上无休止的辩论。Devise是最常用的宝石。然而,长期用户对它的评价总是 "一般般":Devise对角落里的情况并不那么好(处理JWT认证是抱怨之一,还有其他许多抱怨)。Clearance、Sorcery是众所周知的替代品,但它们也与Rails本身紧密耦合,必要时不太容易调整。
1.进入Rodauth
Rodauth消除了上述的大部分痛苦。Rodauth最初不与Rails绑定(它是一个Ruby库)。它自带以下[功能]。
- 登录
- 注销
- 更改密码
- 更改登录
- 重置密码
- 创建账户
- 关闭账户
- 验证账户
- 确认密码
- 记住 (通过令牌自动登录)
- 锁定(暴力保护)
- 审计记录
- 电子邮件认证(通过电子邮件链接进行无密码登录)
- WebAuthn (通过WebAuthn的多因素认证)
- WebAuthn登录 (通过WebAuthn无密码登录)
- WebAuthn验证帐户(无密码的WebAuthn设置)
- OTP (通过TOTP的多因素认证)
- 恢复代码(通过备份代码进行多因素认证)
- 短信代码 (通过短信进行多因素认证)
- 验证登录变更 (在变更登录前验证新的登录)
- 验证账户宽限期 (登录前不需要验证)
- 密码宽限期(如果是最近输入的密码,则不要求输入)。
- 密码复杂性 (更复杂的检查)
- 密码胡椒粉
- 不允许重复使用密码
- 不允许普通密码
- 密码过期
- 帐户过期
- 会话过期
- 活动会话(防止注销后重复使用会话,允许注销所有会话)
- 单一会话 (每个账户只有一个活动会话)
- JSON (所有其他功能都支持JSON API)
- JWT (支持所有其他功能的JSON网络令牌)
- JWT刷新 (访问和刷新令牌)
- JWT CORS (跨源资源共享)
- 更新密码散列(当散列成本改变时)
- Argon2
- HTTP基本认证
- 更改密码通知
- 内部请求
- 路径类方法
对于一个开始来说还不错!对于一个标准的企业来说,你需要其他东西的可能性几乎为零。
没有必要说我们不会涵盖每一个功能,但知道我们不会错过任何东西总是很好的!
2.试试吧,从头开始
首先确保你的电脑上已经安装了所有的经典软件。
$> ruby -v
ruby 3.0.0p0 // you need at least version 3 here
$> bundle -v
Bundler version 2.2.11
$> npm -v
8.3.0 // you need at least version 7.1 here
$> yarn -v
1.22.10
$> psql --version
psql (PostgreSQL) 13.1 // let's use a production-ready database locally
任何上面的版本都可以使用
然后从头开始安装一个新的rails应用程序。
mkdir myapp && cd myapp
echo "source 'https://rubygems.org'" > Gemfile
echo "gem 'rails', '7.0.0'" >> Gemfile
bundle install
bundle exec rails new . --force --css=bootstrap -d=postgresql
bundle update
Bootstrap会让我们有一个更漂亮的演示。或者至少更有可读性 :)
在myapp文件夹中,继续执行以下终端命令。
# Create a default controller
echo "class HomeController < ApplicationController" > app/controllers/home_controller.rb
echo "end" >> app/controllers/home_controller.rb
# Create another controller (the one that should not be reached without proper authentication)
echo "class OtherController < ApplicationController" > app/controllers/other_controller.rb
echo "end" >> app/controllers/other_controller.rb
# Create routes
echo "Rails.application.routes.draw do" > config/routes.rb
echo ' get "home/index"' >> config/routes.rb
echo ' get "other/index"' >> config/routes.rb
echo ' root to: "home#index"' >> config/routes.rb
echo 'end' >> config/routes.rb
# Create a default view
mkdir app/views/home
echo '<h1>This is home</h1>' > app/views/home/index.html.erb
echo '<div class="lead my-3"><%= link_to "go to other page", other_index_path %></div>' >> app/views/home/index.html.erb
# Create another view (will be also protected by authentication)
mkdir app/views/other
echo '<h1>This is another page</h1>' > app/views/other/index.html.erb
echo '<div class="lead my-3"><%= link_to "go to home page", root_path %></div>' >> app/views/other/index.html.erb
# Create database and schema.rb
bin/rails db:create
bin/rails db:migrate
很好!我们现在有了一个很好的默认的Rails 7应用程序,有一个主页,还有一个 "其他 "页面,它应该被保护起来,防止未经认证的访问。
通过运行以下命令偷看一下当前的应用程序
./bin/dev

本地主机
从一个页面导航到另一个页面。到目前为止,还没有什么不可思议的东西,但至少我们已经准备好去尝试一个好的认证宝石了 !
3.安装rodauth-rails
现在打开你的Gemfile,添加
gem "rodauth-rails"
然后
$/myapp> bundle install
让我们看看它是什么。
$/myapp> bundle info rodauth-rails
* rodauth-rails (0.18.1)
Summary: Provides Rails integration for Rodauth.
Homepage: https://github.com/janko/rodauth-rails
Path: /Users/shino/.rbenv/versions/3.0.0/lib/ruby/gems/3.0.0/gems/rodauth-rails-0.18.1
很好!为下一关做好准备 :)
4.在你的应用程序中安装rodauth
创业板现在可用了,但还没有在你的Rails应用程序中运行rodauth的必要文件和文件夹。
让我们开始行动吧。
$/myapp> bin/rails generate rodauth:install
create db/migrate/20211224143551_create_rodauth.rb
create config/initializers/rodauth.rb
create config/initializers/sequel.rb
create app/lib/rodauth_app.rb
create app/controllers/rodauth_controller.rb
create app/models/account.rb
create app/mailers/rodauth_mailer.rb
create app/views/rodauth_mailer/email_auth.text.erb
create app/views/rodauth_mailer/password_changed.text.erb
create app/views/rodauth_mailer/reset_password.text.erb
create app/views/rodauth_mailer/unlock_account.text.erb
create app/views/rodauth_mailer/verify_account.text.erb
create app/views/rodauth_mailer/verify_login_change.text.erb
在你最喜欢的IDE中偷看一下每个文件。
然后输入 。
$/myapp> bin/rails db:migrate
== 20211224143551 CreateRodauth: migrating ====================================
-- enable_extension("citext")
-> 0.1350s
-- create_table(:accounts)
-> 0.0084s
-- create_table(:account_password_hashes)
-> 0.0066s
-- create_table(:account_password_reset_keys)
-> 0.0081s
-- create_table(:account_verification_keys)
-> 0.0217s
-- create_table(:account_login_change_keys)
-> 0.0080s
-- create_table(:account_remember_keys)
-> 0.0050s
== 20211224143551 CreateRodauth: migrated (0.1933s) ===========================
现在schema.rb看起来像这样。
ActiveRecord::Schema.define(version: 2021_12_24_143551) do
enable_extension "citext"
enable_extension "plpgsql"
create_table "account_login_change_keys", force: :cascade do |t|
t.string "key", null: false
t.string "login", null: false
t.datetime "deadline", precision: 6, null: false
end
create_table "account_password_hashes", force: :cascade do |t|
t.string "password_hash", null: false
end
create_table "account_password_reset_keys", force: :cascade do |t|
t.string "key", null: false
t.datetime "deadline", precision: 6, null: false
t.datetime "email_last_sent", precision: 6, default: -> { "CURRENT_TIMESTAMP" }, null: false
end
create_table "account_remember_keys", force: :cascade do |t|
t.string "key", null: false
t.datetime "deadline", precision: 6, null: false
end
create_table "account_verification_keys", force: :cascade do |t|
t.string "key", null: false
t.datetime "requested_at", precision: 6, default: -> { "CURRENT_TIMESTAMP" }, null: false
t.datetime "email_last_sent", precision: 6, default: -> { "CURRENT_TIMESTAMP" }, null: false
end
create_table "accounts", force: :cascade do |t|
t.citext "email", null: false
t.string "status", default: "unverified", null: false
t.index ["email"], name: "index_accounts_on_email", unique: true, where: "((status)::text = ANY ((ARRAY['unverified'::character varying, 'verified'::character varying])::text[]))"
end
add_foreign_key "account_login_change_keys", "accounts", column: "id"
add_foreign_key "account_password_hashes", "accounts", column: "id"
add_foreign_key "account_password_reset_keys", "accounts", column: "id"
add_foreign_key "account_remember_keys", "accounts", column: "id"
add_foreign_key "account_verification_keys", "accounts", column: "id"
end
4.查看可用的路由
Rodauth中间件将处理请求(而不是Rails应用),因此,路由不会显示在/rails/info/routes。
从文档中,这里有可用的端点。
Routes handled by RodauthApp:
/login rodauth.login_path
/create-account rodauth.create_account_path
/verify-account-resend rodauth.verify_account_resend_path
/verify-account rodauth.verify_account_path
/change-password rodauth.change_password_path
/change-login rodauth.change_login_path
/logout rodauth.logout_path
/remember rodauth.remember_path
/reset-password-request rodauth.reset_password_request_path
/reset-password rodauth.reset_password_path
/verify-login-change rodauth.verify_login_change_path
/close-account rodauth.close_account_path
5.创建视图和用户体验
你有一些模板已经可以免费使用,如果你想看看事情是如何运作的。对于一个教程来说,这是一个完美的起点,所以让我们输入 。
$/myapp> bin/rails generate rodauth:views
create app/views/rodauth/_login_form.html.erb
create app/views/rodauth/_login_form_footer.html.erb
create app/views/rodauth/_login_form_header.html.erb
create app/views/rodauth/login.html.erb
create app/views/rodauth/multi_phase_login.html.erb
create app/views/rodauth/create_account.html.erb
create app/views/rodauth/verify_account_resend.html.erb
create app/views/rodauth/verify_account.html.erb
create app/views/rodauth/logout.html.erb
create app/views/rodauth/remember.html.erb
create app/views/rodauth/reset_password_request.html.erb
create app/views/rodauth/reset_password.html.erb
create app/views/rodauth/change_password.html.erb
create app/views/rodauth/change_login.html.erb
create app/views/rodauth/verify_login_change.html.erb
create app/views/rodauth/close_account.html.erb
6.修改主页
现在修改主页,然后你就可以玩你的应用程序了。
<h1>This is home</h1>
<div class="lead my-3"><%= link_to "go to other page", other_index_path %></div>
<% if rodauth.logged_in? %>
<%= link_to "Sign out", rodauth.logout_path, method: :post %>
<% else %>
<%= link_to "Sign in", rodauth.login_path %>
<%= link_to "Sign up", rodauth.create_account_path %>
<% end %>
现在启动你的本地服务器,并尝试创建一个新账户,注销,然后登录,上述标记应该可以正常工作。
如果你想在本地尝试 "重置密码 "的功能,别忘了在下面的一行中加入config/environments/development.rb
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
7.保护另一个页面
记住我们的应用程序中有2个页面:"主页 "和 "其他"(你可以通过http://localhost:3000/other/index,到达其他页面)
修改config/routes.rb如下。
# inside config/routes.rb
Rails.application.routes.draw do
get "home/index"
constraints Rodauth::Rails.authenticated do
get "other/index"
end
root to: "home#index"
end
重新启动你的本地Web服务器。如果在主页上,你试图通过点击链接进入其他页面,会发生什么?
8.文档,信用
非常感谢@janko和@jeremyevans所做的不可思议的工作,以及对GitHub上的问题和PR的友好回答。