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

Rails 5应用中跨自定义域名共享Devise会话的方案问询

嘿,这个问题我之前帮客户处理过,核心就是浏览器的同源策略在搞鬼——主域www.example.com设置的session cookie,默认只能在*.example.com这类子域下被读取,而www.andyexample.com是完全独立的域名,浏览器不会把主域的cookie带到这里,所以Devise自然认不出你的登录状态。给你两种针对性的解决方案,适配你的场景:

方案一:如果自定义域名是主域的子域(比如andy.example.com)

这种情况最简单,只需要调整Rails和Devise的cookie配置,让cookie能在所有子域共享:

  1. 修改config/initializers/session_store.rb:
Rails.application.config.session_store :cookie_store,
  key: '_your_app_session',
  domain: '.example.com', # 前面的点是关键,匹配所有example.com的子域
  secure: Rails.env.production?, # 生产环境必须开启HTTPS才能用
  same_site: :lax # 若需要跨站表单提交,可改成:none
  1. 同步修改Devise的配置config/initializers/devise.rb,确保Devise的cookie也用相同规则:
Devise.setup do |config|
  # 保留你原有的其他配置...
  config.cookie_options = { domain: '.example.com', secure: Rails.env.production? }
end

方案二:跨顶级域(你的场景:www.example.com和www.andyexample.com)

这种情况浏览器的限制更严格,普通cookie共享行不通,推荐用基于Token的认证方案,这里给你两种实现方式:

方法A:用Devise Token Auth快速实现

这是最省心的方式,直接用成熟的gem扩展:

  1. 把gem加入Gemfile:
gem 'devise_token_auth'
  1. 运行安装生成器,自动生成配置文件:
rails generate devise_token_auth:install User auth
  1. 调整config/initializers/devise_token_auth.rb的配置,重点限制允许的跨域域名(别直接设成*,生产环境要做校验):
DeviseTokenAuth.setup do |config|
  config.change_headers_on_each_request = false # 可选,减少请求开销
  config.token_lifespan = 2.weeks # 按需调整令牌有效期
  # 验证请求域名是否是用户合法配置的自定义域名
  config.allow_origin = ->(request) { 
    CustomDomain.exists?(domain: request.host) ? request.host : 'www.example.com'
  }
end
  1. 前端登录后,要把返回的access-token、client、uid这些请求头存起来,后续所有请求都带上这些头,Devise就能跨域名识别用户了。

方法B:自定义JWT认证(更灵活)

如果不想依赖gem,可以自己实现JWT会话:

  1. 添加JWT依赖到Gemfile:
gem 'jwt'
  1. 重写Devise的登录控制器,登录成功后生成JWT返回给前端:
class Users::SessionsController < Devise::SessionsController
  def create
    super do |resource|
      # 生成带过期时间的JWT
      token = JWT.encode(
        { user_id: resource.id, exp: 2.weeks.from_now.to_i },
        Rails.application.credentials.secret_key_base
      )
      render json: { user: resource, token: token } and return
    end
  end
end
  1. 写一个认证模块,用于验证请求中的JWT:
module Authenticable
  extend ActiveSupport::Concern

  included do
    before_action :authenticate_user!
  end

  def authenticate_user!
    # 从请求头获取令牌
    token = request.headers['Authorization']&.split(' ')&.last
    return unauthorized unless token

    begin
      decoded = JWT.decode(token, Rails.application.credentials.secret_key_base, true, algorithm: 'HS256')
      @current_user = User.find(decoded[0]['user_id'])
    rescue JWT::ExpiredSignature, JWT::DecodeError
      unauthorized
    end
  end

  def unauthorized
    render json: { error: 'Unauthorized' }, status: :unauthorized
  end
end
  1. 在全局控制器中引入这个模块,让所有请求都做认证:
class ApplicationController < ActionController::Base
  include Authenticable
end
  1. 前端登录后存储JWT,后续所有请求都在Authorization头里带上Bearer <你的令牌>,这样不管访问哪个域名,只要令牌有效就能验证身份。

Heroku环境额外注意事项

  • 所有自定义域名必须配置HTTPS:Heroku提供免费SSL,用heroku domains:add www.andyexample.com添加域名后,启用SSL即可。
  • 如果用same_site: :none,必须同时设置secure: true,否则浏览器会拒绝保存cookie。
  • 生产环境一定要严格校验跨域请求的域名,别随便开放*权限,避免CSRF攻击。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 03:33:57