javascript - Ogiltigförklara JSON Web Tokens


För ett nytt node.js-projekt som jag arbetar med funderar jag på att byta från en cookie-baserad sessionsmetod (med detta menar jag att lagra ett ID i en nyckel-värde-lagring som innehåller användarsessioner i en användares webbläsare) till en token-baserad sessionsmetod (ingen nyckel-värde-lagring) med hjälp av JSON Web Tokens (jwt).

Projektet är ett spel som använder socket.io - att ha en tokenbaserad session skulle vara användbart i ett sådant scenario där det kommer att finnas flera kommunikationskanaler i en och samma session (webb och socket.io)

Hur skulle man tillhandahålla token/session ogiltigförklaring från servern med hjälp av JWT-metoden?

Jag ville också förstå vilka vanliga (eller ovanliga) fallgropar/attacker jag bör se upp för med denna typ av paradigm. Till exempel om detta paradigm är sårbart för samma/olika typer av attacker som den sessionsbaserade/kaka-baserade metoden.

Så, säg att jag har följande (anpassat från detta och detta):

Session Store-inloggning:

app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        // Create session token
        var token= createSessionToken();

        // Add to a key-value database
        KeyValueStore.add({token: {userid: profile.id, expiresInMinutes: 60}});

        // The client should save this session token in a cookie
        response.json({sessionToken: token});
    });
}

Inloggning med token:

var jwt = require('jsonwebtoken');
app.get('/login', function(request, response) {
    var user = {username: request.body.username, password: request.body.password };
    // Validate somehow
    validate(user, function(isValid, profile) {
        var token = jwt.sign(profile, 'My Super Secret', {expiresInMinutes: 60});
        response.json({token: token});
    });
}

--

En utloggning (eller ogiltigförklaring) för Session Store-metoden skulle kräva en uppdatering av KeyValueStore-databasen med den angivna token.

Det verkar som att en sådan mekanism inte skulle existera i den tokenbaserade metoden eftersom tokenet självt skulle innehålla informationen som normalt skulle finnas i nyckel-värde-lagret.

Svar #1

Jag har också forskat på denna fråga, och även om ingen av idéerna nedan är fullständiga lösningar, kan de hjälpa andra att utesluta idéer eller ge ytterligare sådana.

1) Ta helt enkelt bort token från klienten

Självklart gör detta inget för säkerheten på serversidan, men det stoppar en angripare genom att ta bort token från existens (dvs. de skulle ha varit tvungna att ha stulit token innan utloggning).

2) Skapa en tokenblocklista

Du kan lagra de ogiltiga token tills deras ursprungliga utgångsdatum, och jämföra dem mot inkommande förfrågningar. Detta verkar dock motverka anledningen till att gå helt tokenbaserat från början, eftersom du skulle behöva kontakta databasen för varje förfrågan. Lagringsstorleken skulle troligtvis vara mindre, eftersom du bara skulle behöva lagra token som var mellan utloggning och utgångstid (detta är en magkänsla och är definitivt beroende av sammanhanget).

3) Håll bara tokenutgångstiderna korta och byt dem ofta

Om du håller tokenens utgångstider till tillräckligt korta intervaller och låter den körande klienten hålla reda på och begära uppdateringar när det behövs, skulle nummer 1 i praktiken fungera som ett komplett utloggningssystem. Problemet med denna metod är att det gör det omöjligt att hålla användaren inloggad mellan att klientkoden stängs (beroende på hur lång du gör utgångsintervallet).

Kontinuitetsplaner

Om det någonsin skulle uppstå en nödsituation, eller om en användartoken blev komprometterad, är en sak du kan göra att tillåta användaren att ändra ett underliggande användarsöknings-ID med sina inloggningsuppgifter. Detta skulle göra alla associerade tokens ogiltiga, eftersom den associerade användaren inte längre skulle kunna hittas.

Jag ville också notera att det är en bra idé att inkludera det senaste inloggningsdatumet med token, så att du kan tvinga till en ny inloggning efter en viss tidsperiod.

När det gäller likheter/skillnader med avseende på attacker som använder token, tar detta inlägg upp frågan: https://github.com/dentarg/auth0-blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies-vs-token.markdown

Svar #2

De idéer som nämns ovan är bra, men ett mycket enkelt och lätt sätt att ogiltigförklara alla befintliga JWT:er är helt enkelt att ändra hemligheten.

Om din server skapar JWT, signerar det med en hemlighet (JWS) och sedan skickar det till klienten, kommer en enkel ändring av hemligheten att ogiltigförklara alla befintliga token och kräva att alla användare får en ny token för att autentisera sig, eftersom deras gamla token plötsligt blir ogiltig enligt servern.

Det kräver inga ändringar av de faktiska tokeninnehållet (eller sök-ID).

Tydligt fungerar detta endast för ett nödfall när du ville att alla befintliga token skulle löpa ut, för per-token-utgång krävs en av lösningarna ovan (såsom kort token-utgångstid eller ogiltigförklara en lagrad nyckel inuti token).

Svar #3

Detta är främst en lång kommentar som stöder och bygger vidare på svaret från @mattway

Givet:

Vissa av de andra föreslagna lösningarna på denna sida förespråkar att man träffar datalagringen vid varje förfrågan. Om du träffar huvudlagringen för att validera varje autentiseringsförfrågan, ser jag då mindre anledning att använda JWT istället för andra etablerade token-autentiseringsmekanismer. Du har i princip gjort JWT tillståndsberoende, istället för tillståndslös om du går till datalagringen varje gång.

(Om din webbplats får en stor mängd obehöriga förfrågningar, skulle JWT då neka dem utan att nå databasen, vilket är hjälpsamt. Det finns förmodligen andra användningsfall som det.)

Givet:

Verkligen statslös JWT-autentisering kan inte uppnås för en typisk, verklig webbapplikation eftersom statslös JWT inte har ett sätt att tillhandahålla omedelbart och säkert stöd för följande viktiga användningsfall:

  • Användarens konto är raderat/blockerat/suspenderat.

  • Användarens lösenord är ändrat.

  • Användarens roller eller behörigheter har ändrats.

  • Användaren har loggats ut av administratören.

  • Alla andra applikationskritiska data i JWT-token ändras av webbplatsadministratören.

Du kan inte vänta på att token ska löpa ut i dessa fall. Tokenogiltigförklaringen måste ske omedelbart. Dessutom kan du inte lita på att klienten inte behåller och använder en kopia av den gamla token, vare sig med illvillig avsikt eller inte.

Därför:

Jag tror att svaret från @matt-way, #2 TokenBlackList, skulle vara det mest effektiva sättet att lägga till det nödvändiga tillståndet i JWT-baserad autentisering.

Du har en svartlista som håller dessa tokens tills deras utgångsdatum inträffar. Listan över tokens kommer att vara ganska liten jämfört med det totala antalet användare, eftersom den bara behöver hålla svartlistade tokens tills deras utgång. Jag skulle implementera detta genom att lägga ogiltigförklarade tokens i Redis, Memcached eller en annan minnesbaserad datalagring som stöder att sätta en utgångstid på en nyckel.

Du måste fortfarande göra ett anrop till din in-memory-databas för varje autentiseringsförfrågan som passerar den initiala JWT-autentiseringen, men du behöver inte lagra nycklar för hela din användaruppsättning där. (Vilket kan vara mer eller mindre betydelsefullt för en viss webbplats.)

Svar #4

Jag skulle hålla ett register över jwt-versionnumret på användarmodellen. Nya jwt-token skulle sätta sin version till detta.

När du validerar jwt, kontrollera helt enkelt att det har ett versionsnummer som är lika med användarens aktuella jwt-version.

När som helst du vill ogiltigförklara gamla JWT, höj bara användarens JWT versionsnummer.

Svar #5

Har inte provat detta än, och det använder mycket information baserat på några av de andra svaren. Komplexiteten här är att undvika ett server-sidigt datalagringsanrop per begäran för användarinformation. De flesta andra lösningar kräver ett databassök per begäran till en användarsession-lagring. Det är okej i vissa scenarier, men detta skapades i ett försök att undvika sådana anrop och göra vilken server-sidig tillstånd som behövs mycket liten. Du kommer dock att sluta med att återskapa en server-sidig session, hur liten som helst, för att kunna tillhandahålla alla funktioner för tvångsupphävning. Men om du vill göra det, här är huvudsaken:

Mål:

  • Minska användningen av en datalagring (stateless).
  • Möjlighet att logga ut alla användare.
  • Möjlighet att tvinga någon att logga ut när som helst.
  • Möjlighet att kräva att lösenordet anges igen efter en viss tid.
  • Förmåga att arbeta med flera kunder.
  • Möjlighet att tvinga en ny inloggning när en användare klickar på logga ut från en viss klient. (För att förhindra att någon "återställer" en klienttoken efter att användaren har gått iväg - se kommentarer för ytterligare information)

Lösningen:

  • Använd kortlivade (<5 minuter) åtkomsttoken tillsammans med en längre levande (några timmar) klientlagrad uppdateringstoken.
  • Varje förfrågan kontrollerar antingen åtkomst- eller uppdateringstokenens utgångsdatum för giltighet.
  • När åtkomsttokenet går ut använder klienten uppfriskningstokenet för att uppdatera åtkomsttokenet.
  • Under kontrollen av uppfriskningstoken kontrollerar servern en liten svartlista över användar-id:n - om det hittas avvisas uppfriskningsförfrågan.
  • När en klient inte har en giltig (inte utgången) uppdaterings- eller autentiseringstoken måste användaren logga in igen, eftersom alla andra förfrågningar kommer att avvisas.
  • Vid inloggningsförfrågan, kontrollera användardatabasen för avstängning.
  • Vid utloggning - lägg till att användaren hamnar på sessionssvarta listan så att de måste logga in igen. Du skulle behöva lagra ytterligare information för att inte logga ut dem från alla enheter i en miljö med flera enheter, men det kan göras genom att lägga till ett enhetsfält i användarens svarta lista.
  • För att tvinga återinloggning efter x mängd tid - behåll senaste inloggningsdatum i autentiseringstoken och kontrollera det vid varje begäran.
  • För att tvinga alla användare att logga ut - återställ tokenhashnyckeln.

Detta kräver att du upprätthåller en svartlista (tillstånd) på servern, förutsatt att användartabellen innehåller information om förbjudna användare. Svarta listan för ogiltiga sessioner är en lista med användar-id:n. Denna svarta lista kontrolleras endast under en begäran om uppdatering av token. Poster måste finnas kvar på den så länge uppdaterings-tokenens TTL. När uppdaterings-tokenen går ut, måste användaren logga in igen.

Nackdelar:

  • Fortfarande krävs att göra en sökning i databutiken vid begäran om uppdateringstoken.
  • Ogiltiga token kan fortsätta att fungera under åtkomsttokenets TTL.

Fördelar:

  • Ger önskad funktionalitet.
  • Åtgärden för att uppdatera token är dold för användaren under normal drift.
  • Endast behövs göra en datalagringssökning vid uppdateringsförfrågningar istället för vid varje förfrågan. Det vill säga 1 var 15:e minut istället för 1 per sekund.
  • Minimerar serversidans tillstånd till en mycket liten svartlista.

Med denna lösning behövs inte en minnesbaserad datalagring som Redis, åtminstone inte för användarinformation eftersom servern bara gör ett databas-anrop var femtonde minut eller så. Om man använder Redis skulle det vara en mycket snabbare och enklare lösning att lagra en lista över giltiga/ogiltiga sessioner där. Ingen behov av en uppdateringstoken. Varje autentiseringstoken skulle ha ett session-ID och en enhets-ID, dessa skulle kunna lagras i en Redis-tabell vid skapandet och ogiltigförklaras när det är lämpligt. Sedan skulle de kontrolleras vid varje begäran och avvisas när de är ogiltiga.

Svar #6

Ett tillvägagångssätt jag har övervägt är att alltid ha ett iat (utfärdat vid) värde i JWT. Sedan när en användare loggar ut, lagra den tidpunkten på användarposten. När JWT valideras kan man jämföra iat med den senaste utloggningstiden. Om iat är äldre är det inte giltigt. Ja, du måste gå till databasen, men jag kommer ändå alltid att hämta användarposten om JWT annars är giltigt.

Den största nackdelen jag ser med detta är att det skulle logga ut dem från alla deras sessioner om de är i flera webbläsare eller också har en mobil klient.

Detta skulle också kunna vara en trevlig mekanism för att ogiltigförklara alla JWT:er i ett system. En del av kontrollen skulle kunna vara mot en global tidsstämpel för den senaste giltiga iat-tiden.

Svar #7

----------------Lite sent för detta svar men det kanske kan hjälpa någon----------------

Från klientsidan är det enklaste sättet att ta bort token från webbläsarens lagring.

Men, vad om du vill förstöra token på Node-servern -

Problemet med JWT-paketet är att det inte tillhandahåller någon metod eller sätt att förstöra token. Du kan använda olika metoder med avseende på JWT som nämns ovan. Men här går jag med jwt-redis.

Så för att kunna förstöra token på serversidan kan du använda jwt-redis-paketet istället för JWT

Detta bibliotek (jwt-redis) upprepar helt och hållet hela funktionaliteten hos biblioteket jsonwebtoken, med ett viktigt tillägg. Jwt-redis tillåter dig att lagra tokenIdentifier i redis för att verifiera giltighet. Avsaknaden av en tokenIdentifier i redis gör token ogiltig. För att förstöra token i jwt-redis finns det en destroy-metod

det fungerar på det här sättet:

  1. Installera jwt-redis från npm

  2. Att skapa:

var redis = require('redis');

var JWTR =  require('jwt-redis').default;

var redisClient = redis.createClient();

var jwtr = new JWTR(redisClient);

const secret = 'secret';

const tokenIdentifier = 'test';

const payload = { jti: tokenIdentifier }; // you can put other data in payload as well

jwtr.sign(payload, secret)
    .then((token)=>{
            // your code
    })
    .catch((error)=>{
            // error handling
    });
  1. För att verifiera:
jwtr.verify(token, secret);
  1. Att förstöra:
// if jti passed during signing of token then tokenIdentifier else token 
jwtr.destroy(tokenIdentifier or token) 

Obs :

1). Du kan ange expiresIn under inloggning av token på samma sätt som det anges i JWT.

2). Om jti inte skickas vid signering av token genereras jti slumpmässigt av biblioteket.

Kanske detta kommer att hjälpa dig eller någon annan. Tack.

Svar #8

Jag är lite sen här, men jag tror att jag har en hyfsad lösning.

Jag har en kolumn "last_password_change" i min databas som lagrar datum och tid när lösenordet senast ändrades. Jag lagrar också datum/tid för utfärdande i JWT. När jag validerar en token kontrollerar jag om lösenordet har ändrats efter att token utfärdades, och om det har gjorts avvisas token även om den ännu inte har gått ut.

Relaterade artiklar