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

如何使用Rails与Devise限制同一账号的同时登录会话数?

如何用Devise限制同一用户的同时登录会话数量

绝对可以实现!用Devise做这个其实挺直观的,核心思路就是追踪用户的有效会话——要么强制单会话(新登录自动踢掉旧会话),要么限制最多N个会话同时存在。下面给你详细拆解两种常见方案:

方案一:强制单会话(新登录踢掉旧会话)

这种场景适合要求账号只能在一个设备登录的情况,比如金融类应用。

  1. 给User模型添加会话追踪字段
    首先生成迁移,给用户表加一个存储当前有效会话token的字段:

    rails generate migration AddSessionTokenToUsers session_token:string:index
    rails db:migrate
    
  2. 在User模型里添加会话管理方法
    用来生成新的会话token并失效所有旧会话:

    # app/models/user.rb
    class User < ApplicationRecord
      devise :database_authenticatable, :registerable,
             :recoverable, :rememberable, :validatable
    
      def generate_session_token!
        self.session_token = SecureRandom.urlsafe_base64
        save!
      end
    
      def invalidate_all_sessions!
        generate_session_token!
      end
    end
    
  3. 覆盖Devise的会话控制器
    创建自定义的SessionsController,在用户登录成功后更新会话token并存储到session中:

    # app/controllers/users/sessions_controller.rb
    class Users::SessionsController < Devise::SessionsController
      def create
        super do |resource|
          # 登录时失效所有旧会话,生成新token
          resource.invalidate_all_sessions!
          # 把新token存到当前会话里
          session[:user_session_token] = resource.session_token
        end
      end
    

    然后在路由里指定这个控制器:

    # config/routes.rb
    devise_for :users, controllers: { sessions: 'users/sessions' }
    
  4. 全局验证会话有效性
    在ApplicationController里加前置钩子,每次请求都检查当前会话的token是否和用户的有效token匹配:

    # app/controllers/application_controller.rb
    class ApplicationController < ActionController::Base
      before_action :validate_session_token
    
      private
    
      def validate_session_token
        return unless current_user
    
        stored_token = session[:user_session_token]
        if stored_token != current_user.session_token
          sign_out current_user
          redirect_to new_user_session_path, alert: "你的账号已在其他设备登录,请重新登录"
        end
      end
    end
    

方案二:限制最多N个同时会话

如果允许用户在最多N个设备登录(比如2个),可以用关联表来追踪所有有效会话。

  1. 创建UserSession模型存储会话记录
    生成模型和迁移:

    rails generate model UserSession user:references token:string:index expires_at:datetime
    rails db:migrate
    
  2. 关联User和UserSession模型

    # app/models/user.rb
    class User < ApplicationRecord
      devise :database_authenticatable, :registerable,
             :recoverable, :rememberable, :validatable
    
      has_many :user_sessions, dependent: :destroy
    
      def create_session_token!
        token = SecureRandom.urlsafe_base64
        # 设置会话有效期,比如2周
        user_sessions.create!(token: token, expires_at: 2.weeks.from_now)
        token
      end
    
      # 保留最新的max_count个会话,删除旧的
      def invalidate_old_sessions!(max_count = 2)
        user_sessions.order(created_at: :desc).offset(max_count).destroy_all
      end
    
      # 验证会话token是否有效且未过期
      def valid_session_token?(token)
        user_sessions.where(token: token, expires_at: Time.now..).exists?
      end
    end
    
  3. 更新自定义会话控制器
    登录时创建新会话并清理超出数量限制的旧会话,登出时删除对应会话:

    # app/controllers/users/sessions_controller.rb
    class Users::SessionsController < Devise::SessionsController
      def create
        super do |resource|
          token = resource.create_session_token!
          session[:user_session_token] = token
          # 限制最多2个同时会话,可自行修改数字
          resource.invalidate_old_sessions!(2)
        end
      end
    
      def destroy
        # 登出时删除当前会话记录
        token = session[:user_session_token]
        current_user.user_sessions.where(token: token).destroy_all if current_user
        super
      end
    end
    
  4. 全局验证会话有效性
    同样在ApplicationController里加前置钩子:

    # app/controllers/application_controller.rb
    class ApplicationController < ActionController::Base
      before_action :validate_session_token
    
      private
    
      def validate_session_token
        return unless current_user
    
        token = session[:user_session_token]
        unless current_user.valid_session_token?(token)
          sign_out current_user
          redirect_to new_user_session_path, alert: "你的会话已过期或登录设备过多,请重新登录"
        end
      end
    end
    

额外注意事项

  • 会话过期清理:可以用定时任务(比如Sidekiq Cron)定期清理user_sessions表中已过期的记录,避免数据冗余。
  • Redis优化:如果你的应用用Redis存储会话,可以把会话token存在Redis里,效率会比数据库更高,逻辑类似。
  • 测试场景:记得测试多设备登录、会话过期、登出等场景,确保逻辑符合预期。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 08:58:43