You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为特定控制器启用Guest账户(Rails7+Devise场景)

解决方案:Rails 7 + Devise 实现客户签名链接登录与多模型认证共用路径

一、用 SGID 限制链接用途并完成验证

首先在生成签名链接时指定 for 参数,明确该签名仅用于 Customer 的 show 动作,避免签名被滥用:

# CustomerObserver
class CustomerObserver < ActiveRecord::Observer
  def after_create(customer)
    # 指定for参数为:customer_show,锁定签名用途
    secret_param = customer.to_sgid(expires_in: nil, for: :customer_show).to_s
    # 直接指向customers#show动作,符合RESTful风格
    url = "#{ENV['HOST']}/customers/#{customer.id}?csig=#{secret_param}"
    # 发送包含该链接的邮件...
  end
end

然后在 CustomersController 中跳过全局的 authenticate_user!,改用自定义逻辑验证签名并登录客户:

class CustomersController < ApplicationController
  # 仅对show动作跳过用户认证
  skip_before_action :authenticate_user!, only: :show
  # 添加客户专属的认证前置动作
  before_action :authenticate_customer_via_sgid, only: :show

  def show
    @customer = current_customer
    # 页面业务逻辑...
  end

  private

  def authenticate_customer_via_sgid
    csig = params[:csig]
    unless csig
      redirect_to root_path, alert: "无效链接"
      return
    end

    begin
      # 验证签名时强制匹配:customer_show用途,防止跨场景滥用
      sgid = GlobalID::SignedGlobalID.parse(csig, for: :customer_show)
      customer = sgid.find
      # 将客户ID存入session,实现会话保持
      session[:customer_id] = customer.id
    rescue ActiveRecord::RecordNotFound, GlobalID::InvalidSignature
      redirect_to root_path, alert: "链接无效或已过期"
    end
  end

  # 定义current_customer方法,视图和控制器均可调用
  def current_customer
    @current_customer ||= Customer.find_by(id: session[:customer_id]) if session[:customer_id]
  end
  helper_method :current_customer
end

二、实现 User 与 Customer 共用 /sign_in 路径

1. 自定义 Devise 会话控制器

创建自定义会话控制器,根据请求参数区分用户登录和客户登录逻辑:

# app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
  def create
    if params[:customer]
      # 现阶段客户仅通过签名链接访问,这里提示用户使用专属链接
      redirect_to root_path, alert: "请使用发送给您的专属链接访问"
    else
      super # 走原有的User登录逻辑
    end
  end

  # 登出时同时清除客户会话
  def destroy
    session.delete(:customer_id)
    super
  end
end

2. 修改路由配置

在 config/routes.rb 中指定自定义会话控制器,让 User 和后续可能的 Customer 共用 /sign_in 路径:

Rails.application.routes.draw do
  # User使用自定义会话控制器
  devise_for :users, controllers: { sessions: 'sessions' }
  # 客户仅开放show路由(按需调整)
  resources :customers, only: :show

  root "home#index"
end

3. 全局认证逻辑兼容

在 ApplicationController 中添加判断,避免客户访问专属页面时触发用户认证:

class ApplicationController < ActionController::Base
  before_action :authenticate_user!, unless: :customer_show_action?

  private

  # 判断是否是客户的show动作,跳过用户认证
  def customer_show_action?
    controller_name == 'customers' && action_name == 'show'
  end

  # 可选:全局统一获取当前登录主体(用户或客户)
  def current_visitor
    current_user || current_customer
  end
  helper_method :current_visitor
end

三、后续扩展:支持客户密码登录

如果以后需要开放客户密码登录,只需给 Customer 模型添加 Devise 模块:

# app/models/customer.rb
class Customer < ApplicationRecord
  devise :database_authenticatable, :rememberable, :validatable
  # 其他业务逻辑...
end

然后修改会话控制器的 create 方法,增加客户密码认证逻辑:

def create
  if params[:customer]
    customer = Customer.find_by(email: params[:customer][:email])
    if customer&.valid_password?(params[:customer][:password])
      sign_in(:customer, customer) # Devise指定scope登录客户
      session[:customer_id] = customer.id
      redirect_to customer_path(customer), notice: "登录成功"
    else
      flash.now[:alert] = "邮箱或密码错误"
      render :new
    end
  else
    super
  end
end

最后补充客户的 Devise 路由(共用会话控制器):

devise_for :customers, controllers: { sessions: 'sessions' }, skip: [:registrations]

内容的提问来源于stack exchange,提问作者Sylar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 11:45:36