随着新版本的safari 和 chrome 更新安全策略
As Google rolls out Chrome 80 starting February 4, people could soon become even more frustrated when using the browser. The issue stems from a change to SameSite cookies in the new version of Chrome that could break some websites’ functionality. According to Google, the update will start to come into force from February 17.
此处基于Ci2.0框架给出两个解决方案:
- 只处理 主版本高于67的chrome浏览器
_driver = $params['driver'];
unset($params['driver']);
} elseif ($driver = config_item('sess_driver')) {
$this->_driver = $driver;
}
// Note: BC workaround
elseif (config_item('sess_use_database')) {
log_message('debug', 'Session: "sess_driver" is empty; using BC fallback to "sess_use_database".');
$this->_driver = 'database';
}
$class = $this->_ci_load_classes($this->_driver);
// Configuration ...
$this->_configure($params);
$this->_config['_sid_regexp'] = $this->_sid_regexp;
$class = new $class($this->_config);
if ($class instanceof SessionHandlerInterface) {
if (is_php('5.4')) {
session_set_save_handler($class, true);
} else {
session_set_save_handler(
array($class, 'open'),
array($class, 'close'),
array($class, 'read'),
array($class, 'write'),
array($class, 'destroy'),
array($class, 'gc')
);
register_shutdown_function('session_write_close');
}
} else {
log_message('error', "Session: Driver '".$this->_driver."' doesn't implement SessionHandlerInterface. Aborting.");
return;
}
// Sanitize the cookie, because apparently PHP doesn't do that for userspace handlers
if (isset($_COOKIE[$this->_config['cookie_name']])
&& (
! is_string($_COOKIE[$this->_config['cookie_name']])
or ! preg_match('#\A'.$this->_sid_regexp.'\z#', $_COOKIE[$this->_config['cookie_name']])
)
) {
unset($_COOKIE[$this->_config['cookie_name']]);
}
session_start();
// Is session ID auto-regeneration configured? (ignoring ajax requests)
if ((empty($_SERVER['HTTP_X_REQUESTED_WITH']) or strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) !== 'xmlhttprequest')
&& ($regenerate_time = config_item('sess_time_to_update')) > 0
) {
if (! isset($_SESSION['__ci_last_regenerate'])) {
$_SESSION['__ci_last_regenerate'] = time();
} elseif ($_SESSION['__ci_last_regenerate'] < (time() - $regenerate_time)) {
$this->sess_regenerate((bool) config_item('sess_regenerate_destroy'));
}
}
// Another work-around ... PHP doesn't seem to send the session cookie
// unless it is being currently created or regenerated
elseif (isset($_COOKIE[$this->_config['cookie_name']]) && $_COOKIE[$this->_config['cookie_name']] === session_id()) {
setcookie(
$this->_config['cookie_name'],
session_id(),
(empty($this->_config['cookie_lifetime']) ? 0 : time() + $this->_config['cookie_lifetime']),
$this->_config['cookie_path'],
$this->_config['cookie_domain'],
$this->_config['cookie_secure'],
true
);
}
$this->_ci_init_vars();
log_message('info', "Session: Class initialized using '".$this->_driver."' driver.");
}
// ------------------------------------------------------------------------
/**
* CI Load Classes
*
* An internal method to load all possible dependency and extension
* classes. It kind of emulates the CI_Driver library, but is
* self-sufficient.
*
* @param string $driver Driver name
* @return string Driver class name
*/
protected function _ci_load_classes($driver)
{
// PHP 5.4 compatibility
interface_exists('SessionHandlerInterface', false) or require_once(BASEPATH.'libraries/Session/SessionHandlerInterface.php');
$prefix = config_item('subclass_prefix');
if (! class_exists('CI_Session_driver', false)) {
require_once(
file_exists(APPPATH.'libraries/Session/Session_driver.php')
? APPPATH.'libraries/Session/Session_driver.php'
: BASEPATH.'libraries/Session/Session_driver.php'
);
if (file_exists($file_path = APPPATH.'libraries/Session/'.$prefix.'Session_driver.php')) {
require_once($file_path);
}
}
$class = 'Session_'.$driver.'_driver';
// Allow custom drivers without the CI_ or MY_ prefix
if (! class_exists($class, false) && file_exists($file_path = APPPATH.'libraries/Session/drivers/'.$class.'.php')) {
require_once($file_path);
if (class_exists($class, false)) {
return $class;
}
}
if (! class_exists('CI_'.$class, false)) {
if (file_exists($file_path = APPPATH.'libraries/Session/drivers/'.$class.'.php') or file_exists($file_path = BASEPATH.'libraries/Session/drivers/'.$class.'.php')) {
require_once($file_path);
}
if (! class_exists('CI_'.$class, false) && ! class_exists($class, false)) {
throw new UnexpectedValueException("Session: Configured driver '".$driver."' was not found. Aborting.");
}
}
if (! class_exists($prefix.$class, false) && file_exists($file_path = APPPATH.'libraries/Session/drivers/'.$prefix.$class.'.php')) {
require_once($file_path);
if (class_exists($prefix.$class, false)) {
return $prefix.$class;
}
log_message('debug', 'Session: '.$prefix.$class.".php found but it doesn't declare class ".$prefix.$class.'.');
}
return 'CI_'.$class;
}
// ------------------------------------------------------------------------
private function isSupportSamesiteNone()
{
$o = get_instance();
$o->load->library('user_agent');
// https://www.chromium.org/updates/same-site/incompatible-clients
// chrome 67 之后 浏览器才支持 samesite=NONE;
$isChrome = $o->agent->is_browser('Chrome');
if ($isChrome && version_compare($o->agent->version(), '67', '>=')) {
return true;
}
return false;
}
/**
* Configuration
*
* Handle input parameters and configuration defaults
*
* @param array &$params Input parameters
* @return void
*/
protected function _configure(&$params)
{
$expiration = config_item('sess_expiration');
if (isset($params['cookie_lifetime'])) {
$params['cookie_lifetime'] = (int) $params['cookie_lifetime'];
} else {
$params['cookie_lifetime'] = ( ! isset($expiration) && config_item('sess_expire_on_close'))
? 0 : (int) $expiration;
}
isset($params['cookie_name']) or $params['cookie_name'] = config_item('sess_cookie_name');
if (empty($params['cookie_name'])) {
$params['cookie_name'] = ini_get('session.name');
} else {
ini_set('session.name', $params['cookie_name']);
}
isset($params['cookie_path']) or $params['cookie_path'] = config_item('cookie_path');
isset($params['cookie_domain']) or $params['cookie_domain'] = config_item('cookie_domain');
isset($params['cookie_secure']) or $params['cookie_secure'] = (bool) config_item('cookie_secure');
// OBT-2862
if ($this->isSupportSamesiteNone()) {
$params['cookie_path'] = "/;samesite=NONE;";
$params['cookie_secure'] = true;
}
session_set_cookie_params(
$params['cookie_lifetime'],
$params['cookie_path'],
$params['cookie_domain'],
$params['cookie_secure'],
true // HttpOnly; Yes, this is intentional and not configurable for security reasons
);
if (empty($expiration)) {
$params['expiration'] = (int) ini_get('session.gc_maxlifetime');
} else {
$params['expiration'] = (int) $expiration;
ini_set('session.gc_maxlifetime', $expiration);
}
$params['match_ip'] = (bool) (isset($params['match_ip']) ? $params['match_ip'] : config_item('sess_match_ip'));
isset($params['save_path']) or $params['save_path'] = config_item('sess_save_path');
$this->_config = $params;
// Security is king
ini_set('session.use_trans_sid', 0);
ini_set('session.use_strict_mode', 1);
ini_set('session.use_cookies', 1);
ini_set('session.use_only_cookies', 1);
$this->_configure_sid_length();
}
// ------------------------------------------------------------------------
/**
* Configure session ID length
*
* To make life easier, we used to force SHA-1 and 4 bits per
* character on everyone. And of course, someone was unhappy.
*
* Then PHP 7.1 broke backwards-compatibility because ext/session
* is such a mess that nobody wants to touch it with a pole stick,
* and the one guy who does, nobody has the energy to argue with.
*
* So we were forced to make changes, and OF COURSE something was
* going to break and now we have this pile of shit. -- Narf
*
* @return void
*/
protected function _configure_sid_length()
{
if (PHP_VERSION_ID < 70100) {
$hash_function = ini_get('session.hash_function');
if (ctype_digit($hash_function)) {
if ($hash_function !== '1') {
ini_set('session.hash_function', 1);
}
$bits = 160;
} elseif (! in_array($hash_function, hash_algos(), true)) {
ini_set('session.hash_function', 1);
$bits = 160;
} elseif (($bits = strlen(hash($hash_function, 'dummy', false)) * 4) < 160) {
ini_set('session.hash_function', 1);
$bits = 160;
}
$bits_per_character = (int) ini_get('session.hash_bits_per_character');
$sid_length = (int) ceil($bits / $bits_per_character);
} else {
$bits_per_character = (int) ini_get('session.sid_bits_per_character');
$sid_length = (int) ini_get('session.sid_length');
if (($bits = $sid_length * $bits_per_character) < 160) {
// Add as many more characters as necessary to reach at least 160 bits
$sid_length += (int) ceil((160 % $bits) / $bits_per_character);
ini_set('session.sid_length', $sid_length);
}
}
// Yes, 4,5,6 are the only known possible values as of 2016-10-27
switch ($bits_per_character) {
case 4:
$this->_sid_regexp = '[0-9a-f]';
break;
case 5:
$this->_sid_regexp = '[0-9a-v]';
break;
case 6:
$this->_sid_regexp = '[0-9a-zA-Z,-]';
break;
}
$this->_sid_regexp .= '{'.$sid_length.'}';
}
// ------------------------------------------------------------------------
/**
* Handle temporary variables
*
* Clears old "flash" data, marks the new one for deletion and handles
* "temp" data deletion.
*
* @return void
*/
protected function _ci_init_vars()
{
if (! empty($_SESSION['__ci_vars'])) {
$current_time = time();
foreach ($_SESSION['__ci_vars'] as $key => &$value) {
if ($value === 'new') {
$_SESSION['__ci_vars'][$key] = 'old';
}
// Hacky, but 'old' will (implicitly) always be less than time() ;)
// DO NOT move this above the 'new' check!
elseif ($value < $current_time) {
unset($_SESSION[$key], $_SESSION['__ci_vars'][$key]);
}
}
if (empty($_SESSION['__ci_vars'])) {
unset($_SESSION['__ci_vars']);
}
}
$this->userdata =& $_SESSION;
}
// ------------------------------------------------------------------------
/**
* Mark as flash
*
* @param mixed $key Session data key(s)
* @return bool
*/
public function mark_as_flash($key)
{
if (is_array($key)) {
for ($i = 0, $c = count($key); $i < $c; $i++) {
if (! isset($_SESSION[$key[$i]])) {
return false;
}
}
$new = array_fill_keys($key, 'new');
$_SESSION['__ci_vars'] = isset($_SESSION['__ci_vars'])
? array_merge($_SESSION['__ci_vars'], $new)
: $new;
return true;
}
if (! isset($_SESSION[$key])) {
return false;
}
$_SESSION['__ci_vars'][$key] = 'new';
return true;
}
// ------------------------------------------------------------------------
/**
* Get flash keys
*
* @return array
*/
public function get_flash_keys()
{
if (! isset($_SESSION['__ci_vars'])) {
return array();
}
$keys = array();
foreach (array_keys($_SESSION['__ci_vars']) as $key) {
is_int($_SESSION['__ci_vars'][$key]) or $keys[] = $key;
}
return $keys;
}
// ------------------------------------------------------------------------
/**
* Unmark flash
*
* @param mixed $key Session data key(s)
* @return void
*/
public function unmark_flash($key)
{
if (empty($_SESSION['__ci_vars'])) {
return;
}
is_array($key) or $key = array($key);
foreach ($key as $k) {
if (isset($_SESSION['__ci_vars'][$k]) && ! is_int($_SESSION['__ci_vars'][$k])) {
unset($_SESSION['__ci_vars'][$k]);
}
}
if (empty($_SESSION['__ci_vars'])) {
unset($_SESSION['__ci_vars']);
}
}
// ------------------------------------------------------------------------
/**
* Mark as temp
*
* @param mixed $key Session data key(s)
* @param int $ttl Time-to-live in seconds
* @return bool
*/
public function mark_as_temp($key, $ttl = 300)
{
$ttl += time();
if (is_array($key)) {
$temp = array();
foreach ($key as $k => $v) {
// Do we have a key => ttl pair, or just a key?
if (is_int($k)) {
$k = $v;
$v = $ttl;
} else {
$v += time();
}
if (! isset($_SESSION[$k])) {
return false;
}
$temp[$k] = $v;
}
$_SESSION['__ci_vars'] = isset($_SESSION['__ci_vars'])
? array_merge($_SESSION['__ci_vars'], $temp)
: $temp;
return true;
}
if (! isset($_SESSION[$key])) {
return false;
}
$_SESSION['__ci_vars'][$key] = $ttl;
return true;
}
// ------------------------------------------------------------------------
/**
* Get temp keys
*
* @return array
*/
public function get_temp_keys()
{
if (! isset($_SESSION['__ci_vars'])) {
return array();
}
$keys = array();
foreach (array_keys($_SESSION['__ci_vars']) as $key) {
is_int($_SESSION['__ci_vars'][$key]) && $keys[] = $key;
}
return $keys;
}
// ------------------------------------------------------------------------
/**
* Unmark temp
*
* @param mixed $key Session data key(s)
* @return void
*/
public function unmark_temp($key)
{
if (empty($_SESSION['__ci_vars'])) {
return;
}
is_array($key) or $key = array($key);
foreach ($key as $k) {
if (isset($_SESSION['__ci_vars'][$k]) && is_int($_SESSION['__ci_vars'][$k])) {
unset($_SESSION['__ci_vars'][$k]);
}
}
if (empty($_SESSION['__ci_vars'])) {
unset($_SESSION['__ci_vars']);
}
}
// ------------------------------------------------------------------------
/**
* __get()
*
* @param string $key 'session_id' or a session data key
* @return mixed
*/
public function __get($key)
{
// Note: Keep this order the same, just in case somebody wants to
// use 'session_id' as a session data key, for whatever reason
if (isset($_SESSION[$key])) {
return $_SESSION[$key];
} elseif ($key === 'session_id') {
return session_id();
}
return null;
}
// ------------------------------------------------------------------------
/**
* __isset()
*
* @param string $key 'session_id' or a session data key
* @return bool
*/
public function __isset($key)
{
if ($key === 'session_id') {
return (session_status() === PHP_SESSION_ACTIVE);
}
return isset($_SESSION[$key]);
}
// ------------------------------------------------------------------------
/**
* __set()
*
* @param string $key Session data key
* @param mixed $value Session data value
* @return void
*/
public function __set($key, $value)
{
$_SESSION[$key] = $value;
}
// ------------------------------------------------------------------------
/**
* Session destroy
*
* Legacy CI_Session compatibility method
*
* @return void
*/
public function sess_destroy()
{
session_destroy();
}
// ------------------------------------------------------------------------
/**
* Session regenerate
*
* Legacy CI_Session compatibility method
*
* @param bool $destroy Destroy old session data flag
* @return void
*/
public function sess_regenerate($destroy = false)
{
$_SESSION['__ci_last_regenerate'] = time();
session_regenerate_id($destroy);
}
// ------------------------------------------------------------------------
/**
* Get userdata reference
*
* Legacy CI_Session compatibility method
*
* @returns array
*/
public function &get_userdata()
{
return $_SESSION;
}
// ------------------------------------------------------------------------
/**
* Userdata (fetch)
*
* Legacy CI_Session compatibility method
*
* @param string $key Session data key
* @return mixed Session data value or NULL if not found
*/
public function userdata($key = null)
{
if (isset($key)) {
return isset($_SESSION[$key]) ? $_SESSION[$key] : null;
} elseif (empty($_SESSION)) {
return array();
}
$userdata = array();
$_exclude = array_merge(
array('__ci_vars'),
$this->get_flash_keys(),
$this->get_temp_keys()
);
foreach (array_keys($_SESSION) as $key) {
if (! in_array($key, $_exclude, true)) {
$userdata[$key] = $_SESSION[$key];
}
}
return $userdata;
}
// ------------------------------------------------------------------------
/**
* Set userdata
*
* Legacy CI_Session compatibility method
*
* @param mixed $data Session data key or an associative array
* @param mixed $value Value to store
* @return void
*/
public function set_userdata($data, $value = null)
{
if (is_array($data)) {
foreach ($data as $key => &$value) {
$_SESSION[$key] = $value;
}
return;
}
$_SESSION[$data] = $value;
}
// ------------------------------------------------------------------------
/**
* Unset userdata
*
* Legacy CI_Session compatibility method
*
* @param mixed $key Session data key(s)
* @return void
*/
public function unset_userdata($key)
{
if (is_array($key)) {
foreach ($key as $k) {
unset($_SESSION[$k]);
}
return;
}
unset($_SESSION[$key]);
}
// ------------------------------------------------------------------------
/**
* All userdata (fetch)
*
* Legacy CI_Session compatibility method
*
* @return array $_SESSION, excluding flash data items
*/
public function all_userdata()
{
return $this->userdata();
}
// ------------------------------------------------------------------------
/**
* Has userdata
*
* Legacy CI_Session compatibility method
*
* @param string $key Session data key
* @return bool
*/
public function has_userdata($key)
{
return isset($_SESSION[$key]);
}
// ------------------------------------------------------------------------
/**
* Flashdata (fetch)
*
* Legacy CI_Session compatibility method
*
* @param string $key Session data key
* @return mixed Session data value or NULL if not found
*/
public function flashdata($key = null)
{
if (isset($key)) {
return (isset($_SESSION['__ci_vars'], $_SESSION['__ci_vars'][$key], $_SESSION[$key]) && ! is_int($_SESSION['__ci_vars'][$key]))
? $_SESSION[$key]
: null;
}
$flashdata = array();
if (! empty($_SESSION['__ci_vars'])) {
foreach ($_SESSION['__ci_vars'] as $key => &$value) {
is_int($value) or $flashdata[$key] = $_SESSION[$key];
}
}
return $flashdata;
}
// ------------------------------------------------------------------------
/**
* Set flashdata
*
* Legacy CI_Session compatibility method
*
* @param mixed $data Session data key or an associative array
* @param mixed $value Value to store
* @return void
*/
public function set_flashdata($data, $value = null)
{
$this->set_userdata($data, $value);
$this->mark_as_flash(is_array($data) ? array_keys($data) : $data);
}
// ------------------------------------------------------------------------
/**
* Keep flashdata
*
* Legacy CI_Session compatibility method
*
* @param mixed $key Session data key(s)
* @return void
*/
public function keep_flashdata($key)
{
$this->mark_as_flash($key);
}
// ------------------------------------------------------------------------
/**
* Temp data (fetch)
*
* Legacy CI_Session compatibility method
*
* @param string $key Session data key
* @return mixed Session data value or NULL if not found
*/
public function tempdata($key = null)
{
if (isset($key)) {
return (isset($_SESSION['__ci_vars'], $_SESSION['__ci_vars'][$key], $_SESSION[$key]) && is_int($_SESSION['__ci_vars'][$key]))
? $_SESSION[$key]
: null;
}
$tempdata = array();
if (! empty($_SESSION['__ci_vars'])) {
foreach ($_SESSION['__ci_vars'] as $key => &$value) {
is_int($value) && $tempdata[$key] = $_SESSION[$key];
}
}
return $tempdata;
}
// ------------------------------------------------------------------------
/**
* Set tempdata
*
* Legacy CI_Session compatibility method
*
* @param mixed $data Session data key or an associative array of items
* @param mixed $value Value to store
* @param int $ttl Time-to-live in seconds
* @return void
*/
public function set_tempdata($data, $value = null, $ttl = 300)
{
$this->set_userdata($data, $value);
$this->mark_as_temp(is_array($data) ? array_keys($data) : $data, $ttl);
}
// ------------------------------------------------------------------------
/**
* Unset tempdata
*
* Legacy CI_Session compatibility method
*
* @param mixed $data Session data key(s)
* @return void
*/
public function unset_tempdata($key)
{
$this->unmark_temp($key);
}
}
- 支持所有浏览器, 方案代码就不贴了,可付费支持
策略影响说明:
http://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html
https://gist.github.com/iansltx/18caf551baaa60b79206
https://www.chromium.org/updates/same-site
https://www.chromium.org/updates/same-site/incompatible-clients