Introdução
Não sei se existe ou haverá uma maneira de identificar exclusivamente máquinas usando um navegador sozinho. Os principais motivos são:
- Você precisará salvar os dados no computador dos usuários. Esses dados podem ser excluídos pelo usuário a qualquer momento. A menos que você tenha uma maneira de recriar esses dados, que são únicos para cada máquina e depois para a sua máquina.
- Validação. Você precisa se proteger contra falsificação, seqüestro de sessão, etc.
Mesmo que haja maneiras de rastrear um computador sem usar cookies, sempre haverá uma maneira de ignorá-lo e de software que fará isso automaticamente. Se você realmente precisar rastrear algo baseado em um computador, precisará criar um aplicativo nativo (Apple Store / Android Store / Windows Program / etc).
Talvez eu não seja capaz de dar uma resposta para a pergunta que você fez, mas posso mostrar como implementar o acompanhamento de sessões. Com o acompanhamento de sessões, você tenta acompanhar a sessão de navegação em vez do computador que visita o site. Ao rastrear a sessão, seu esquema do banco de dados ficará assim:
sesssion:
sessionID: string
// Global session data goes here
computers: [{
BrowserID: string
ComputerID: string
FingerprintID: string
userID: string
authToken: string
ipAddresses: ["203.525....", "203.525...", ...]
// Computer session data goes here
}, ...]
Vantagens do rastreamento baseado em sessão:
- Para usuários conectados, você sempre pode gerar a mesma ID de sessão a partir dos usuários
username
/ password
/ email
.
- Você ainda pode rastrear usuários convidados usando
sessionID
.
- Mesmo que várias pessoas usem o mesmo computador (por exemplo, cybercafé), você poderá rastreá-las separadamente se elas fizerem login.
Desvantagens do rastreamento baseado em sessão:
- As sessões são baseadas no navegador e não no computador. Se um usuário usar 2 navegadores diferentes, isso resultará em 2 sessões diferentes. Se este for um problema, você pode parar de ler aqui.
- As sessões expiram se o usuário não estiver conectado. Se um usuário não estiver conectado, elas usarão uma sessão de convidado que será invalidada se o usuário excluir cookies e cache do navegador.
Implementação
Existem muitas maneiras de implementar isso. Eu não acho que posso cobrir todos eles, vou apenas listar o meu favorito, o que tornaria isso uma resposta opinativa . Tenha isso em mente.
Fundamentos
Acompanharei a sessão usando o que é conhecido como cookie para sempre. São dados que se auto-recriam automaticamente, mesmo que o usuário exclua seus cookies ou atualize seu navegador. No entanto, ele não sobreviverá ao usuário excluir os cookies e o cache de navegação.
Para implementar isso, usarei o mecanismo de cache dos navegadores ( RFC ), a API WebStorage ( MDN ) e os cookies do navegador ( RFC , Google Analytics ).
Legal
Para utilizar os IDs de rastreamento, você precisa adicioná-los à sua política de privacidade e aos seus termos de uso, de preferência sob o subtítulo Rastreamento . Usaremos as seguintes teclas em ambos document.cookie
e window.localStorage
:
- _ga : dados do Google Analytics
- __utma : cookie de rastreamento do Google Analytics
- sid : SessionID
Inclua links para sua política de privacidade e termos de uso em todas as páginas que usam o rastreamento.
Onde guardo meus dados da sessão?
Você pode armazenar os dados da sessão no banco de dados do site ou no computador dos usuários. Como eu normalmente trabalho em sites menores (com mais de 10 mil conexões contínuas) que usam aplicativos de terceiros (Google Analytics / Clicky / etc), é melhor eu armazenar dados no computador do cliente. Isso tem as seguintes vantagens:
- Nenhuma pesquisa de banco de dados / sobrecarga / carga / latência / espaço / etc.
- O usuário pode excluir seus dados sempre que quiser, sem a necessidade de me escrever e-mails irritantes.
e desvantagens:
- Os dados devem ser criptografados / descriptografados e assinados / verificados, o que cria sobrecarga da CPU no cliente (não tão ruim) e no servidor (bah!).
- Os dados são excluídos quando o usuário exclui seus cookies e cache. (é isso que eu realmente quero)
- Os dados não estão disponíveis para análise quando os usuários ficam off-line. (análise apenas para usuários que navegam atualmente)
UUIDS
- BrowserID : id único gerado a partir da seqüência do agente navegadores usuário.
Browser|BrowserVersion|OS|OSVersion|Processor|MozzilaMajorVersion|GeckoMajorVersion
- ComputerID : Gerado a partir dos usuários Endereço IP e chave de sessão HTTPS.
getISP(requestIP)|getHTTPSClientKey()
- FingerPrintID : impressão digital baseada em JavaScript com base em um fingerprint.js modificado .
FingerPrint.get()
- SessionID : chave aleatória gerada quando o primeiro usuário visita o site.
BrowserID|ComputerID|randombytes(256)
- GoogleID : gerado a partir de
__utma
cookies.getCookie(__utma).uniqueid
Mecanismo
No outro dia, eu estava assistindo o show de Wendy Williams com minha namorada e fiquei completamente horrorizada quando a apresentadora aconselhou os espectadores a excluir o histórico do navegador pelo menos uma vez por mês. A exclusão do histórico do navegador normalmente tem os seguintes efeitos:
- Exclui o histórico dos sites visitados.
- Exclui cookies e
window.localStorage
(aww man).
A maioria dos navegadores modernos disponibiliza essa opção facilmente, mas não tem medo de amigos. Pois existe uma solução. O navegador possui um mecanismo de armazenamento em cache para armazenar scripts / imagens e outras coisas. Normalmente, mesmo se excluirmos nosso histórico, esse cache do navegador ainda permanece. Tudo o que precisamos é de uma maneira de armazenar nossos dados aqui. Existem 2 métodos para fazer isso. O melhor é usar uma imagem SVG e armazenar nossos dados dentro de suas tags. Dessa forma, os dados ainda podem ser extraídos, mesmo se o JavaScript estiver desativado usando o flash. No entanto, como isso é um pouco complicado, demonstrarei a outra abordagem que usa JSONP ( Wikipedia )
example.com/assets/js/tracking.js (na verdade tracking.php)
var now = new Date();
var window.__sid = "SessionID"; // Server generated
setCookie("sid", window.__sid, now.setFullYear(now.getFullYear() + 1, now.getMonth(), now.getDate() - 1));
if( "localStorage" in window ) {
window.localStorage.setItem("sid", window.__sid);
}
Agora podemos obter a chave da sessão a qualquer momento:
window.__sid || window.localStorage.getItem("sid") || getCookie("sid") || ""
Como faço para o tracking.js ficar no navegador?
Podemos conseguir isso usando os cabeçalhos Cache-Control , Last-Modified e ETag HTTP. Podemos usar o SessionID
valor as para o cabeçalho etag:
setHeaders({
"ETag": SessionID,
"Last-Modified": new Date(0).toUTCString(),
"Cache-Control": "private, max-age=31536000, s-max-age=31536000, must-revalidate"
})
Last-Modified
O cabeçalho informa ao navegador que esse arquivo basicamente nunca é modificado. Cache-Control
informa aos proxies e gateways para não armazenar em cache o documento, mas ao navegador para armazená-lo em cache por 1 ano.
Na próxima vez que o navegador solicitar o documento, ele enviará If-Modified-Since
e If-None-Match
cabeçalhos. Podemos usá-los para retornar uma 304 Not Modified
resposta.
example.com/assets/js/tracking.php
$sid = getHeader("If-None-Match") ?: getHeader("if-none-match") ?: getHeader("IF-NONE-MATCH") ?: "";
$ifModifiedSince = hasHeader("If-Modified-Since") ?: hasHeader("if-modified-since") ?: hasHeader("IF-MODIFIED-SINCE");
if( validateSession($sid) ) {
if( sessionExists($sid) ) {
continueSession($sid);
send304();
} else {
startSession($sid);
send304();
}
} else if( $ifModifiedSince ) {
send304();
} else {
startSession();
send200();
}
Agora, toda vez que o navegador solicitar, tracking.js
nosso servidor responderá com um 304 Not Modified
resultado e forçará a execução da cópia local de tracking.js
.
Eu ainda não entendo. Explique para mim
Vamos supor que o usuário limpe seu histórico de navegação e atualize a página. A única coisa que resta no computador dos usuários é uma cópia tracking.js
no cache do navegador. Quando o navegador solicita, tracking.js
ele recebe uma 304 Not Modified
resposta que faz com que execute a 1ª versão tracking.js
recebida. tracking.js
executa e restaura o SessionID
que foi excluído.
Validação
Suponha que o Haxor X roube os cookies de nossos clientes enquanto eles ainda estão conectados. Como os protegemos? Criptografia e impressão digital do navegador para o resgate. Lembre-se de nossa definição original para SessionID
was:
BrowserID|ComputerID|randomBytes(256)
Podemos mudar isso para:
Timestamp|BrowserID|ComputerID|encrypt(randomBytes(256), hk)|sign(Timestamp|BrowserID|ComputerID|randomBytes(256), hk)
Onde hk = sign(Timestamp|BrowserID|ComputerID, serverKey)
.
Agora podemos validar nosso SessionID
usando o seguinte algoritmo:
if( getTimestamp($sid) is older than 1 year ) return false;
if( getBrowserID($sid) !== createBrowserID($_Request, $_Server) ) return false;
if( getComputerID($sid) !== createComputerID($_Request, $_Server) return false;
$hk = sign(getTimestamp($sid) + getBrowserID($sid) + getComputerID($sid), $SERVER["key"]);
if( !verify(getTimestamp($sid) + getBrowserID($sid) + getComputerID($sid) + decrypt(getRandomBytes($sid), hk), getSignature($sid), $hk) ) return false;
return true;
Agora, para que o ataque da Haxor funcione, eles devem:
- Tenha o mesmo
ComputerID
. Isso significa que eles precisam ter o mesmo provedor de ISP que a vítima (Tricky). Isso dará à vítima a oportunidade de tomar uma ação legal em seu próprio país. A Haxor também deve obter a chave de sessão HTTPS da vítima (Difícil).
- Tenha o mesmo
BrowserID
. Qualquer pessoa pode falsificar a sequência do agente do usuário (irritante).
- Ser capaz de criar seu próprio falso
SessionID
(Muito Difícil). Os ataques de volume não funcionarão porque usamos um carimbo de data / hora para gerar a chave de criptografia / assinatura; basicamente, é como gerar uma nova chave para cada sessão. Além disso, criptografamos bytes aleatórios para que um simples ataque de dicionário também esteja fora de questão.
Podemos melhorar a validação encaminhando GoogleID
e FingerprintID
(via ajax ou campos ocultos) e comparando com esses.
if( GoogleID != getStoredGoodleID($sid) ) return false;
if( byte_difference(FingerPrintID, getStoredFingerprint($sid) > 10%) return false;