世界上最伟大的投资就是投资自己的教育
session 原理与实现 (rails 篇)
1. 介绍
上一篇文章cookie 原理与实现 (rails 篇)有说过在 rails 是如何存取 cookie 的,而这一篇文章来讲讲 rails 是如何操作 session 的。
默认情况下,session 是保存在 cookie 中的。这又是怎么回事呢。众所周知,cookie 是存在客户端浏览器中的,把 session 放在 cookie 中,可以吗?答案当然是可以。那安全吗?还算安全。就算有人得到了那 cookie,它也解密不了,因为存在 cookie 中的 session 是通过对称加密处理的,没有密钥是得不到原串的。
rails 中关于存储 session 的配置是这样的:
# config/initializers/session_store.rb
Rails.application.config.session_store :cookie_store, key: '_rails365_session'
所以的 session 信息,都会存放到 cookies 中,且 key 为_rails365_session
。
2. ActionDispatch::Session::CookieStore
实现把 session 存放到 cookie 中的功能都依靠这个中间件:ActionDispatch::Session::CookieStore
来看下实现这个功能相关的源码:
# https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/session/cookie_store.rb
# 得到非加密的session数据,就是原来的session数据
def unpacked_cookie_data(req)
req.fetch_header("action_dispatch.request.unsigned_session_cookie") do |k|
v = stale_session_check! do
if data = get_cookie(req)
data.stringify_keys!
end
data || {}
end
req.set_header k, v
end
end
# session数据包含session_id,值为随机码
def write_session(req, sid, session_data, options)
session_data["session_id"] = sid
session_data
end
# 把session数据存储到cookie中
def set_cookie(request, session_id, cookie)
cookie_jar(request)[@key] = cookie
end
def get_cookie(req)
cookie_jar(req)[@key]
end
来看看unpacked_cookie_data
的实现,它是得到解密后 session 的数据。
比如下面这样:
env["action_dispatch.request.unsigned_session_cookie"]
{
"session_id" => "760be4b1069ab0c80ccade6d36f00355",
"_csrf_token" => "QdtMjYciHnF8XqSCe0xr8nHo3N5pQdhNeKWhe5ZxOC4=",
"admin" => true
}
或者
session
{
"session_id" => "760be4b1069ab0c80ccade6d36f00355",
"_csrf_token" => "QdtMjYciHnF8XqSCe0xr8nHo3N5pQdhNeKWhe5ZxOC4=",
"admin" => true
}
上面的数据都可以在 controller 里得到。
3. rack/session/cookie.rb
也就是说,从客户端传给服务器的加密过的 cookie 已经被先被解密了。
是的,已经先被处理了。
cookie_store.rb
的源码,有一行是这样的:
require 'rack/session/cookie'
是 rack 程序,我们找到其源码。
# https://github.com/rack/rack/blob/master/lib/rack/session/cookie.rb
def unpacked_cookie_data(request)
request.fetch_header(RACK_SESSION_UNPACKED_COOKIE_DATA) do |k|
session_data = request.cookies[@key]
if @secrets.size > 0 && session_data
digest, session_data = session_data.reverse.split("--", 2)
digest.reverse! if digest
session_data.reverse! if session_data
session_data = nil unless digest_match?(session_data, digest)
end
request.set_header(k, coder.decode(session_data) || {})
end
end
def write_session(req, session_id, session, options)
session = session.merge("session_id" => session_id)
session_data = coder.encode(session)
if @secrets.first
session_data << "--#{generate_hmac(session_data, @secrets.first)}"
end
if session_data.size > (4096 - @key.size)
req.get_header(RACK_ERRORS).puts("Warning! Rack::Session::Cookie data size exceeds 4K.")
nil
else
session_data
end
end
def generate_hmac(data, secret)
OpenSSL::HMAC.hexdigest(@hmac.new, secret, data)
end
可见,不仅解密,连加密也是在那个 rack 程序中实现的。
使用的加密算法是最常用的OpenSSL::HMAC
算法。
这个算法使用的 secret 或 salt 是@secrets.first
,@secrets
是在下面这里定义的:
# https://github.com/rails/rails/blob/d50d7094247aad5005cd1b47258ddf338b0dddd7/railties/lib/rails/application.rb#L383
def secrets
@secrets ||= begin
secrets = ActiveSupport::OrderedOptions.new
yaml = config.paths["config/secrets"].first
if File.exist?(yaml)
require "erb"
all_secrets = YAML.load(ERB.new(IO.read(yaml)).result) || {}
env_secrets = all_secrets[Rails.env]
secrets.merge!(env_secrets.symbolize_keys) if env_secrets
end
# Fallback to config.secret_key_base if secrets.secret_key_base isn't set
secrets.secret_key_base ||= config.secret_key_base
# Fallback to config.secret_token if secrets.secret_token isn't set
secrets.secret_token ||= config.secret_token
secrets
end
end
其实就是加载config/secrets.yml
文件取得里面的secret_key_base
参数的值,作为 secret。
也就是说,session 以 cookie 的方式存储的加密还是会依赖于secret_key_base
参数的值,所以secret_key_base
是不能泄漏的。
完结。
本站文章均为原创内容,如需转载请注明出处,谢谢。
© 汕尾市求知科技有限公司 | Rails365 Gitlab | 知乎 | b 站 | csdn
粤公网安备 44152102000088号 | 粤ICP备19038915号
Top