Devise 到自定义认证:不仅仅是 `has_secure_password`
Source: Dev.to

想要切换的冲动
Devise 是 Rails 认证领域的 800 磅大猩猩。它可靠、安全,并且能 做所有事。
但最终你会碰到“墙”。你可能想要无密码登录流程,厌倦了为了改一个重定向而覆盖数十个控制器方法,或者只是想了解自己的应用是如何登录用户的。
于是你决定:“我要把 Devise 拔掉,改用 Rails 内置的 has_secure_password。”
我已经完成了这次迁移。它让代码库更简洁、更快——但通往那里的道路布满地雷。以下是你将面临的最大挑战。
挑战 1:数据库模式不匹配
Devise 对数据库列有自己的约定。
- Devise 使用:
encrypted_password - Rails(
has_secure_password)期望:password_digest
你有两种选择,各有利弊。
选项 A:别名(快速且脏)
# app/models/user.rb
class User < ApplicationRecord
has_secure_password :password, validations: false
alias_attribute :password_digest, :encrypted_password
end
风险: 这种做法感觉像是 hack。未来的 gem 或工具如果期待标准的 Rails 约定,可能会出问题。
选项 B:迁移(干净但有风险)
# db/migrate/xxxx_rename_encrypted_password.rb
class RenameEncryptedPassword < ActiveRecord::Migration[6.1]
def change
rename_column :users, :encrypted_password, :password_digest
end
end
风险: 确保部署期间零停机时间。回滚可能会很快变得混乱。
挑战 2: “Recoverable” 模块
Devise 为你免费提供了“Forgot Password”。在将其移除后,除非你自行实现,否则任何人都无法重置密码。
安全的密码重置流程必须:
- 生成唯一的、高熵的令牌。
- 存储该令牌的摘要(永不存储原始令牌)。
- 设置过期时间(例如,2 小时)。
- 处理电子邮件发送。
- 关键: 使用后使令牌失效,以防重放攻击。
Devise 为你处理了时序攻击防护和令牌哈希;现在这项安全责任由你承担。
挑战 3:Cookie 安全与 “记住我”
session[:user_id] 适用于基本登录,但 “记住我” 更为复杂。Devise 的 Rememberable 模块负责持久化 cookie、令牌轮换以及安全比较。
如果自行实现:
- 对 cookie 进行签名/加密。
- 防范 会话劫持(被盗的 cookie 可授予长期访问权限)。
- 提供一种 “使所有会话失效” 的方式(例如,当用户更改密码时)。
挑战 4:视图与控制器清理
Devise 的帮助方法遍布各处。你需要在整个代码库中替换它们:
authenticate_user!→ 你自己的require_login过滤器user_signed_in?→logged_in?current_user→ 在ApplicationController中的自定义帮助方法devise_error_messages!→ 你自己的错误局部视图
小贴士: 不要立刻删除该 gem。创建一个 shim,将旧的 Devise 方法名映射到你的新逻辑,从而可以逐步迁移。
# app/controllers/concerns/auth_shim.rb
module AuthShim
extend ActiveSupport::Concern
included do
helper_method :current_user, :logged_in?
end
def authenticate_user!
redirect_to login_path unless logged_in?
end
def user_signed_in?
logged_in?
end
def logged_in?
!!current_user
end
def current_user
@current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
end
end
在 ApplicationController 中包含 AuthShim,并逐步替换调用。
挑战 5:遗留密码兼容性
Devise 和 has_secure_password 默认都使用 BCrypt,因此它们通常可以互操作。然而,较旧的 Rails 应用(例如从 Rails 3/4 迁移而来)可能将 Devise 配置为使用遗留的哈希策略(带盐的 SHA‑1)。has_secure_password 无法验证这些哈希。
解决方案: 实现自定义认证方法,步骤如下:
- 检测存储的密码是否为 BCrypt 哈希。
- 如果不是,则使用遗留的验证逻辑进行校验。
- 在遗留登录成功后,使用 BCrypt 重新对密码进行哈希并更新记录。
判定:值得吗?
如果以下情况,请不要这样做:
- 为客户构建 MVP(速度很重要)。
- 严重依赖 OmniAuth(Devise 能简化 OAuth 集成)。
- 对会话固定或时序攻击等安全概念不熟悉。
如果以下情况,请这样做:
- 正在构建预计使用 5 年以上的单体应用。
- 需要专门的流程(仅 OTP、魔法链接、多步骤 onboarding)。
- 想要降低内存占用和依赖膨胀。
移除 Devise 是在 Rails 中最具教育意义的操作之一。带上手电筒——管道里很暗。