thinkphp5.1. session sharing solution for multi domain jump problem

Posted by Zooter on Sun, 30 Jan 2022 04:52:15 +0100

Catalogue of series articles

Recently, I encountered the problem of jumping from multiple domain names to session s. Here is a Demo to record my ideas

Preparation conditions

1. Framework thinkphp5 one
2. apache server
3.MySQL database

I Problem description

Suppose I have two sites with different domain names, a.com and b.com, which have login between the two sites. As a user, I hope that after a.com login, I can not log in to b.com, but directly enter the home page of b.com, so how can I achieve it?

II Implementation process

1. Write the login control program of the two sites (it is required that the home page and related interfaces cannot be accessed without login, which can be realized by middleware)

Insert picture description here

This page is the unified login portal of a.com and b.com

2. Enter the account password in this unified portal, such as admin 123456. After the backend program successfully verifies, it obtains the sessionID obtained from the cookie and jumps to a prompt page, which is called info html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Jump in progress...</title>
</head>
<body>
  Skipping....
  <iframe width="0" height="0" src="http://a.com/index/login/set_cookie?sid={$sid}"></iframe>
  <iframe width="0" height="0" src="http://b.com/index/login/set_cookie?sid={$sid}"></iframe>
</body>
</html>
<script>

   setTimeout(function () {
       window.location.href='http://b.com/index/index/index';
   },1000);
</script>

Where, set of a.com and b.com_ The cookie method is as follows:

public function  set_cookie()
    {
        $sid = input('sid');
        session_id($sid);
        session_start();
    }

3. Rewrite the session saving mechanism of thinkphp

As we know, php's default sessions are saved in files. When there are multiple sites and servers, each server will generate a file to save the session information of user login. No contact can be established in each file, so the user session information of multiple servers cannot be shared. We can save the session ID of php to redis or MySQL database, and I will save it to MySQL database here.
php reserves an interface for the session saving mechanism. As long as we implement its standard, we can save the session ID to the location we want to save

<?php
/**
 * <b>SessionHandlerInterface</b> is an interface which defines
 * a prototype for creating a custom session handler.
 * In order to pass a custom session handler to
 * session_set_save_handler() using its OOP invocation,
 * the class must implement this interface.
 * @link http://php.net/manual/en/class.sessionhandlerinterface.php
 * @since 5.4.0
 */
interface SessionHandlerInterface {

	/**
	 * Close the session
	 * @link http://php.net/manual/en/sessionhandlerinterface.close.php
	 * @return bool <p>
	 * The return value (usually TRUE on success, FALSE on failure).
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function close();

	/**
	 * Destroy a session
	 * @link http://php.net/manual/en/sessionhandlerinterface.destroy.php
	 * @param string $session_id The session ID being destroyed.
	 * @return bool <p>
	 * The return value (usually TRUE on success, FALSE on failure).
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function destroy($session_id);

	/**
	 * Cleanup old sessions
	 * @link http://php.net/manual/en/sessionhandlerinterface.gc.php
	 * @param int $maxlifetime <p>
	 * Sessions that have not updated for
	 * the last maxlifetime seconds will be removed.
	 * </p>
	 * @return bool <p>
	 * The return value (usually TRUE on success, FALSE on failure).
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function gc($maxlifetime);

	/**
	 * Initialize session
	 * @link http://php.net/manual/en/sessionhandlerinterface.open.php
	 * @param string $save_path The path where to store/retrieve the session.
	 * @param string $name The session name.
	 * @return bool <p>
	 * The return value (usually TRUE on success, FALSE on failure).
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function open($save_path, $name);


	/**
	 * Read session data
	 * @link http://php.net/manual/en/sessionhandlerinterface.read.php
	 * @param string $session_id The session id to read data for.
	 * @return string <p>
	 * Returns an encoded string of the read data.
	 * If nothing was read, it must return an empty string.
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function read($session_id);

	/**
	 * Write session data
	 * @link http://php.net/manual/en/sessionhandlerinterface.write.php
	 * @param string $session_id The session id.
	 * @param string $session_data <p>
	 * The encoded session data. This data is the
	 * result of the PHP internally encoding
	 * the $_SESSION superglobal to a serialized
	 * string and passing it as this parameter.
	 * Please note sessions use an alternative serialization method.
	 * </p>
	 * @return bool <p>
	 * The return value (usually TRUE on success, FALSE on failure).
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function write($session_id, $session_data);
}

/**
 * <b>SessionUpdateTimestampHandlerInterface</b> is an interface which
 * defines a prototype for updating the life time of an existing session.
 * In order to use the lazy_write option must be enabled and a custom session
 * handler must implement this interface.
 * @since 7.0.0
 */
interface SessionUpdateTimestampHandlerInterface {

    /**
     * Validate session id
     * @param string $session_id The session id
     * @return bool <p>
     * Note this value is returned internally to PHP for processing.
     * </p>
     */
    public function validateId($session_id);

    /**
     * Update timestamp of a session
     * @param string $session_id The session id
     * @param string $session_data <p>
     * The encoded session data. This data is the
     * result of the PHP internally encoding
     * the $_SESSION superglobal to a serialized
     * string and passing it as this parameter.
     * Please note sessions use an alternative serialization method.
     * </p>
     * @return bool
     */
    public function updateTimestamp($session_id, $session_data);

}

/**
 * <b>SessionHandler</b> a special class that can
 * be used to expose the current internal PHP session
 * save handler by inheritance. There are six methods
 * which wrap the six internal session save handler
 * callbacks (open, close, read, write, destroy and gc).
 * By default, this class will wrap whatever internal
 * save handler is set as as defined by the
 * session.save_handler configuration directive which is usually
 * files by default. Other internal session save handlers are provided by
 * PHP extensions such as SQLite (as sqlite),
 * Memcache (as memcache), and Memcached (as memcached).
 * @link http://php.net/manual/en/class.reflectionzendextension.php
 * @since 5.4.0
 */
class SessionHandler implements SessionHandlerInterface, SessionUpdateTimestampHandlerInterface {

	/**
	 * Close the session
	 * @link http://php.net/manual/en/sessionhandler.close.php
	 * @return bool <p>
	 * The return value (usually TRUE on success, FALSE on failure).
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function close() { }

    /**
     * Return a new session ID
     * @link http://php.net/manual/en/sessionhandler.create-sid.php
     * @return string <p>A session ID valid for the default session handler.</p>
     * @since 5.5.1
     */
	public function create_sid() {}

	/**
	 * Destroy a session
	 * @link http://php.net/manual/en/sessionhandler.destroy.php
	 * @param string $session_id The session ID being destroyed.
	 * @return bool <p>
	 * The return value (usually TRUE on success, FALSE on failure).
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function destroy($session_id) { }

	/**
	 * Cleanup old sessions
	 * @link http://php.net/manual/en/sessionhandler.gc.php
	 * @param int $maxlifetime <p>
	 * Sessions that have not updated for
	 * the last maxlifetime seconds will be removed.
	 * </p>
	 * @return bool <p>
	 * The return value (usually TRUE on success, FALSE on failure).
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function gc($maxlifetime) { }

	/**
	 * Initialize session
	 * @link http://php.net/manual/en/sessionhandler.open.php
	 * @param string $save_path The path where to store/retrieve the session.
	 * @param string $session_name The session name.
	 * @return bool <p>
	 * The return value (usually TRUE on success, FALSE on failure).
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function open($save_path, $session_name) { }


	/**
	 * Read session data
	 * @link http://php.net/manual/en/sessionhandler.read.php
	 * @param string $session_id The session id to read data for.
	 * @return string <p>
	 * Returns an encoded string of the read data.
	 * If nothing was read, it must return an empty string.
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function read($session_id) { }

	/**
	 * Write session data
	 * @link http://php.net/manual/en/sessionhandler.write.php
	 * @param string $session_id The session id.
	 * @param string $session_data <p>
	 * The encoded session data. This data is the
	 * result of the PHP internally encoding
	 * the $_SESSION superglobal to a serialized
	 * string and passing it as this parameter.
	 * Please note sessions use an alternative serialization method.
	 * </p>
	 * @return bool <p>
	 * The return value (usually TRUE on success, FALSE on failure).
	 * Note this value is returned internally to PHP for processing.
	 * </p>
	 * @since 5.4.0
	 */
	public function write($session_id, $session_data) { }

    /**
     * Validate session id
     * @param string $session_id The session id
     * @return bool <p>
     * Note this value is returned internally to PHP for processing.
     * </p>
     */
    public function validateId($session_id) { }

    /**
     * Update timestamp of a session
     * @param string $session_id The session id
     * @param string $session_data <p>
     * The encoded session data. This data is the
     * result of the PHP internally encoding
     * the $_SESSION superglobal to a serialized
     * string and passing it as this parameter.
     * Please note sessions use an alternative serialization method.
     * </p>
     * @return bool
     */
    public function updateTimestamp($session_id, $session_data) { }

}

This is the interface that defines the sessionID processing mechanism. Just implement it
My implementation is as follows:

<?php
/**
 * Created by PhpStorm.
 * User: php
 * Date: 2021/5/14
 * Time: 7:38
 */

namespace think\session\driver;
use SessionHandlerInterface; //PHP implements the reserved session interface
use think\Db;
use think\Exception;
use think\facade\Config;

class Mysql implements SessionHandlerInterface
{
    protected $handler = null;
    protected $config = [
        'hostname' => '127.0.0.1',// server address
        'database' => 'test',// Database name
        'username' => 'root',// user name
        'password' => 'root',// password
        'hostport' => '3306',// port
        'charset' => 'utf8mb4', // The database code is utf8 by default
        'expire' => 3600, // session validity
        'session_name' => '', // sessionkey prefix
        'session_table' => 'think_session', // Name of data table stored in session
    ];
    protected $table_name = null;

    public function __construct($config = [])
    {
        //Get the database configuration and update the database configuration
        $this->config = array_merge($this->config, Config::get('database.'), $config);
        $this->table_name = empty($this->config['session_table']) ? $this->config['prefix'] . '_session' : $this->config['session_table'];
    }

    /**
     * Open Session
     * @access public
     * @param string $savePath
     * @param mixed $sessName
     */
    public function open($savePath, $sessName)
    {
        if (empty($this->config['hostname'])) throw new Exception('database config error');
        $this->handler = Db::connect($this->config);
        return true;
    }

    /**
     * Close Session
     * @access public
     */
    public function close()
    {
        $this->gc(ini_get('session.gc_maxlifetime'));
        $this->handler = null;
        return true;
    }

    /**
     * Read Session
     * @access public
     * @param string $sessID
     */
    public function read($sessID)
    {
        return (string)Db::table($this->table_name)->where([['session_id', '=', $this->config['session_name'] . $sessID], ['session_expire', '>=', time()]])->value('session_data');
    }

    /**
     * Write Session
     * @access public
     * @param string $sessID
     * @param string $sessData
     * @return bool
     */
    public function write($sessID, $sessData)
    {
        //Get the hidden parameters passed in through the session for other operations
        $unserialize_session_data = unserialize(explode('|', $sessData)[1]);

        //Build data stored in the database
        $params = [
            'session_id' => $this->config['session_name'] . $sessID,
            'session_expire' => $this->config['expire'] + time(),
            'session_data' => $sessData,
            'create_time' => date('Y-m-d H:i:s',time()),
            'ip' => get_ip(),

        ];

        $sql = "REPLACE INTO {$this->table_name} (session_id,session_expire,session_data,create_time,ip) VALUES (:session_id, :session_expire, :session_data,:create_time,:ip)";
        $result = $this->handler->execute($sql, $params);
        return $result ? true : false;
    }

    /**
     * Delete Session
     * @access public
     * @param string $sessID
     * @return bool
     */
    public function destroy($sessID)
    {
        $result = $this->handler->execute("DELETE FROM {$this->table_name} WHERE session_id = :session_id", ['session_id' => $this->config['session_name'] . $sessID]);
        return $result ? true : false;
    }

    /**
     * Session garbage collection
     * @access public
     * @param string $sessMaxLifeTime
     * @return true
     */
    public function gc($sessMaxLifeTime)
    {
        $result = $this->handler->execute("DELETE FROM {$this->table_name} WHERE session_expire < :session_expire", ['session_expire' => time()]);
        return $result ? true : false;
    }
}

Here, I save the default sessionID of php to the local mysql test database, and the saved table name is think_session, the table structure is as follows:

CREATE TABLE `think_session` (
  `session_id` varchar(255) CHARACTER SET utf8 NOT NULL,
  `session_expire` int(11) NOT NULL DEFAULT '0' COMMENT 'SESSION Expiration time of',
  `session_data` blob COMMENT 'SESSION Data content of',
  `create_time` datetime NOT NULL,
  `ip` varchar(255) CHARACTER SET utf8 NOT NULL DEFAULT '',
  UNIQUE KEY `session_id` (`session_id`),
  KEY `NewIndex1` (`session_expire`),
  KEY `Uname` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

When the first three steps are done, you can see the effect


The cookie s between different domains of the two sites are saved, and the sessionID between the two domains is the same


In this way, you can freely jump between the two domains without login. The precondition is that the cookie cannot be cleared and the browser cannot be closed!

Topics: PHP