本文内容紧接之前的那篇文章,因为个人喜欢没事找事做,所以才会对一些实现细节比较感兴趣。比如 Laravel 的 Session 实现。
既然 Laravel 的 Session 并不是采用 PHP 原生的 Session 框架,那么它的这重起炉灶的一套 Session 机制,有什么更好的地方,或者说更优雅的地方?
我们先来看 PHP 的原生 Session 实现。PHP 初学者,勿喷。。
Session 是什么
自行谷歌或者 Bing。
PHP 原生 Session 机制
这主要涉及三个方面,Session 的生成,回收,存储
Session 的生成
这部分主要见于 PHP 源码,以手头的 PHP7 的源码为例,具体位于 /ext/session/session.c 中的 *php_session_create_id 方法。 下面为了解释方便,省略部分无用代码。
PHPAPI zend_string *php_session_create_id(PS_CREATE_SID_ARGS) /* {{{ */
{
PHP_MD5_CTX md5_context;
PHP_SHA1_CTX sha1_context;
gettimeofday(&tv, NULL); // 获得时间戳
if ((array = zend_hash_str_find(&EG(symbol_table),
"_SERVER",
sizeof("_SERVER") - 1))
&& Z_TYPE_P(array) == IS_ARRAY
&& (token = zend_hash_str_find(Z_ARRVAL_P(array),
"REMOTE_ADDR",
sizeof("REMOTE_ADDR") - 1))
&& Z_TYPE_P(token) == IS_STRING)
{
remote_addr = Z_STRVAL_P(token);
}
// 如果可能,获得用户的地址
/* maximum 15+19+19+10 bytes */
spprintf(&buf, 0, "%.15s%ld" ZEND_LONG_FMT "%0.8F",
remote_addr ? remote_addr : "",
tv.tv_sec,
(zend_long)tv.tv_usec,
php_combined_lcg() * 10);
//%.15s remote_addr ? remote_addr : "" 这是用户的地址
//%ld tv.tv_sec 当前的时间戳
//%ld (long int)tv.tv_usec 当前毫秒数
//%0.8F php_combined_lcg(TSRMLS_C) * 10 一个随机数
switch (PS(hash_func)) {
case PS_HASH_FUNC_MD5: // 用 MD5 做处理
PHP_MD5Init(&md5_context);
PHP_MD5Update(&md5_context, (unsigned char *) buf, strlen(buf));
digest_len = 16;
break;
case PS_HASH_FUNC_SHA1: // 用 SHA1 做处理
PHP_SHA1Init(&sha1_context);
PHP_SHA1Update(&sha1_context, (unsigned char *) buf, strlen(buf));
digest_len = 20;
break;
}
efree(buf);
if (PS(entropy_length) > 0) { // 这个东西是用来减少碰撞概率的
...
// 具体的方式是从一个随机的文件中读出随机长度的内容
}
outid = zend_string_alloc((digest_len + 2) * ((8.0f / PS(hash_bits_per_character) + 0.5)), 0);
ZSTR_LEN(outid) = (size_t)(bin_to_readable((char *)digest,
digest_len,
ZSTR_VAL(outid),
(char)PS(hash_bits_per_character))-(char *)&ZSTR_VAL(outid));
// 散列后的二进制数据digest用字符串表示成可读的形式,并放置在outid字符串里
efree(digest);
// 此时的 outid 就是最终的 Session ID
return outid;
}
/* }}} */
总的来说,这个碰撞的概率确实挺低的,而且入侵的难度很高。
通常,PHP 默认是通过 Cookie 将这个 Session ID 存储到客户端,所以我们打开伟大的 FireFox,通过开发者工具,就可以看到如下的 Session ID。
那么,如果用户禁用了 Cookie,PHP 则会自动切换到 URL 重写的模式,比如生成这样的 URL:
http://d0n9x1n.dev/index.php?SESSIONID=2bd170b3f86523f1b1b60b55ffde0f66
PHP 中 Session 的回收和存储
前面说太多了- -,这边简单点。
在 php.ini 中 session.gc_maxlifetime 为 session 设置了生存时间(默认为1440s)。
如果 session 文件的最后更新时间到现在超过了生存时间,这个 session 文件就被认为是过期的了。在下一次 session 回收的时候就会被删除。
那下一次 session 回收是在什么时候呢?这和 php 请求次数有关的。在 PHP 内部机制中,当 php 被请求了N次后就会有一次触发回收机制。到底是请求多少次触发一次是通过以下两个参数控制的:
session.gc_probability = 1
session.gc_divisor = 100
这是 php.ini 的默认设置,意思是每100次PHP请求就有一次回收发生。概率是 gc_probability/gc_divisor。
那么这些 Session 存在什么地方?当然是文件里。php.ini 中的 session.save_path这一项,就是负责session文件的存放位置。当然,如果不填写,Ubuntu 默认则是 /var/lib/php/session 下。
说了这么多,似乎PHP 原生的 Session 机制,并没有什么大的问题,而且看上去实现的还算是优雅。那么,为什么 Laravel 需要重新设计一套与原生没有一点联系的 Session 机制?
Laravel Session 由来
详情见Laravel 开发者的自白, 英文不太好,翻译轻喷。大意就是 PHP 原生的 Session 直接写进了请求的头部,没有办法进行修改,这样违背了他们想要达到的封装一切设计理念。
Laravel Session 产生
所以,他们就自己重新开发了一套。 – -。那么,为什么他们需要修改 Session?其实,主要是为了防止 CSRF。通过阅读 Laravel 源码,我们可以看到他的 Session 生成思路:
代码在:vendor/laravel/framework/src/Illuminate/Session/Store.php中
/**
* Get a new, random session ID.
*
* @return string
*/
protected function generateSessionId()
{
return sha1(uniqid('', true).Str::random(25).microtime(true));
}
即对 uniqid 拼接上一个25位的随机字符串然后还有当前时间戳的 sha1 值, 比如,可以通过火狐的开发者工具看到 Laravel 的 Session ID。
有可能我们会发现他超过了sha1的长度,原因也很简单,因为我们开启了 CSRF 中间件。
那么,如果我们需要在不同的系统中公用 Session 怎么办?
因为 Laravel 的 Session 是不兼容 PHP 的 Session 的。网上常用的说法是通过写一个转换器,或者是解析器来解决。
我这边提供一个思路,就是重写 Laravel 的 Session,将生成 Session ID 直接等同于 PHP 原生的 Session。这个其实挺方便的。不过这就和 Laravel 的设计思路违背了。
相对来说一个比较优雅的方法,我觉得是通过原有系统的接口来做。而非简单的直接读 Session 文件。
蛤蛤蛤,之前我也遇到这个问题,在web group中才能访问session,我刚开始还以为目录没有写权限。
5.2之后更新了不少东西,还是需要仔细看一下release notes。
另外,.cn的域名没有备案被禁止访问了。。。
@forehalo:那个已经在等审核了。