This article shares the PHP session in the use of the lock and concurrency problems, associated with the phenomenon of request blocking, session data loss, session data can not read.
I can't log in.
One day, I am ready to log in to a background system, to solve a bug, in the account password verification code are accurately entered the case, I do not log on, after several experiments found that there are two main error messages:
- CSRF validation failed
- Authenticode error "I swear by the code god, I entered the verification code I saw in half a corner, and in the same order, without extra characters."
Our system
Our system was developed based on Phalcon 2.0.8, and as you can see, we have added a domain to prevent CSRF attacks in form fields. The authentication code is also enabled.
<input type= "hidden"
name= "{{Security.gettokenkey ()}}"
value= "{{Security.gettoken ()}}"/>
I looked at the two components first and found that they were all saving the data in session:
# Phalcon/security.zep
# Security::gettoken () let session
= <SessionInterface> dependencyinjector-> Getshared ("session");
Session->set (This->_tokenvaluesessionid, token);
$this->session->set (' admin_get_captcha_action ', $captcha);
Then I looked at the implementation of our session and found that the data was stored in Redis.
Look for it.
What problem caused me to log on? Since there is a problem with data validation, start with the data, I log in our test environment Redis machine, execute REDIS-CLI Monitor, and then walk through the login process, found the output as follows (meaning):
- Get SessionId
- Get SessionId
- Setex SessionId 3600 Csrf=xxxx
- Setex SessionId 3600 CAPTCHA=ABCD
We can see:
1, there are two requests, one is the form load, one is to generate the verification code.
2, there is a "concurrent" situation, the two requests should be the form of loading rendering after the request verification code, that is, the session sequence should be get->set->get->set, it looks like how the concurrent request.
3, the back of the Setex no CSRF content, that is, cover up the previous data
The whole world is a bad thing, but it's a little bit clear what the problem is. What's the problem, it's a long story, to start with the access to the PHP session data.
Access to the session data of PHP
The session data is encoded into a string stored in the memory "file, DB, Redis, memcache, etc.", when we use the session, when the memory to fetch data? When did you write data to the storage?
The answer to this question may not be the same as some friends think, a request, PHP will only read a memory, in the Session_Start, and then will only write a memory, at the end of the request, or call Session_write_close, Brush the data back to memory and close session.
So here's the question:
1, if a session, at the same time, there are two read and write session requests, no guarantee to get 1-write 1-Get 2-write 2, without the CAS version management mechanism, these concurrent requests will not read each other's writes, and the last write will overwrite the session written by the previous request.
2, if the request is serial, like the login page form and verification code, it is possible that the previous request has been output, but the session has not been written, the following request has been initiated.
Lock and not lock
The concurrency that resolves such resources is typically handled through locks or versioning. But version management I don't see a good way. Just talk about the lock.
In fact, the lock is not suitable, there are drawbacks.
PHP session, the default is stored in the file, in the opening sessions, the file will be exclusive lock, so that other requests can not get the lock, can only wait until the front of the lock solution.
This guarantees the order of read-write, read-write.
Other storage, such as MySQL, can be used for row locking with Select for update. Redis can be achieved by using a self-adding key to return 1 of the acquisition to the lock.
This implementation, for the data flow is ideal, but for the current page of a large number of applications Ajax, all Request queuing processing, will greatly increase the time to show the page, even the request timeout and so on can not be used.
No solution to the problem
It is not recommended to use the session too much, and the problem caused by one read once write mechanism can cause the pit to exist.
Call Session_write_close before the template is rendered, or before the output is requested
# write back the session immediately, avoid the session cover
$eventManager = $this->view->geteventsmanager ();
if (! $eventManager) {
$eventManager = new Manager ();
$this->view->seteventsmanager ($eventManager);
}
$eventManager->attach ("View:afterrender", function () {
session_write_close ();
});
return $this->view;
if ($login) {
# immediately writes back the session, avoids the session to read
$eventManager = $this->dispatcher->geteventsmanager ();
if (! $eventManager) {
$eventManager = new Manager ();
$this->dispatcher->seteventsmanager ($eventManager);
}
$eventManager->attach (' Dispatch:afterdispatchloop ', function () {
session_write_close ();
});
return $this->response->setheader (' Location ', '/');
}
The above is about the PHP session lock and concurrency, I hope to help you learn.