在Rails的会话管理中,什么是最佳选择?

前提

在多个web应用程序上启动并讨论如何实现它们的用户登录会话管理。
在Rails中,有很多可用的会话管理方法,通过总结各自的优缺点来寻求最佳解决方案。

以下是本次比较的会话管理方法的种类:
– CookieStore(Cookie方式)
– Redis(内存方式)
– ActiveRecord(数据库方式)

CookieStore(饼干商店)

机构构造

将所有Session信息使用secret_key_base加密,并保存到客户端的Cookie中。
在发送请求时,将保存在Cookie中的Session信息全部发送,并在服务器端使用secret_key_base进行解密以获取Session信息。

优点

    • Railsのデフォルトで用意されているセッション管理方式なので、手軽に使え、何かを意識する必要がない。

 

    • サーバ処理の際にDBにアクセスする必要がないため、処理が高速。

 

    別アプリケーションへのセッション共有の際に、同一ドメイン配下だと僅かな設定でセッションを共有できる。

缺点

    • セッション再生攻撃をされる恐れがある。

 

    • サーバでSession情報を保持していないので、Session情報を変更したいタイミングで変更できない。

 

    Cookieの容量上限である4kBがデータの保存上限値となる。

实践

准备

构建Rails应用程序。

rails new CookieStoreTest
cd CookieStoreTest
bundle install
rails g scaffold Task title:string status:integer

添加源代码,从 Cookie 中获取会话信息,并进行恢复和在控制台中显示。

require 'cgi'
require 'active_support'
require 'yaml'

class TasksController < ApplicationController
  before_action :set_task, only: [:show, :edit, :update, :destroy]

  # GET /tasks
  # GET /tasks.json
  def index

    cookie = cookies["_app_name_session"]
    key = YAML.load_file("config/secrets.yml")["development"]["secret_key_base"]

    cookie = CGI::unescape(cookie)

    # Default values for Rails 4 apps
    key_iter_num = 1000
    salt         = "encrypted cookie"
    signed_salt  = "signed encrypted cookie"

    key_generator = ActiveSupport::KeyGenerator.new(key, iterations: key_iter_num)
    secret = key_generator.generate_key(salt)[0, 32]
    sign_secret = key_generator.generate_key(signed_salt)

    encryptor = ActiveSupport::MessageEncryptor.new(secret, sign_secret, serializer: ActiveSupport::MessageEncryptor::NullSerializer)
    puts encryptor.decrypt_and_verify(cookie)

    session['foo'] = 'foo'
    @tasks = Task.all
  end
・・・

确认

成功时,请访问 http://localhost:3000/tasks 并在控制台上输出会话信息。

在我的环境中的输出内容。

{"session_id":"8d7745352f73abf772eb2705bd78e3ef","_csrf_token":"Q2qOIpUim341/rSA7cymP8PD9N47gCSFxVF09+wYLoA=","foo":"foo"}

Redis(内存数据存储方式)

机制 (jī zhì)

Rails将创建session_id并将其与Session信息一起发送到Redis服务器。
客户端的Cookie仅保存已创建的session_id,并在请求时发送session_id。
Rails会将从客户端接收到的session_id发送到Redis服务器进行搜索,以确定用户并获取Session信息。
Redis会定期保存并持久化保存在内存中的数据。
通过配置,可以为Redis中注册的Session设置有效期。

优点

    • インメモリのため、処理が早い。

 

    • クライアントにはIDしか保存しないため、情報漏洩のリスクが低い。

 

    エクスパイア処理を1行の設定で出来る。

缺点 (quē

    • メモリを使い果たすと書き込みが全てエラーになる。

 

    • 情報を検索する必要があるため、クッキー方式に比べると処理が遅い。(メリットにある様に、基本的に早い部類ではある)

 

    • スナップショットを保存してから、次のスナップショット保存までの期間にサーバが停止・再起動した場合は、その間のデータが失われる。

 

    Redisサーバを立てる必要があるため、コストが掛かる。また、可用性を上げるための施策を打たなければならない。(Redisサーバが落ちるとログイン機能などのSessionを元に構築されている機能が使えなくなってしまうため。)

实践

准备

▼构建Rails应用程序

rails new RedisTest
cd RedisTest
bundle install
rails g scaffold Task title:string status:integer

建立 Redis 服务器 (容器)。

因为我正在使用Docker进行环境配置,所以我已经添加了以下描述来准备Redis服务器。请根据各自的环境来准备Redis服务器。

・・・
  redis:
    image: redis:latest
    ports:
      - 6379:6379
    volumes:
      - ./data/redis:/data
    command: redis-server

请在Gemfile文件中添加redis-rails库。

・・・
gem 'redis-rails'

▼ 安装 gem

bundle install

将redis-rails设置为session_store

AppName::Application.config.session_store :redis_store, {
  servers: [
             {
               host: "redis",
               port: 6379,
               db: 0,
               namespace: "session"
             },
           ],
  key: "_#{Rails.application.class.parent_name.downcase}_session"
}

从Cookie中获取session_id,并通过该session_id从Redis服务器获取Session信息并显示,将此源代码添加进去。
请根据您自己的环境设置Redis.new中的主机和端口。

class TasksController < ApplicationController
  before_action :set_task, only: [:show, :edit, :update, :destroy]

  def index
    redis_namespace = "session"
    cookie_key = cookies[Rails.application.config.session_options[:key]]

    session_key = "#{redis_namespace}:#{cookie_key}"

    redis = Redis.new(:host => "redis", :port => 6379)
    puts Marshal.load(redis.get(session_key))

    session['foo'] = 'foo'

    @tasks = Task.all
  end
・・・

确认

成功的话,请访问 http://localhost:3000/tasks,并在控制台输出会话信息。

▼ 私の環境での出力内容

{"foo"=>"foo"}

如果您想确认在Redis服务器中是否保存了会话,请说明。

redis-cli
keys *
# keys * で取得したsession:~の内容をgetする
get session:~

如果有 Session 的话就算成功了。

应用

为Session设置有效期限

AppName::Application.config.session_store :redis_store, {
  servers: [
             {
               host: "redis",
               port: 6379,
               db: 0,
               namespace: "session"
             },
           ],
  #この1行を追加↓
  expire_after: 10.seconds,
  key: "_#{Rails.application.class.parent_name.downcase}_session"
}

重新启动服务器,访问http://localhost:3000/tasks。
在Redis服务器上检查会话,如果会话在10秒后消失了,则表示成功。

ActiveRecord(数据库方法)

系统

Railsはsession_idを作成し、Session情報と共にDBサーバに送信する。
クライアントのCookieには作成したsession_idのみを保存し、リクエストの際にsession_idを送信する。
Railsはクライアントから受け取ったsession_idをDBサーバに検索させ、ユーザの特定・Session情報の取得を行う。

好处

    • DBに保存されているため、データが常に永続化されている。

 

    • 基本的にsession情報に比べるとDBの容量は遥かに大きいため、容量を気にせず情報を保存できる。

 

    クライアントにはIDしか保存しないため、情報漏洩のリスクが低い。

缺点 (quē

    • DBに対して情報を検索する必要があるため、処理が遅い。(インメモリに比べて1000倍ほど処理時間が掛かると言われている)

 

    エクスパイア処理をバッチ等で作成するか、またはSession情報を溜めっぱなしにすることになる。

实践 (shí

准备

构建Rails应用程序

rails new ActiveRecordTest
cd ActiveRecordTest
bundle install
rails g scaffold Task title:string status:integer

在Gemfile中添加activerecord-session_store。

・・・
gem 'activerecord-session_store'

▼ 安装gem

bundle install

将activerecord-session_store设置为session_store。

AppName::Application.config.session_store :active_record_store, key: "_#{Rails.application.class.parent_name.downcase}_session"

创建用于Session管理的表

rails generate active_record:session_migration
rake db:migrate

从Cookie中获取session_id,然后使用该session_id从DB服务器获取Session的信息,并添加显示的源代码。

class TasksController < ApplicationController
  before_action :set_task, only: [:show, :edit, :update, :destroy]

  def index
    session_data = Session.find_by(session_id: cookies[Rails.application.config.session_options[:key]])[:data]
    data_dec64 = Base64.decode64(session_data)
    puts Marshal.load(data_dec64)
    session["foo"] = "foo"
    @tasks = Task.all
  end
・・・

确认

要求访问 http://localhost:3000/tasks,如果会话信息成功输出到控制台,则表示成功。

在我的环境下的输出内容。

{"foo"=>"foo", "_csrf_token"=>"gC++OkzlxWAXudi4vIN1vFZLXNX/G8rZxCIElnfqdJE="}

另外,还需要确认一下会话是否保存在数据库的sessions表中。

最佳解答

每种方法都有优缺点,各有千秋。
没有最佳解,感觉应根据产品和环境的不同来选择采用的方法。
以下是相关内容。

曲奇店

当遭受会话重放攻击并不带来巨大风险时,应该采用该方法。特别是与其他应用程序的协作更容易,这是一个重要的优势。

Redis (中文:远程字典服务)

如果存在承受会话重放攻击的高风险情况,并且希望在服务器端进行会话信息的更改,应该采用这种方法。尽管成本较高,但能够尽可能地减少风险并且具备快速处理速度,因此可以称之为混合管理方法。

ActiveRecord

活动记录

如果在存在重播攻击风险的情况下,并且希望在服务器上进行会话信息修改且希望控制成本,这时应该采用这种方法。可用性很高,但由于处理速度较慢且需要创建过期处理,我觉得不应主动采用。

广告
将在 10 秒后关闭
bannerAds