PHP para iniciantes. A sessão

ElePHPant. PHP para iniciantes. Sessão

Tenham um bom dia. Aqui está o primeiro artigo da série PHP para desenvolvedores iniciantes. Esta será uma série incomum de artigos, não haverá echo "Hello World" , haverá hardcore na vida dos programadores PHP com uma pequena mistura de "lição de casa" para consolidar o material.

Começarei com as sessões - este é um dos componentes mais importantes com os quais você deve trabalhar. Não entendendo os princípios de seu trabalho - faça negócios. Então, para evitar problemas, tentarei falar sobre todas as nuances possíveis.

Mas para iniciantes, para entender por que precisamos de uma sessão, voltamos para as origens - para o protocolo HTTP.

Protocolo HTTP


O protocolo HTTP é o HyperText Transfer Protocol - o "Hypertext Transfer Protocol" - ou seja, de fato - um protocolo de texto, e entender que não é difícil.
Inicialmente, entendeu-se que, de acordo com este protocolo, somente o HTML será transmitido, o endereço e o nome, mas agora eles apenas não enviarão e = ^. ^ = E (• _ ㅅ _ •)

Para não ficar parado, deixe-me dar um exemplo de comunicação através do protocolo HTTP.
Aqui está um exemplo da solicitação de como o navegador a envia quando você solicita a página http://example.com :

 GET / HTTP/1.1 Host: example.com Accept: text/html < > 

E aqui está um exemplo de resposta:

 HTTP/1.1 200 OK Content-Length: 1983 Content-Type: text/html; charset=utf-8 <html> <head>...</head> <body>...</body> </html> 

Estes são exemplos muito simplificados, mas mesmo aqui você pode ver em que consiste a solicitação e resposta HTTP:

  1. linha de partida - para uma solicitação contém o método e o caminho da página solicitada, para uma resposta - a versão do protocolo e o código de resposta
  2. cabeçalhos - têm um formato de valor-chave separado por dois pontos, cada novo cabeçalho é gravado a partir de uma nova linha
  3. corpo da mensagem - diretamente HTML ou dados são separados dos cabeçalhos por duas quebras de linha, podem estar ausentes, como na solicitação acima

Então, descobrimos o protocolo - é simples, lidera sua história desde 1992, então você não o nomeará ideal, mas o que é - envie uma solicitação - obtenha uma resposta e é isso, o servidor e o cliente não estão mais conectados. Mas esse cenário não é o único possível, podemos ter autorização, o servidor deve entender de alguma forma que essa solicitação veio de um usuário específico, ou seja, o cliente e o servidor devem se comunicar dentro de uma determinada sessão. E sim, o seguinte mecanismo foi inventado para isso:

  1. Quando um usuário autoriza, o servidor gera e se lembra de uma chave exclusiva - o identificador da sessão e a reporta ao navegador
  2. O navegador salva essa chave e, a cada solicitação subsequente, envia

Para implementar esse mecanismo, foram criados cookies (cookies, cookies) - arquivos de texto simples no seu computador, por arquivo para cada domínio (embora alguns navegadores sejam mais avançados e usem um banco de dados para armazenar SQLite), enquanto o navegador impõe um limite no número de registros e o tamanho dos dados armazenados (para a maioria dos navegadores são 4096 bytes, consulte RFC 2109 de 1997)
I.e. se você roubar um cookie do seu navegador, pode acessar a sua página do facebook em seu nome? Não se assuste, isso não pode ser feito, pelo menos com o facebook, e então mostrarei uma das maneiras possíveis de se proteger contra esse tipo de ataque aos seus usuários.

Agora vamos ver como nossas respostas a pedidos mudam, esteja lá a autorização:

Pedido
 POST /login/ HTTP/1.1 Host: example.com Accept: text/html login=Username&password=Userpass 


Nosso método mudou para POST, e o login e a senha são transmitidos no corpo da solicitação. Se você usar o método GET, a string de consulta conterá um nome de usuário e senha, o que não é muito correto do ponto de vista ideológico e tem vários efeitos colaterais na forma de log (por exemplo, no mesmo access.log ) e no cache de senhas de forma clara.

Resposta
 HTTP/1.1 200 OK Content-Type: text/html; charset=utf-8 Set-Cookie: KEY=VerySecretUniqueKey <html> <head>...</head> <body>...</body> </html> 

A resposta do servidor conterá o cabeçalho Set-Cookie: KEY=VerySecretUniqueKey , que forçará o navegador a salvar esses dados em cookies e, na próxima vez em que acessar o servidor, ele será enviado e reconhecido pelo servidor:

Pedido
 GET / HTTP/1.1 Host: example.com Accept: text/html Cookie: KEY=VerySecretUniqueKey < > 

Como você pode ver, os cabeçalhos enviados pelo navegador (Request Headers) e pelo servidor (Response Headers) são diferentes, embora sejam comuns para solicitações e respostas (General Headers)

O servidor reconheceu nosso usuário pelo cookie enviado e fornecerá ainda mais acesso a informações pessoais. Então, bem, com sessões e HTTP resolvidos, agora você pode retornar ao PHP e seus recursos.

PHP e sessão


Espero que você já tenha o PHP instalado no seu computador, como Além disso, darei exemplos, e eles precisarão ser executados

A linguagem PHP foi criada para corresponder ao protocolo HTTP - ou seja, Sua principal tarefa é dar uma resposta à solicitação HTTP e "morrer" liberando memória e recursos. Portanto, o mecanismo da sessão não funciona no PHP no modo automático, mas no modo manual, e você precisa saber o que chamar e em que ordem.
Aqui você tem um artigo sobre o tópico PHP deve morrer , ou aqui está em russo , mas é melhor colocá-lo nos favoritos "para mais tarde".

Primeiro, você precisa "iniciar" a sessão - para isso, usamos a função session_start () , crie um arquivo session.start.php com o seguinte conteúdo:

 <?php session_start(); 

Execute o servidor web PHP embutido na pasta com o seu script:

 php -S 127.0.0.1:8080 

Inicie o navegador e abra as Ferramentas do desenvolvedor (ou o que for ) e vá para a página http://127.0.0.1:8080/session.start.php - você deve ver apenas uma página em branco, mas não se apresse para fechar - veja para os cabeçalhos que o servidor nos enviou:

Cookie

Haverá muitas coisas. Estamos interessados ​​apenas nesta linha na resposta do servidor (cookies limpos, se não houver essa linha e atualize a página):

 Set-Cookie: PHPSESSID=dap83arr6r3b56e0q7t5i0qf91; path=/ 

Ao ver isso, o navegador salvará um cookie chamado `PHPSESSID`:

Cookie de sessão do navegador

PHPSESSID - o nome da sessão padrão, é ajustado na configuração do php.ini com a diretiva session.name ; se necessário, o nome pode ser alterado no próprio arquivo de configuração ou usando a função session_name ()

E agora - atualizamos a página e vemos que o navegador envia esse cookie para o servidor, você pode tentar atualizar a página algumas vezes, o resultado será idêntico:

Pedido do navegador com cookie

O total que temos - a teoria coincidiu com a prática, e isso é ótimo.

O próximo passo é salvar um valor arbitrário na sessão, para isso, a variável super global $_SESSION usada no PHP, economizaremos o tempo atual - para isso, chame a função date () :

 session_start(); $_SESSION['time'] = date("H:i:s"); echo $_SESSION['time']; 

Atualizamos a página e vemos a hora do servidor, atualizamos novamente - e a hora foi atualizada. Agora, verifique se o tempo definido não muda a cada atualização da página:

 session_start(); if (!isset($_SESSION['time'])) { $_SESSION['time'] = date("H:i:s"); } echo $_SESSION['time']; 

Nós atualizamos - o tempo não muda, o que é necessário. Mas, ao mesmo tempo, lembramos que o PHP está morrendo, o que significa que ele armazena esta sessão em algum lugar e encontraremos esse lugar ...

Tudo segredo fica claro


Por padrão, o PHP armazena a sessão em arquivos - a diretiva session.save_handler é responsável por isso, procure o caminho no qual os arquivos são salvos na diretiva session.save_path ou use a função session_save_path () para obter o caminho necessário.
Na sua configuração, o caminho para os arquivos pode não ser especificado, então os arquivos da sessão serão armazenados em arquivos temporários do seu sistema - chame a função sys_get_temp_dir () e descubra onde está esse local oculto.

Então, seguimos esse caminho e localizamos seu arquivo de sessão (eu tenho esse arquivo sess_dap83arr6r3b56e0q7t5i0qf91 ), abra-o em um editor de texto:

 time|s:8:"16:19:51"; 

Como você pode ver, aqui é a nossa hora, este é o formato complicado em que nossa sessão é armazenada, mas podemos fazer alterações, alterar a hora ou simplesmente inserir qualquer linha, por que não:

 time|s:13:"\m/ (@.@) \m/"; 

Para converter essa string em uma matriz, você precisa usar a função session_decode () , para a conversão reversa - session_encode () - isso é chamado de serialização, apenas no PHP para sessões - é o seu próprio - especial, embora você possa usar a serialização padrão do PHP - escreva na diretiva de configuração da sessão O valor .serialize_handler é php_serialize e você ficará feliz, e $_SESSION pode ser usado sem restrições - agora você pode usar números e caracteres especiais como índice | e ! em nome (para todos os mais de 10 anos de trabalho, nunca precisei :)

Tarefa
Escreva sua função, semelhante em função a session_decode() , aqui você tem um conjunto de dados de teste para a sessão (não é necessário resolver o conhecimento de expressões regulares), pegue o texto para conversão do arquivo da sua sessão atual:

 $_SESSION['integer var'] = 123; $_SESSION['float var'] = 1.23; $_SESSION['octal var'] = 0x123; $_SESSION['string var'] = "Hello world"; $_SESSION['array var'] = array('one', 'two', [1,2,3]); $object = new stdClass(); $object->foo = 'bar'; $object->arr = array('hello', 'world'); $_SESSION['object var'] = $object; $_SESSION['integer again'] = 42; 


Então, o que ainda não tentamos? É isso mesmo - para roubar cookies, vamos lançar outro navegador e adicionar os mesmos cookies. Para isso, escrevi um javascript simples para você, copie-o no console do navegador e execute-o, lembre-se de alterar o identificador da sessão para o seu:

 javascript:(function(){document.cookie='PHPSESSID=dap83arr6r3b56e0q7t5i0qf91;path=/;';window.location.reload();})() 

Agora, os dois navegadores estão olhando para a mesma sessão. Mencionei acima que falarei sobre métodos de proteção, considerarei a maneira mais simples - vincularemos a sessão ao navegador, mais precisamente, a aparência do navegador ao servidor - lembraremos do User-Agent e verificaremos sempre:

 session_start(); if (!isset($_SESSION['time'])) { $_SESSION['ua'] = $_SERVER['HTTP_USER_AGENT']; $_SESSION['time'] = date("H:i:s"); } if ($_SESSION['ua'] != $_SERVER['HTTP_USER_AGENT']) { die('Wrong browser'); } echo $_SESSION['time']; 

É mais difícil falsificar, mas ainda é possível, adicionar as $_SERVER['HTTP_X_FORWARDED_FOR'] salvar e verificar $_SERVER['REMOTE_ADDR'] e $_SERVER['HTTP_X_FORWARDED_FOR'] , e isso parecerá mais ou menos com proteção contra invasores que invadem nossos cookies.

A palavra-chave no parágrafo anterior parece que os cookies estão rodando o protocolo HTTPS em projetos reais há muito tempo, para que ninguém possa roubá-los sem acesso físico ao seu computador ou smartphone


Vale mencionar a diretiva session.cookie-activationponly , graças a ela o cookie da sessão ficará inacessível a partir do JavaScript. Além disso, se você olhar o manual da função setcookie () , notará que o último parâmetro também é responsável pelo HttpOnly. Lembre-se disso - essa configuração permite lidar efetivamente com ataques XSS em quase todos os navegadores .

Tarefa
Adicione uma verificação ao IP do usuário no código; se a verificação falhar, exclua a sessão comprometida.

Passo a passo


E agora vou explicar em etapas o algoritmo como uma sessão funciona em PHP, usando o seguinte código como exemplo (configurações padrão):

 session_start(); $_SESSION['id'] = 42; 

  1. depois de chamar session_start() PHP procura no identificador de sessão o cookie pelo nome especificado em session.name - este é PHPSESSID
  2. se não houver identificador, ele será criado (consulte session_id () ) e criará um arquivo de sessão vazio no caminho session.save_path com o nome sess_{session_id()} , os cabeçalhos serão adicionados à resposta do servidor para definir o cookie {session_name()}={session_id()}
  3. se o identificador estiver presente, procure o arquivo da sessão na pasta session.save_path :
    • não o encontramos - criamos um arquivo vazio com o nome sess_{$_COOKIE[session_name()]} (o identificador pode conter apenas caracteres dos intervalos az , AZ , 0-9 , vírgula e sinal de menos)
    • encontre, leia o arquivo e descompacte os dados (consulte session_decode () ) na variável super global $_SESSION (o arquivo está bloqueado para leitura / gravação)
  4. quando o script terminar seu trabalho, todos os dados de $_SESSION serão compactados usando session_encode() em um arquivo no caminho session.save_path chamado sess_{session_id()} (o bloqueio é liberado)

Tarefa
Defina um valor arbitrário de cookie no seu navegador com o nome PHPSESSID , 1234567890 , atualize a página, verifique se você criou um novo arquivo sess_1234567890

Existe vida sem biscoitos?


O PHP pode funcionar com a sessão mesmo se os cookies estiverem desativados no navegador, mas todos os URLs no site conterão um parâmetro com o identificador da sua sessão e sim - você ainda precisa configurar isso, mas precisa? Não precisava usá-lo, mas se realmente quiser, direi onde cavar:


E se você precisar armazenar uma sessão em um banco de dados?


Para armazenar uma sessão no banco de dados, você precisará alterar o armazenamento da sessão e informar ao PHP como usá- la.Para esse efeito, a interface SessionHandlerInterface e a função session_set_save_handler foram criadas.
Separadamente, observo que você não precisa escrever seus próprios manipuladores de sessão para redis e memcache - quando você instala essas extensões, os manipuladores correspondentes também os acompanham, portanto o RTFM é tudo. Bem, sim, o manipulador deve ser especificado antes de chamar session_start() ;)

Tarefa
Implemente SessionHandlerInterface para armazenar a sessão no MySQL, verifique se funciona.
Essa é uma tarefa de asterisco para aqueles que já se familiarizaram com os bancos de dados.


Quando a sessão morre?


A diretiva session.gc_maxlifetime é responsável pelo tempo de vida de uma sessão. Por padrão, essa diretiva é igual a 1440 segundos (24 minutos); deve-se entender que, se uma sessão não tiver sido acessada por um tempo especificado, a sessão será considerada "podre" e aguardará sua vez de excluir.

Outra pergunta é interessante: você pode pedir a desenvolvedores maduros - quando o PHP exclui arquivos de sessões expiradas? A resposta está no guia oficial, mas não explicitamente - lembre-se:

A coleta de lixo pode ser iniciada quando a função session_start() é chamada, a probabilidade de iniciar depende de duas diretivas session.gc_probability e session.gc_divisor , a primeira atua como um dividendo, a segunda atua como um divisor e, por padrão, esses valores são 1 e 100, etc. e a probabilidade de o coletor ser iniciado e os arquivos da sessão serem excluídos é de aproximadamente 1%.

Tarefa
Altere o valor da diretiva session.gc_divisor para que o coletor de lixo seja iniciado sempre, verifique se isso acontece.


O erro mais trivial


Um erro com mais de meio milhão de resultados nos resultados do Google:

Não é possível enviar o cookie da sessão - os cabeçalhos já enviados por
Não é possível enviar o limitador de cache da sessão - os cabeçalhos já enviados

Para obter um, crie um arquivo session.error.php com o seguinte conteúdo:

 echo str_pad(' ', ini_get('output_buffering')); session_start(); 

Na segunda linha, uma estranha "mágica" é um foco com um buffer de saída, falarei sobre isso em um dos seguintes artigos, até agora considero que seja apenas uma sequência com um comprimento de 4096 caracteres, nesse caso, são todos os espaços

Comece excluindo o cookie antecipadamente e você receberá os erros acima, embora o texto do erro seja diferente, mas a essência seja a mesma: o trem partiu - o servidor já enviou o conteúdo da página para o navegador e é tarde para enviar os cabeçalhos, isso não funcionará nos cookies e o identificador de sessão estimado não aparecerá nos cookies. Se você encontrar esse erro - procure um local em que o texto seja exibido antes do tempo, pode haver um espaço antes dos caracteres <?php ou after ?> Em um dos arquivos conectados e, se for um espaço, pode haver algum segmento que não pode ser impresso como BOM , tenha cuidado, e esta infecção não afetará você (afinal ... risadas homéricas).

Tarefa
Para testar esse conhecimento, quero que você implemente seu próprio mecanismo de sessão e faça o código acima funcionar:

 require_once 'include/sess.php'; sess_start(); if (isset($_SESS["id"])) { echo $_SESS["id"]; } else { $_SESS["id"] = 42; } 

Para implementar seu plano, você precisará da função register_shutdown_function ()



Bloquear


Outro erro comum entre iniciantes é uma tentativa de ler o arquivo da sessão enquanto ele está bloqueado por outro script. Na verdade, isso não é um erro, é um mal-entendido do princípio do bloqueio :)

Mas vamos dar os passos novamente:

  1. session_start() não apenas cria / lê um arquivo, mas também o bloqueia para que ninguém possa fazer alterações no momento em que o script é executado ou ler dados não consistentes do arquivo da sessão
  2. o bloqueio é liberado no final do script


"Furar" nesse erro é muito fácil, crie dois arquivos:

 // start.php session_start(); echo "OK"; 


 // lock.php session_start(); sleep(10); echo "OK"; 


Agora, se você abrir a página lock.php no navegador e abrir start.php em uma nova guia, verá que a segunda página será aberta somente após a execução do primeiro script, que bloqueia o arquivo da sessão por 10 segundos.

Existem algumas opções para evitar esse fenômeno - “desajeitado” e “pensativo”.

"Machado"
Use um manipulador de sessão personalizado no qual "esquecer" para implementar o bloqueio :)
Uma opção um pouco melhor é pegar uma pronta e desabilitar o bloqueio (por exemplo, o memcached possui essa opção - memcached.sess_locking ) O_o
Passe horas depurando o código em busca de um erro pop-up raro ...

"Pensativo"
Onde é a melhor maneira - monitorar você mesmo a trava de sessão e removê-la quando não for necessária:

- Se tiver certeza de que não precisa fazer alterações nos dados da sessão, use a opção read_and_close ao iniciar a sessão:

 session_start([ 'read_and_close' => true ]); 


Assim, o bloqueio será liberado imediatamente após a leitura dos dados da sessão.

- Se você ainda precisar fazer alterações na sessão, depois de fazê-las fechar a sessão da gravação:

 session_start(); // some changes session_write_close(); 


Tarefa
A listagem dos dois arquivos lock.php e lock.php foi um pouco maior: crie mais read-close.php e write-close.php , nos quais você controlará o bloqueio das maneiras listadas. Verifique como o bloqueio funciona (ou não).


Em conclusão


Neste artigo, você recebeu sete tarefas, enquanto elas se referem não apenas ao trabalho com sessões , mas também apresentam o MySQL e funções de string . Para a assimilação deste material - você não precisa de um artigo separado, basta o manual nos links fornecidos - ninguém o lerá para você. Vá em frente!

PS Se você aprendeu algo novo com o artigo - agradeça ao autor - compartilhe o artigo nas redes sociais;)
PPS Sim, este é um artigo de postagem cruzada do meu blog , mas ainda é relevante :)

Uma série de artigos "PHP para iniciantes":

Source: https://habr.com/ru/post/pt437972/


All Articles