Rails 5多租户系统(Apartment gem)用户认证跨租户登录异常问题
这个问题我之前帮朋友排查过,核心原因就是你的session里只存了user_id,没带上租户的上下文信息——不同租户的users表是完全独立的,同一个user_id在不同租户里对应完全不同的用户,系统没办法区分这个ID属于哪个租户,所以切换子域名(切换租户)后,就会自动在当前租户的用户表中找相同ID的用户,导致串登录的问题。
下面是具体的解决步骤,亲测在Rails 5 + Apartment gem环境下有效:
1. 登录时存储租户标识到Session
在登录验证通过后,不要只把user_id存到session里,同时把当前租户的标识(也就是你Tenant模型里的tenant字段值)也存进去。比如在你的SessionsController的create方法里修改:
def create user = User.find_by(email: params[:email]) if user&.authenticate(params[:password]) # 同时存储当前租户和用户ID session[:current_tenant] = Apartment::Tenant.current session[:user_id] = user.id redirect_to dashboard_path, notice: '登录成功' else flash[:alert] = '邮箱或密码错误' render :new end end
2. 重写current_user方法,加入租户校验
在ApplicationController里,获取当前用户的时候,先校验session里的租户标识是否和当前租户一致。如果不一致,直接清空session并返回nil,避免串登录:
class ApplicationController < ActionController::Base protect_from_forgery with: :exception helper_method :current_user def current_user # 只有session里的租户和当前租户匹配时,才查找用户 if session[:current_tenant] == Apartment::Tenant.current @current_user ||= User.find_by(id: session[:user_id]) else # 租户不匹配时重置session reset_session nil end end # 可选:每次请求前校验租户和session的匹配性 before_action :validate_tenant_session private def validate_tenant_session return unless session[:current_tenant].present? if session[:current_tenant] != Apartment::Tenant.current reset_session flash[:alert] = '请重新登录' redirect_to login_path end end end
3. 退出登录时清理租户相关Session
别忘了在退出登录的方法里,同时清除租户标识和用户ID的session键,避免残留信息导致问题:
def destroy session.delete(:user_id) session.delete(:current_tenant) reset_session redirect_to root_path, notice: '已退出登录' end
4. 可选:优化租户切换时的Session处理
如果你用Apartment的自动切换租户逻辑(比如基于子域名),可以在租户切换的钩子中提前清理不匹配的session。比如在config/initializers/apartment.rb里添加:
Apartment::Tenant.configure do |config| # 你的其他Apartment配置... config.on_switch do |new_tenant| # 如果当前请求的session里有租户标识,且和新租户不匹配,就清除用户session if request.session[:current_tenant].present? && request.session[:current_tenant] != new_tenant request.session.delete(:user_id) request.session.delete(:current_tenant) end end end
注意:在initializer里访问session需要用request.session,如果你的Rails版本不支持这种方式,就用前面提到的ApplicationController的before_action来处理即可。
测试验证
做完这些修改后,你可以测试以下场景:
- 登录tenantA的用户Brad(user_id:1),切换到tenantB的子域名,应该会被要求重新登录,而不是自动登录tenantB的user_id:1用户
- 登录tenantB的用户后,切换回tenantA,同样需要重新登录
- 退出登录后,切换任何租户都不会自动登录
内容的提问来源于stack exchange,提问作者Brad

