How To Storage Session By Redis In Symfony2

Server Environment

1
2
3
4
5
6
7
8
9
10
11
12
$ cat /etc/issue
Ubuntu 16.04.2 LTS \n \l

$ redis-server --version
Redis server v=3.0.6 sha=00000000:0 malloc=jemalloc-3.6.0 bits=64

$ cat composer.json
"require": {
"php": ">=5.5.9",
"symfony/symfony": "2.8.*",
...
},

Using Redis instead of the default PHP file storage session

For the first preparation, we need to have a running Redis service.

Then, installing php redis extension. This tutorial bases on the Ubuntu:16.04 system, so i will use command apt install to do this.

1
$ sudo apt-get install -y php-redis

Next, find the config.yml in your Symfony project and register the session handler which named session_handler_redis.

1
$ vim app/config/config.yml
1
2
3
4
5
6
framework:
...
session:
# handler_id set to null will use default session handler from php.ini
# handler_id: ~
handler_id: session_handler_redis

This configuration uses a new session handler to figure out the session service.

Now we can declare a Symfony Service in services.yml.

1
$ vim app/config/services.yml
1
2
3
4
5
6

services:
...
session_handler_redis:
class: AppBundle\SessionHandler\NativeRedisSessionHandler
arguments: ["%session_handler_redis_scheme%","%session_handler_redis_host%","%session_handler_redis_port%","%session_handler_redis_auth%","%session_guest_life_seconds%"]

How can we manage the session in these new handler AppBundle\SessionHandler\NativeRedisSessionHandler ?
There are a lot of tools on github that can do this, but I found that there is already such code in the Symfony source to help us do this.

So declare a new file named NativeRedisSessionHandler in your project:

1
$ vim src/AppBundle/SessionHandler/NativeRedisSessionHandler.php

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<?php
/**
* Created by PhpStorm.
* User: liang
* Date: 18年01月17日
* Time: 下午12:07
*/

namespace AppBundle\SessionHandler;

use \Symfony\Component\HttpFoundation\Session\Storage\Handler\NativeSessionHandler;

/**
* NativeRedisSessionStorage.
*
* Driver for the redis session <div id="R77EHMs" style="position: absolute; top: -1183px; left: -1358px; width: 243px;"></div> save hadlers provided by the redis PHP extension.
*
* @see https://github.com/nicolasff/phpredis
*
* @author Andrej Hudec &lt;pulzarraider@gmail.com&gt;
* @author Piotr Pelczar &lt;me@athlan.pl&gt;
*/
class NativeRedisSessionHandler extends NativeSessionHandler
{
/**
* Constructor.
*
* @param string $host Path of redis server.
* @param string $scheme
* @param int $port
* @param null $auth
* @param int $saveTime
*/
public function __construct($scheme, $host, $port = 6379, $auth = null, $saveTime = 1440)
{
if (!extension_loaded('redis')) {
throw new \RuntimeException('PHP does not have "redis" session module registered');
}

if ($host) {

$savePath = "{$scheme}://{$host}:{$port}";
if ($auth){
$savePath = "{$savePath}?auth={$auth}";
}

ini_set('session.save_handler', 'redis');
ini_set('session.save_path', $savePath);
ini_set('session.gc_maxlifetime', $saveTime);
}
}
}

That’s all! How simple it is!

Restrict account logins by controlling sessions

In some business contexts, user accounts should not be allowed to log in to multiple devices at the same time.
When an account is logged in to a new device, the old account session should be cleaned up.
We can maintain a new key map to control the session. For example, uniqueAccount - sessionId.

In order to control redis more conveniently, we need to install the package about redis. Here we use the predis.

1
2
3
4
5
"require": {
...
"snc/redis-bundle": "2.1",
"predis/predis": "^1.0"
}

Then we add a new record in redis which key is user unique account and value is the sessionId after login success.
However, it should be noted that you need to first find and delete all corresponding sessionIds through the account, and then generate a new session record.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* @Route("/login/success",name="login_success")
*/
public function loginSuccessAction()
{
...
$CurrentUser = $this->getCurrentUser();
$Client = new \Predis\Client([
'scheme' => $this->getParameter('session_handler_redis_scheme'),
'host' => $this->getParameter('session_handler_redis_host'),
'port' => $this->getParameter('session_handler_redis_port'),
'password' => $this->getParameter('session_handler_redis_auth'),
]);
$sessionId = $Client->get($CurrentUser->account());
if ($sessionId) {
$Client->del([$CurrentUser->account(), $sessionId]);
}

$session = $this->get('session');
$session->start();
$sessionId = "PHPREDIS_SESSION:" . $session->getId();

$Client->set($CurrentUser->account(), $sessionId);
$Client->expire($sessionId, $this->getParameter('session_user_life_seconds'));
}

Ok, after this step, repeat account login limit is completed too.

Clear Session record if logout

Default logout method in Symfony is declared in the firewall configuration. When we need to customize the behavior of this account logout, we need to declare a new handler.

1
$ vim app/config/security.yml
1
2
3
4
5
6
7
8
9
10

security:
firewalls:
...
main:
...
logout:
# path: /logout
# target: /
handlers: [app.webservice_logout_listener]

First, we should register a new logout service

1
$ vim app/config/services.yml

1
2
3
4
5
6

services:
...
app.webservice_logout_listener:
class: AppBundle\Security\LogoutListener
arguments: ["%session_handler_redis_scheme%","%session_handler_redis_host%","%session_handler_redis_port%","%session_handler_redis_auth%","%session_guest_life_seconds%"]

And this listener will clear all session by current login user’s account.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

<?php
/**
* Created by PhpStorm.
* User: liyuliang
* Date: 17/01/2018
* Time: 3:17 PM
*/

namespace AppBundle\Security;

use AppBundle\Controller\me;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Http\Logout\LogoutHandlerInterface;

class LogoutListener implements LogoutHandlerInterface
{
use me;

private $scheme;
private $host;
private $port;
private $auth;
private $saveTime;

public function __construct($scheme, $host, $port = 6379, $auth = null, $saveTime = 1440)
{
$this->scheme = $scheme;
$this->host = $host;
$this->port = $port;
$this->auth = $auth;
$this->saveTime = $saveTime;
}

/**
* This method is called by the LogoutListener when a user has requested
* to be logged out. Usually, you would unset session variables, or remove
* cookies, etc.
* @param Request $request
* @param Response $response
* @param TokenInterface $token
*/
public function logout(Request $request, Response $response, TokenInterface $token)
{
$user = $token->getUser();

$Client = new \Predis\Client([
'scheme' => 'tcp',
'host' => $this->host,
'port' => $this->port,
'password' => $this->auth,
]);

$sessionId = $Client->get($user->getUsername());
if ($sessionId) {
$Client->del([$user->getUsername(), $sessionId]);
}
}
}

Thank you for reading.
This post is copyrighted by Liyuliang’s Blog.
If reproduced, please indicate the source: Liyuliang’s Blog
This blog uses Creative Commons Attribution-NonCommercial-Share-Sharing 4.0 International License Agreement to license.


Comments

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×