JSON 웹 토큰을 무효화하는 중
현재 진행 중인 새로운 node.js 프로젝트에서는 쿠키 기반 세션 접근법(즉, 사용자의 브라우저에 사용자 세션을 포함하는 키 값 저장소로 ID를 저장함)에서 JSON Web 토큰(jwt)을 사용한 토큰 기반 세션 접근법(키 값 저장 없음)으로 전환하려고 합니다.
이 프로젝트는 socket.io을 활용한 게임입니다.한 세션에 여러 개의 커뮤니케이션 채널이 존재하는 시나리오(web 및 socket.io)에서 토큰 기반 세션을 갖는 것이 유용합니다.
jwt 접근 방식을 사용하여 서버에서 토큰/세션 무효화를 어떻게 제공합니까?
또한 이러한 패러다임에서 주의해야 할 일반적인(또는 드문) 함정/공격에 대해서도 이해하고 싶었습니다.예를 들어 이 패러다임이 세션스토어/쿠키 기반 접근법과 동일하거나 다른 종류의 공격에 취약한 경우.
예를 들어 다음과 같은 것이 있다고 합시다(이러한 것과 이것으로부터).
세션 스토어 로그인:
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});
});
}
토큰 기반 로그인:
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});
});
}
--
Session Store 접근법에 대한 로그아웃(또는 비활성화)을 수행하려면 지정된 토큰을 사용하여 KeyValueStore 데이터베이스를 업데이트해야 합니다.
토큰 자체에 키 값 저장소에 일반적으로 존재하는 정보가 포함되어 있기 때문에 토큰 기반 접근법에는 이러한 메커니즘이 존재하지 않는 것으로 보입니다.
저도 이 문제를 연구해 왔습니다.아래의 아이디어 중 어느 것도 완전한 해결책은 아니지만, 다른 사람이 아이디어를 배제하는 데 도움이 될 수도 있고, 더 많은 아이디어를 제공할 수도 있습니다.
1) 클라이언트에서 토큰을 삭제하기만 하면 됩니다.
서버측 보안에는 전혀 도움이 되지 않지만 토큰을 삭제함으로써 공격자를 막을 수 있습니다.로그아웃하기 전에 토큰을 도용해야 합니다).
2) 토큰 블록리스트 작성
유효하지 않은 토큰을 초기 만료일까지 저장하고 수신 요청과 비교할 수 있습니다.단, 이는 처음부터 모든 요구에 대해 데이터베이스를 터치해야 하기 때문에 완전한 토큰을 사용해야 하는 이유를 부정하는 것으로 보입니다.그러나 로그아웃에서 유효기간 사이의 토큰만 저장하면 되기 때문에 스토리지 크기는 더 작을 수 있습니다(이는 직감적인 느낌이며 컨텍스트에 따라 다릅니다).
3) 토큰의 유효기간을 짧게 하고, 자주 회전시킵니다.
토큰의 유효기간을 충분히 짧게 유지하고 실행 중인 클라이언트가 필요에 따라 업데이트를 추적하여 요구하도록 하면 번호1은 사실상 완전한 로그아웃시스템으로 기능합니다.이 방법의 문제는 (유효기간 간격에 따라) 클라이언트코드를 닫을 때까지 사용자를 로그인 상태로 둘 수 없다는 것입니다.
만일의 사태에 대비한 계획
긴급상황이 발생했거나 사용자 토큰이 손상된 경우 사용자가 로그인 자격정보를 사용하여 기본 사용자 룩업 ID를 변경할 수 있도록 허용해야 합니다.이렇게 하면 연결된 사용자를 더 이상 찾을 수 없기 때문에 연결된 모든 토큰이 비활성화됩니다.
또한 토큰에 마지막 로그인 날짜를 포함하는 것이 좋습니다.그러면 시간이 좀 지난 후에 다시 로그인 할 수 있습니다.
토큰을 사용한 공격에 관한 유사점/차이에 관해서는, 이 투고에서는, 다음의 질문에 대해 설명합니다.https://github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies-vs-token.markdown
위에 게시된 아이디어는 좋지만 기존 JWT를 모두 무효화하는 매우 간단하고 쉬운 방법은 단순히 비밀을 변경하는 것입니다.
서버가 JWT를 작성하고 비밀(JWS)으로 서명하여 클라이언트에 보내는 경우 암호를 변경하는 것만으로 기존 토큰이 모두 무효가 되며 서버에 따라 오래된 토큰이 갑자기 무효가 되기 때문에 모든 사용자가 인증을 위해 새로운 토큰을 얻어야 합니다.
실제 토큰 내용(또는 조회 ID)을 수정할 필요가 없습니다.
확실히 이것은 모든 기존 토큰의 유효기간이 만료되는 긴급 상황에만 유효합니다.토큰의 유효기간별로 상기의 솔루션 중 하나가 필요하기 때문입니다(토큰의 유효기간이 짧거나 토큰내에 보존되어 있는 키가 무효가 되는 등).
이는 주로 @mattway의 답변을 뒷받침하고 구축한 긴 코멘트입니다.
지정:
이 페이지에서 제안하는 다른 솔루션 중 일부는 모든 요청에 대해 데이터스토어를 타격할 것을 권장합니다.모든 인증 요청을 검증하기 위해 기본 데이터스토어를 누르면 다른 확립된 토큰 인증 메커니즘 대신 JWT를 사용해야 할 이유가 줄어듭니다.따라서 매번 데이터스토어로 이동하는 경우 상태 비저장 대신 JWT를 상태 저장 상태로 만들었습니다.
사이트에 대량의 무단 요청이 수신되면 JWT가 데이터스토어에 영향을 주지 않고 요청을 거부하므로 유용합니다.아마도 이와 같은 다른 사용 사례도 있을 것입니다.
지정:
상태 비저장 JWT는 다음과 같은 중요한 사용 사례에 대해 즉각적이고 안전한 지원을 제공할 수 없기 때문에 일반적인 실제 웹 앱에서는 상태 비저장 JWT 인증을 달성할 수 없습니다.
사용자의 계정이 삭제/차단/일시 중단되었습니다.
사용자의 비밀번호가 변경되었습니다.
사용자의 역할 또는 권한이 변경됩니다.
사용자가 관리자에 의해 로그아웃되었습니다.
JWT 토큰 내의 다른 응용 프로그램에 중요한 데이터는 사이트 관리자에 의해 변경됩니다.
이 경우 토큰이 만료될 때까지 기다릴 수 없습니다.토큰 무효화는 즉시 이루어져야 합니다.또한 클라이언트가 악의적인 의도가 있든 없든 이전 토큰의 복사본을 보관하고 사용하지 않을 것이라고 신뢰할 수 없습니다.
그 때문에,
JWT 기반 인증에 필요한 상태를 추가하려면 @matt-way, #2 TokenBlackList의 답변이 가장 효율적이라고 생각합니다.
이러한 토큰은 만료 날짜가 될 때까지 유지되는 블랙리스트가 있습니다.토큰 목록은 전체 사용자 수에 비해 상당히 적습니다. 왜냐하면 블랙리스트에 있는 토큰이 만료될 때까지만 보관하면 되기 때문입니다.유효하지 않은 토큰을 redis, memcached 또는 키의 유효기간 설정을 지원하는 다른 메모리 내 데이터스토어에 저장하여 구현합니다.
초기 JWT 인증을 통과한 모든 인증요구에 대해 인메모리 DB에 호출해야 하지만 사용자 전체 세트를 저장할 필요는 없습니다(이것은 특정 사이트에 큰 문제가 될 수도 있고 아닐 수도 있습니다).
사용자 모델에 jwt 버전 번호를 기록하겠습니다.새로운 jwt 토큰은 그 버전을 이렇게 설정합니다.
jwt를 검증할 때 사용자의 현재 jwt 버전과 동일한 버전 번호를 가지고 있는지 확인하기만 하면 됩니다.
오래된 jwt를 비활성화하려면 항상 사용자의 jwt 버전 번호를 범핑하십시오.
아직 시도해보지 않았고, 다른 답변들을 바탕으로 많은 정보를 사용하고 있습니다.여기서의 복잡성은, 유저 정보의 요구 마다 서버측의 데이터 스토어의 콜을 회피하는 것입니다.다른 솔루션 대부분은 사용자 세션스토어에 대한 요청당 DB 조회가 필요합니다.이는 일부 시나리오에서는 문제가 없지만 이러한 콜을 피하고 필요한 서버 측 상태를 매우 작게 만들기 위해 작성되었습니다.모든 강제 무효화 기능을 제공하기 위해서는 아무리 작더라도 서버 측 세션을 다시 작성합니다. 하지만 여기서 하고 싶다면, 요점은 다음과 같습니다.
목표:
- 데이터 스토어의 사용을 경감합니다(스테이트리스).
- 모든 사용자를 강제로 로그아웃할 수 있습니다.
- 언제든지 모든 사용자를 강제로 로그아웃할 수 있습니다.
- 일정 시간 후 비밀번호 재입력을 요구할 수 있습니다.
- 여러 클라이언트와 연계할 수 있습니다.
- 사용자가 특정 클라이언트에서 로그아웃을 클릭했을 때 강제로 재로그인을 할 수 있습니다.(사용자가 퇴장 후 클라이언트 토큰을 "삭제 해제"하지 않도록 하려면 - 자세한 내용은 주석을 참조하십시오.)
솔루션:
- 단수명(5m 미만) 액세스 토큰을 긴 수명(정규 시간)의 클라이언트 스토어 리프레시 토큰과 쌍으로 사용합니다.
- 모든 요구는 auth 또는 refresh 토큰의 유효기간을 체크합니다.
- 액세스 토큰이 만료되면 클라이언트는 리프레시 토큰을 사용하여 액세스토큰을 리프레시 합니다
- 새로 고침 토큰 검사 중에 서버는 사용자 ID의 작은 블랙리스트를 검사합니다. 새로 고침 요청을 거부합니다.
- 클라이언트에 유효한(만료되지 않은) 새로 고침 또는 인증 토큰이 없는 경우 다른 모든 요청이 거부되므로 사용자는 다시 로그인해야 합니다.
- 로그인 요청 시 사용자 데이터 저장소에서 금지 여부를 확인합니다.
- 로그아웃 시 - 해당 사용자를 세션 블랙리스트에 추가하여 다시 로그인해야 합니다.여러 디바이스 환경에서 모든 디바이스에서 로그아웃하지 않으려면 추가 정보를 저장해야 합니다.단, 디바이스 필드를 사용자 블랙리스트에 추가하면 됩니다.
- x 시간 후 강제로 재입력하려면 인증 토큰에 마지막 로그인 날짜를 유지하고 요청별로 확인합니다.
- 모든 사용자를 강제로 로그아웃하려면 - 토큰 해시 키를 재설정합니다.
이렇게 하려면 사용자 테이블에 금지된 사용자 정보가 포함되어 있다고 가정하고 서버에서 블랙리스트(상태)를 유지해야 합니다.비활성 세션 블랙리스트 - 사용자 ID 목록입니다.이 블랙리스트는 토큰 새로 고침 요청 중에만 검사됩니다.엔트리는 리프레시 토큰 TTL인 한 존속해야 합니다.리프레시 토큰이 만료되면 사용자는 다시 로그인해야 합니다.
단점:
- 리프레시 토큰 요구에 대해 데이터 저장소 조회를 수행해야 합니다.
- 액세스 토큰의 TTL에 대해 잘못된 토큰이 계속 작동할 수 있습니다.
장점:
- 필요한 기능을 제공합니다.
- 통상의 조작에서는, 토큰의 리프레시 액션은 유저로부터 숨겨집니다.
- 모든 요청이 아닌 새로 고침 요청에 대해서만 데이터 저장소 조회를 수행해야 합니다.즉, 초당 1이 아닌 15분마다 1개입니다.
- 서버측 상태를 매우 작은 블랙리스트로 최소화합니다.
이 솔루션에서는 reddis와 같은 메모리 데이터 저장소가 필요하지 않습니다.서버는 15분 정도 간격으로 DB 콜을 발신하기 때문에 사용자 정보에는 사용할 수 없습니다.reddis를 사용하는 경우 유효/비활성 세션목록을 저장하는 것이 매우 빠르고 간단한 솔루션이 될 수 있습니다.새로 고침 토큰은 필요 없습니다.각 인증 토큰은 세션 ID와 디바이스 ID를 가지며 생성 시 reddis 테이블에 저장되며 필요에 따라 비활성화됩니다.그러면 모든 요청에 대해 검사되고 무효가 되면 거부됩니다.
제가 생각해온 접근법은 항상 이 모든 것들을iat
JWT(JWT)입니다.그런 다음 사용자가 로그아웃할 때 해당 타임스탬프를 사용자 레코드에 저장합니다.할 때는 JWT를 .iat
마지막으로 로그아웃한 타임스탬프까지. 경우,iat
이치노만, . 하지만 JWT가 유효하다면 저는 항상 사용자 레코드를 가져올 것입니다.
여기서 가장 큰 단점은 여러 브라우저에 있거나 모바일 클라이언트가 있는 경우 세션에서 로그아웃된다는 것입니다.
이는 시스템 내의 모든 JWT를 무효화하는 훌륭한 메커니즘이 될 수도 있습니다.일 수 .iat
time.time.time.time.time.time
-----------------------------------------------------------------------------------------------------------------
클라이언트 측에서 가장 쉬운 방법은 브라우저 저장소에서 토큰을 삭제하는 것입니다.
단, 노드 서버의 토큰을 파기하려면 어떻게 해야 합니까?
JWT 패키지의 문제는 토큰을 폐기하는 방법이나 방법을 제공하지 않는다는 것입니다.위에서 설명한 JWT와 관련하여 다른 방법을 사용할 수 있습니다.하지만 난 Jwt-redis로 간다.
따라서 서버 측에서 토큰을 파기하려면 JWT 대신 jwt-redis 패키지를 사용할 수 있습니다.
이 라이브러리(jwt-redis)는 라이브러리 jsonwebtoken의 모든 기능을 완전히 반복하며 중요한 추가 사항을 추가합니다.Jwt-redis를 사용하면 tokenIdentifier를 redis로 저장하여 유효성을 확인할 수 있습니다.redis에 tokenIdentifier가 없으면 토큰이 유효하지 않습니다.jwt-redis에서 토큰을 파기하려면 파기 방법이 있습니다.
다음과 같이 동작합니다.
npm부터 jwt-redis 설치
작성 방법:
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 });
- 확인하려면:
jwtr.verify(token, secret);
- 파기 방법:
// if jti passed during signing of token then tokenIdentifier else token jwtr.destroy(tokenIdentifier or token)
주의:
1) 기한을 지정할 수 있습니다.JWT에서 제공되는 것과 동일한 토큰 사인 중.
2) 토큰 서명 중에 jti가 전달되지 않으면 라이브러리에서 jti가 랜덤으로 생성됩니다.
이게 당신이나 다른 사람에게 도움이 될 수도 있어요감사해요.
좀 늦었지만 괜찮은 해결책이 있을 것 같아요.
데이터베이스에 비밀번호가 마지막으로 변경된 날짜와 시간을 저장하는 "last_password_change" 열이 있습니다.JWT에도 발행일시를 저장하고 있습니다.토큰을 검증할 때 토큰 발행 후 패스워드가 변경되었는지, 아직 유효기간이 지나지 않았는데도 토큰이 거부되었는지 확인합니다.
사용자의 문서/레코드 DB에 "last_key_used" 필드가 있을 수 있습니다.
사용자가 로그인하여 통과하면 새로운 랜덤 문자열을 생성하여 last_key_used 필드에 저장하고 토큰 서명 시 payload에 추가합니다.
사용자가 토큰을 사용하여 로그인할 때 DB에서 사용된 last_key_를 확인하여 토큰과 일치시킵니다.
그런 다음 사용자가 로그아웃을 할 때 또는 토큰을 무효로 할 경우 "last_key_used" 필드를 다른 랜덤 값으로 변경하기만 하면 후속 체크가 실패하기 때문에 사용자는 다시 로그인하여 패스해야 합니다.
이렇게 메모리 내 목록 보관
user_id revoke_tokens_issued_before
-------------------------------------
123 2018-07-02T15:55:33
567 2018-07-01T12:34:21
토큰이 1주일 후에 만료되면 그보다 오래된 레코드를 삭제하거나 무시하십시오.또한 각 사용자의 최신 기록만 보관합니다.목록 크기는 토큰을 보관하는 기간과 사용자가 토큰을 해지하는 빈도에 따라 달라집니다.db는 테이블이 변경될 때만 사용하십시오.애플리케이션을 기동하면, 테이블을 메모리에 로드합니다.
사용자별 고유 문자열 및 글로벌 문자열이 함께 해시됨
to serve as the JWT secret portion allow both individual and global token invalidation. Maximum flexibility at the cost of a db lookup/read during request auth. Also easy to cache as well, since they are seldom changing.다음은 예를 제시하겠습니다.
HEADER:ALGORITHM & TOKEN TYPE
{
"alg": "HS256",
"typ": "JWT"
}
PAYLOAD:DATA
{
"sub": "1234567890",
"some": "data",
"iat": 1516239022
}
VERIFY SIGNATURE
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
HMACSHA256('perUserString'+'globalString')
)
where HMACSHA256 is your local crypto sha256
nodejs
import sha256 from 'crypto-js/sha256';
sha256(message);
예를 들어 사용방법에 대해서는 https://jwt.io을 참조해 주십시오(다이나믹 256비트비밀을 처리할 수 있는지는 확실하지 않습니다).
jti claim(난스)을 사용하여 목록에 사용자 레코드 필드로 저장하면 됩니다(db 의존적이지만 최소한 쉼표로 구분된 목록이면 됩니다).다른 사용자가 지적한 것처럼 사용자 레코드를 가져오고 싶을 것입니다.이렇게 하면 다른 클라이언트인스턴스에 대해 유효한 토큰을 여러 개 보유할 수 있습니다('로그아웃하면 목록이 비어 있음'으로 리셋될 수 있습니다.
파티에 늦게 도착한 MY 2센트는 조사 후 아래에 기재되어 있습니다.로그아웃 중에 다음 사항이 발생하는지 확인합니다.
클라이언트 스토리지/세션 클리어
로그인 또는 로그아웃이 각각 발생할 때마다 사용자 테이블의 마지막 로그인 날짜 시간과 로그아웃 날짜 시간을 업데이트합니다.따라서 로그인 날짜 시간은 항상 로그아웃보다 커야 합니다(또는 현재 상태가 로그인 상태이고 아직 로그아웃되지 않은 경우에는 로그아웃 날짜를 null로 유지합니다).
이것은 블랙리스트의 추가 테이블을 유지하고 정기적으로 삭제하는 것보다 훨씬 더 간단하다.여러 디바이스를 지원하려면 로그인 및 로그아웃 날짜를 유지하기 위해 OS 또는 클라이언트에 대한 세부 정보 등의 추가 정보가 포함된 추가 테이블이 필요합니다.
다음과 같이 했습니다.
- 생성하다
unique hash
redis와 JWT에 저장합니다.이것은 세션이라고 할 수 있습니다.- 또한 특정 JWT의 요청 수도 저장합니다.jwt가 서버로 전송될 때마다 요청 수가 정수로 증가합니다.(옵션)
따라서 사용자가 로그인하면 고유한 해시가 생성되어 redis에 저장되고 JWT에 주입됩니다.
사용자가 보호된 엔드포인트를 방문하려고 하면 JWT에서 고유한 세션 해시를 가져와서 redis를 쿼리하고 일치 여부를 확인합니다.
이를 확장하여 JWT의 보안을 더욱 강화할 수 있습니다.
특정 JWT가 X 요청을 할 때마다 새로운 고유 세션을 생성하여 JWT에 저장한 다음 이전 세션을 블랙리스트에 올립니다.
즉, JWT는 지속적으로 변경되며 오래된 JWT가 해킹당하거나 도난당하는 것을 방지합니다.
- 토큰 유효기간 1일 지정
- 매일 블랙리스트를 유지합니다.
- 무효/로그아웃 토큰을 블랙리스트에 올립니다.
토큰 검증의 경우 토큰 만료 시간을 먼저 확인한 후 토큰이 만료되지 않은 경우 블랙리스트를 확인하십시오.
긴 세션의 경우 토큰 유효기간을 연장하는 메커니즘이 필요합니다.
Kafka 메시지 큐 및 로컬 블랙리스트
저는 카프카 같은 메시징 시스템을 사용할까 생각했습니다.설명하겠습니다.
예를 들어, 1개의 마이크로 서비스(userMgmtMs 서비스라고 부름)를 사용할 수 있습니다.이 서비스에는 예를 들어,login
★★★★★★★★★★★★★★★★★」logout
JWT에 의한 것입니다.이 토큰은 클라이언트에 전달됩니다.
이제 클라이언트는 이 토큰을 사용하여 다른 마이크로 서비스(priceMs라고 부릅니다)를 호출할 수 있습니다.가격 범위 내에서 Ms는 데이터베이스 체크가 이루어지지 않습니다.users
초기 토큰 생성이 트리거된 테이블입니다. "userMgmtMs" .또한 JWT 토큰에는 권한/역할이 포함되어 있어야 합니다.따라서 priceMs는 스프링 보안 기능을 위해 DB에서 아무것도 검색할 필요가 없습니다.
JwtRequestFilter는 가격에서 DB로 이동하는 대신 JWT 토큰에 제공된 데이터로 생성된 UserDetails 개체를 제공할 수 있습니다(암호 없이).
그러면 토큰을 로그아웃하거나 무효화하려면 어떻게 해야 할까요?priecesMs에 대한 모든 요구에서 userMgmtMs 데이터베이스를 호출하고 싶지 않기 때문에(많은 불필요한 의존관계가 도입됩니다), 이 토큰블랙리스트를 사용하는 것이 해결책일 수 있습니다.
이 블랙리스트를 중앙 집중식으로 유지하고 모든 마이크로 서비스의 1개의 테이블에 의존하는 대신 kafka 메시지 큐를 사용할 것을 제안합니다.
userMgmtMs를 합니다.logout
이렇게 하면 자체 블랙리스트(마이크로 서비스 간에 공유되지 않는 테이블)에 추가됩니다.또한 이 토큰의 내용이 포함된 kafka 이벤트를 다른 모든 마이크로 서비스가 가입되어 있는 내부 kafka 서비스로 전송합니다.
다른 마이크로서비스는 카프카 이벤트를 수신하면 내부 블랙리스트에 포함시킵니다.
로그아웃 시 일부 마이크로서비스가 다운되어도 최종적으로 다시 업 상태가 되어 나중에 메시지가 수신됩니다.
kafka는 클라이언트가 읽은 메시지를 참조할 수 있도록 개발되었기 때문에 클라이언트, 다운 또는 업이 이 비활성 토큰을 놓치는 일이 없습니다.
제가 생각할 수 있는 유일한 문제는 카프카 메시징 서비스가 다시 단일 장애 지점을 도입한다는 것입니다.그러나 이와는 반대되는 것은 유효하지 않은 JWT 토큰이 모두 저장되고 이 DB 또는 마이크로 서비스가 다운된 글로벌 테이블이 하나라도 있으면 아무 것도 작동하지 않기 때문입니다.kafka 접근법 + 일반 사용자 로그아웃용 JWT 토큰을 클라이언트 측에서 삭제하면 대부분의 경우 kafka 다운타임이 눈에 띄지 않습니다.블랙리스트는 모든 마이크로 서비스 간에 내부 복사본으로 배포되기 때문입니다.
해킹당한 사용자를 무효화해야 할 경우 kafka가 다운되면 여기서부터 문제가 발생합니다.이 경우 최후의 수단으로 비밀을 변경하는 것이 도움이 될 수 있습니다.또는 kafka가 올라갔는지 확인한 후 실행하세요.
면책사항:저는 아직 이 솔루션을 구현하지 않았지만, 제안된 솔루션의 대부분은 중앙 데이터베이스 룩업을 통해 JWT 토큰의 아이디어를 부정하는 것 같습니다.그래서 나는 다른 해결책을 생각하고 있었다.
어떻게 생각하시는지 알려주세요. 말이 되나요?아니면 안 되는 명백한 이유가 있나요?
사용자 토큰을 해지하려면 DB에서 발행된 모든 토큰을 추적하고 세션과 유사한 테이블에 유효한지(존재하는지) 확인할 수 있습니다.단점은 요청 시마다 DB를 검색한다는 것입니다.
시도해보지 않았지만 DB 히트를 최소화하면서 토큰을 해지할 수 있도록 다음과 같은 방법을 제안합니다.
데이터베이스 체크율을 낮추려면 발행된 모든 JWT 토큰을 결정론적 연관성에 따라 X그룹으로 나눕니다(예: 사용자 ID의 첫 번째 자릿수로 10개의 그룹).
에는 그룹 및 작성 시 JWT ID)가 됩니다.{ "group_id": 1, "timestamp": 1551861473716 }
는 모든 에 보관 각 타임스탬프가 : " " " " " " " ID " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "{ "group1": 1551861473714, "group2": 1551861487293, ... }
오래된 그룹 타임스탬프를 가진 JWT 토큰을 가진 요청은 유효성을 검사하고(DB 히트), 유효할 경우 클라이언트가 나중에 사용할 수 있도록 새로운 타임스탬프를 가진 새로운 JWT 토큰이 발행됩니다.토큰의 그룹 타임스탬프가 최신일 경우 JWT(DB 적중 없음)를 신뢰합니다.
그래서...
- 토큰에 오래된 그룹 타임스탬프가 있는 경우에만 DB를 사용하여 JWT 토큰의 유효성을 검사하지만, 이후 요청은 사용자 그룹의 누군가가 로그아웃할 때까지 유효성이 검사되지 않습니다.
- 그룹을 사용하여 타임스탬프 변경 수를 제한합니다(내일이 없는 것처럼 로그인 및 로그아웃하는 사용자가 있다고 가정하면 모든 사용자가 아니라 제한된 수의 사용자만 영향을 받습니다).
- 메모리에 보관되어 있는 타임스탬프의 양을 제한하기 위해 그룹 수를 제한합니다.
- 토큰을 무효화하는 것은 간단합니다.세션 테이블에서 토큰을 삭제하고 사용자 그룹에 대한 새로운 타임스탬프를 생성하기만 하면 됩니다.
"모든 디바이스에서 로그아웃" 옵션이 허용 가능한 경우(대부분 해당):
- 토큰 버전필드를 사용자 레코드에 추가합니다.
- JWT에 저장된 클레임에 이 필드의 값을 추가합니다.
- 사용자가 로그아웃 할 때마다 버전을 늘립니다.
- 토큰을 검증할 때 버전 클레임을 사용자 레코드에 저장된 버전과 비교하고 동일하지 않으면 거부합니다.
대부분의 경우 사용자 레코드를 가져오기 위한 DB 트립이 필요하기 때문에 검증 프로세스에 오버헤드가 많이 발생하지 않습니다.참여 또는 개별 호출을 사용해야 하므로 DB 로드가 큰 블랙리스트를 유지하는 것과 달리 오래된 레코드를 정리하는 등의 작업을 수행합니다.
JWT 새로 고침 사용...
실용적이라고 생각되는 접근법은 갱신 토큰(GUID일 수 있음)과 상대 갱신 토큰 ID(갱신 횟수에 관계없이 변경되지 않음)를 데이터베이스에 저장하고 사용자의 JWT 생성 시 이를 사용자에 대한 클레임으로 추가하는 것입니다.데이터베이스 대신 메모리 캐시를 사용할 수 있습니다.하지만 이 답변에는 데이터베이스를 사용하고 있습니다.
그런 다음 JWT 만료 전에 클라이언트가 호출할 수 있는 JWT 새로 고침 웹 API 엔드포인트를 만듭니다.새로 고침이 호출되면 JWT의 클레임에서 새로 고침 토큰을 가져옵니다.
JWT 리프레시 엔드 포인트에 대한 콜 시 데이터베이스 상의 쌍으로 현재 리프레시 토큰과 리프레시 토큰 ID를 확인합니다.새로운 리프레시 토큰을 생성하여 리프레시 토큰 ID를 사용하여 데이터베이스의 오래된 리프레시 토큰을 대체합니다.JWT에서 추출할 수 있는 클레임임을 기억하십시오.
현재 JWT에서 사용자 클레임을 추출합니다.새 JWT 생성 프로세스를 시작합니다.이전 새로 고침 토큰 클레임 값을 데이터베이스에 새로 저장된 새로 생성된 새로 고침 토큰으로 바꿉니다.그리고 새로운 JWT를 생성하여 클라이언트에 전송합니다.
따라서 리프레시 토큰을 사용한 후 의도한 사용자가 사용하든 공격자가 사용하든 상관없이 데이터베이스에서 리프레시 토큰 ID와 쌍을 이루지 않는 리프레시 토큰을 사용하려고 해도 새로운 JWT가 생성되지 않으므로 리프레시 토큰 ID를 가진 클라이언트가 백엔드를 사용할 수 없게 됩니다.이러한 클라이언트(정규 클라이언트 포함)의 완전한 로그아웃으로 이행합니다.
그것이 기본적인 정보를 설명해 줍니다.
다음으로 JWT를 갱신할 수 있는 기간을 창으로 설정하여 창 밖에 있는 모든 것이 의심스러운 액티비티가 되도록 합니다.예를 들어, JWT가 만료되기 전에 창이 10분 정도 걸릴 수 있습니다.JWT가 생성된 날짜 시간은 JWT 자체에 클레임으로 저장할 수 있습니다.이러한 의심스러운 액티비티가 발생했을 경우(즉, 윈도 내에서 이미 사용되고 있는 리프레시 토큰 ID를 다른 사용자가 재사용하려고 할 경우)는 리프레시 토큰 ID를 무효로 마킹해야 합니다.따라서 리프레시 토큰 ID의 유효한 소유자라도 새로 로그인해야 합니다.
데이터베이스에서 제시된 새로 고침 토큰 ID와 쌍을 이루는 새로 고침 토큰을 찾을 수 없으면 새로 고침 토큰 ID를 비활성화해야 합니다.예를 들어 공격자가 이미 사용한 새로 고침 토큰을 유휴 사용자가 사용하려고 할 수 있습니다.
공격자에 의해 도난당하여 의도된 사용자가 사용하기 전에 사용된 JWT는 앞서 설명한 바와 같이 사용자가 새로 고침 토큰을 사용하려고 할 때도 유효하지 않은 것으로 표시됩니다.
유일한 문제는 공격자가 이미 JWT를 도난당한 후에도 클라이언트가 JWT 갱신을 시도하지 않는 경우입니다.그러나 공격자가 보호하지 않는(또는 이와 유사한) 클라이언트에서는 이러한 현상이 발생할 가능성이 낮습니다. 즉, 클라이언트가 백엔드 사용을 중지하는 시점에 대해 공격자가 클라이언트를 예측할 수 없습니다.
클라이언트가 통상의 로그아웃을 개시하는 경우.로그아웃 시 데이터베이스에서 리프레시 토큰 ID 및 관련 레코드를 삭제해야 합니다.따라서 클라이언트는 리프레시 JWT를 생성할 수 없습니다.
다음과 같은 접근방식을 통해 두 가지 솔루션을 모두 활용할 수 있습니다.
"immediate"는 "~1분"을 의미합니다.
케이스:
사용자가 로그인을 시도합니다.
A. 토큰에 "발행 시간" 필드를 추가하고 필요에 따라 유효 기간을 유지합니다.
B. 사용자 비밀번호 해시의 해시를 저장하거나 사용자 테이블에 토큰 해시라고 하는 새 필드를 만듭니다.생성된 토큰에 토큰 해시를 저장합니다.
사용자는 다음 URL에 액세스합니다.
A. "발행 시간"이 "즉시" 범위에 있는 경우 토큰을 정상적으로 처리합니다."발행 시간"을 변경하지 마십시오."즉시" 기간에 따라 취약 기간이 달라집니다.단, 1~2분 정도의 짧은 시간은 그다지 위험하지 않습니다.(이것은 퍼포먼스와 보안의 균형입니다.)여기서 DB를 찾을 필요는 없습니다.
B. 토큰이 "즉시" 범위에 있지 않은 경우 토큰 해시를 db와 대조하여 확인합니다.문제가 없는 경우 "발행 시간" 필드를 업데이트합니다.문제가 해결되지 않으면 요청을 처리하지 마십시오(최종 보안 적용).
사용자는 토큰 해시를 변경하여 계정을 보호합니다."즉각" 미래에 그 계좌는 확보된다.
데이터베이스 룩업을 "즉시" 범위로 저장합니다.이것은, 「즉시」시간내에 클라이언트로부터의 요구가 버스트 하는 경우에 가장 유리합니다.
JWT 리프레쉬를 사용하지 않고...
두 가지 공격 시나리오가 떠오릅니다.하나는 손상된 로그인 자격 정보에 대한 것입니다.그리고 다른 하나는 JWT의 실제 절도입니다.
손상된 로그인 자격 증명의 경우 새 로그인이 발생할 때 일반적으로 사용자에게 이메일 알림을 보냅니다.따라서 고객이 로그인한 사람이 아닌 경우 자격 정보의 리셋을 실시하도록 권장합니다.이것에 의해, 패스워드가 마지막으로 설정되어 있던 날짜가 데이타베이스/캐시에 보존됩니다(또, 유저가 초기 등록시에 패스워드를 설정할 때에도, 이 설정을 실시합니다).사용자 액션이 인가될 때마다 사용자가 패스워드를 변경한 날짜를 데이터베이스/캐시에서 가져와 특정 JWT가 생성된 날짜와 비교해야 합니다.또, 상기 credential의 날짜 시각이 리셋 되기 전에 생성된 JWT의 액션을 금지하고, 결과적으로 이러한 JWT는 무효가 됩니다.즉, JWT 생성 일시를 JWT 자체에 클레임으로 저장한다.ASP에서.NET Core, 정책/요건을 사용하여 이 비교를 수행할 수 있으며 장애가 발생하면 클라이언트는 금지됩니다.이것에 의해, credential의 리셋이 행해질 때마다, 백엔드의 유저가 글로벌하게 로그아웃 됩니다.
JWT를 실제로 도난당한 경우...JWT 도난은 쉽게 감지되지 않지만 만료되는 JWT로 쉽게 해결할 수 있습니다.그러나 JWT가 만료되기 전에 공격자를 막기 위해 무엇을 할 수 있을까요?이는 실제 글로벌 로그아웃을 수반합니다.이는 위에서 credential 리셋에 대해 설명한 것과 유사합니다.이를 위해서는 보통 사용자가 글로벌로그아웃을 시작한 날짜를 데이터베이스/캐시에 저장하고 사용자 액션을 인가할 때 취득하여 특정 JWT 생성 날짜와 비교합니다.또한 해당 글로벌로그아웃 날짜 이전에 생성된 JWT에 대한 액션을 금지합니다.따라서 기본적으로 이러한 JWT는 사용할 수 없게 됩니다.이것은 ASP의 정책/요건을 사용하여 수행할 수 있습니다.앞서 설명한 바와 같이 NET Core.
그럼 JWT의 도난은 어떻게 감지하나요?현시점에서는, 공격자가 확실히 로그아웃 할 수 있기 때문에, 유저에게 글로벌하게 로그아웃 했다가 다시 로그아웃 하도록 경고하는 것이, 현시점에서는 대응책입니다.
토큰을 무효화하는 좋은 방법으로는 데이터베이스 트립이 필요합니다.역할 변경, 암호 변경, 이메일 등과 같이 사용자 기록의 일부 변경 시기를 포함하는 목적입니다. 해서 더하면 .modified
★★★★★★★★★★★★★★★★★」updated_at
사용자 레코드의 필드에 이 변경 시간을 기록한 후 클레임에 포함시킵니다.따라서 JWT가 인증될 때 클레임의 시간과 DB에 기록된 시간을 비교합니다. 클레임의 시간이 이전이라면 토큰이 유효하지 않습니다.은 또한 합니다.iat
DB db db db 、 [ DB 。
주의: 를 사용하고 있는 경우modified
★★★★★★★★★★★★★★★★★」updated_at
옵션을 선택하면 사용자가 로그인 및 로그아웃할 때 업데이트해야 합니다.
사용자 스키마에 다음 개체를 추가하기만 하면 됩니다.
const userSchema = new mongoose.Schema({
{
... your schema code,
destroyAnyJWTbefore: Date
}
/마다 이 를 "/login" POST로 합니다.Date.now()
에서 "를 체크하면 "Middleware"는 "Middleware"를 체크합니다.isAuthanticated
★★★★★★★★★★★★★★★★★」protected
있는 이든 간에 '을하여 '확인'을 합니다.myjwt.iat
userDoc.destroyAnyJWTbefore
.
- 서버측에서 JWT를 파괴하고 싶다면 이 솔루션이 가장 적합합니다.
- 이 솔루션은 더 이상 클라이언트 측에 의존하지 않으며 서버 측에 토큰 저장을 중지한다는 JWT 사용의 주요 목표를 위반합니다.
- 프로젝트 컨텍스트에 따라 다르지만 대부분의 경우 서버에서 JWT를 파기해야 합니다.
클라이언트측에서만 토큰을 파기할 경우 브라우저에서 쿠키를 삭제하기만 하면 됩니다(클라이언트가 브라우저인 경우).스마트폰이나 다른 클라이언트에서도 마찬가지입니다.
서버측에서 토큰을 파기하는 경우는 Radis를 사용하여 다른 사용자가 언급한 블랙리스트 스타일을 구현하여 신속하게 이 작업을 수행할 것을 권장합니다.
여기서 중요한 질문은 JWT가 쓸모없느냐는 것입니다.아무도 모른다.
JWT를 사용할 때 모든 기기에서 로그아웃을 제공해야 하는 경우 답변 드리겠습니다.이 방법에서는 각 요청에 대해 데이터베이스 룩업을 사용합니다.서버 크래시가 발생해도 지속적인 보안 상태가 필요하기 때문입니다.사용자 테이블에는 두 개의 열이 있습니다.
- LastValidTime(기본값: 작성 시간)
- 로그인(기본값: true)
사용자로부터 로그아웃 요구가 있을 때마다 LastValidTime을 현재 시각으로, Logged-In을 false로 업데이트합니다.로그인 요청이 있는 경우 Last Valid Time은 변경되지 않지만 Logged-In은 true로 설정됩니다.
JWT를 생성하면 페이로드에 JWT 생성 시간이 포함됩니다.서비스 승인 시 3가지 조건을 확인합니다.
- JWT는 유효합니까?
- JWT 페이로드 작성 시간이 User Last Valid Time보다 큰지 여부
- 사용자가 로그인하고 있습니까?
실제 시나리오를 살펴보겠습니다.
사용자 X에는 2개의 디바이스 A, B가 있습니다.그는 디바이스 A와 디바이스 B를 사용하여 오후 7시에 서버에 로그인했습니다(JWT의 유효기간은 12시간이라고 합니다).A와 B 모두 JWT가 생성되어 있습니다.시간 : 오후 7시
오후 9시에 그는 장치 B를 잃어버렸다.디바이스 A에서 즉시 로그아웃 합니다.즉, 데이터베이스 X 사용자 엔트리의 LastValidTime은 "ThatDate:9:00:xx:xxx"로, Logged-In은 "false"로 설정됩니다.
9시 30분에 Mr.도둑이 디바이스 B를 사용하여 로그인을 시도합니다.Logged-In이 false인 경우에도 데이터베이스를 체크하기 때문에 허용하지 않습니다.
오후 10시에 X씨는 디바이스 A에서 로그인합니다.이제 디바이스 A에는 JWT가 생성되고 시각은 pm 10이 됩니다.이제 데이터베이스 로그인 상태가 "true"로 설정되었습니다.
오후 10시 30분 미스터.도둑이 로그인을 시도합니다.Logged-In(로그인)이 True인 경우에도 마찬가지입니다.LastValidTime은 데이터베이스에서 오후 9시이지만 B의 JWT는 오후 7시로 시간을 생성했습니다.그래서 그는 그 서비스에 접속할 수 없다.따라서 1대의 디바이스 로그아웃 후 사용할 수 없는 패스워드를 가지고 있지 않은 디바이스B 를 사용하고, JWT 를 작성했습니다.
Keycloak과 같은 IAM 솔루션은 다음과 같은 Token Revocation 엔드포인트는 다음과 같습니다.
엔드 포인트/realms/{realm-name}/protocol/openid-connect/revoke
단순히 useragent(또는 사용자)를 로그아웃하려는 경우 엔드포인트도 호출할 수 있습니다(이 경우 토큰이 비활성화됩니다).다시 말씀드리지만 Keycloak의 경우, Reling Party는 엔드 포인트에 전화하기만 하면 됩니다.
/realms/{realm-name}/protocol/openid-connect/logout
다른 방법으로는 중요한 API 엔드포인트 전용 미들웨어 스크립트를 사용하는 것이 있습니다.
이 미들웨어 스크립트는 토큰이 관리자에 의해 비활성화되면 데이터베이스를 체크인합니다.
이 솔루션은 사용자의 접근을 즉시 완전히 차단할 필요가 없는 경우에 유용합니다.
이 예에서는 최종 사용자도 계정을 가지고 있다고 가정합니다.만약 그가 그렇지 않다면, 다른 접근 방식은 효과가 없을 것 같습니다.
JWT를 생성할 때 로그인 중인 계정과 연결된 데이터베이스에서 JWT를 유지합니다.즉, JWT에서 사용자에 대한 추가 정보를 추출할 수 있기 때문에 환경에 따라서는 문제가 없을 수도 있습니다.
이후 모든 요구에서는 사용하는 프레임워크(JWT가 유효한지 확인하는 프레임워크)와 함께 제공되는 표준 검증을 수행할 뿐만 아니라 사용자 ID 또는 다른 토큰(데이터베이스에 일치해야 함)도 포함됩니다.
로그아웃 시 cookie(사용하는 경우)를 삭제하고 데이터베이스에서 JWT(문자열)를 비활성화합니다.cookie를 클라이언트 측에서 삭제할 수 없는 경우, 적어도 로그아웃 프로세스에서는 토큰이 파기됩니다.
이 접근방식을 다른 고유 식별자(데이터베이스에 2개의 지속 항목이 있으며 프런트 엔드에서 사용 가능)와 결합하여 매우 탄력적인 세션을 발견했습니다.
모든 요청에 대해 데이터베이스를 호출하지 않고 수행하는 방법은 다음과 같습니다.
- 유효한 토큰의 해시 맵을 메모리 캐시 내에 보관합니다(예를 들어 크기가 제한된 LRU).
- 토큰 확인 시: 토큰이 캐시에 있는 경우 즉시 결과를 반환하십시오. 데이터베이스 쿼리는 필요하지 않습니다(대소문자).그렇지 않으면 전체 검사를 수행합니다(데이터베이스 쿼리, 사용자 상태 및 비활성 토큰 확인...).그런 다음 캐시를 업데이트합니다.
- 토큰을 무효화하는 경우: 토큰을 데이터베이스 블랙리스트에 추가한 후 캐시를 업데이트하고 필요한 경우 모든 서버에 신호를 보냅니다.
캐시는 LRU와 같이 크기가 제한되어야 합니다. 그렇지 않으면 메모리가 부족해질 수 있습니다.
저장소에서 토큰을 삭제하더라도 토큰은 여전히 유효하지만 악의적으로 사용될 가능성을 줄이기 위해 단기간 동안만 유효합니다.
해서 만들 수 요.deny-listing
저장소에서 토큰을 삭제하면 이 목록에 토큰을 추가할 수 있습니다.마이크로 서비스 서비스가 있는 경우 이 토큰을 사용하는 다른 모든 서비스는 이 목록을 확인하기 위해 로직을 추가해야 합니다.이것에 의해, 각 서버가 일원화된 데이터 구조를 확인할 필요가 있기 때문에, 인증이 일원화됩니다.
토큰 검증 때마다 DB를 조회하지 않으면 이 문제를 해결하기가 매우 어려울 것 같습니다.무효 토큰의 블랙리스트를 서버 측에 보관하는 방법도 생각할 수 있습니다.재기동시에 서버가 데이터베이스를 체크해 현재의 블랙리스트를 로드하는 것으로, 재기동시에 변경을 계속하기 위해서 변경이 발생할 때마다, 데이타베이스상에서 갱신할 필요가 있습니다.
그러나 서버 메모리(글로벌 변수 등)에 보관하고 있는 경우 여러 서버 간에 확장할 수 없기 때문에 공유 Redis 캐시에 보관해 둘 수 있습니다.이 경우 공유 Redis 캐시는 데이터를 어딘가에 보관하도록 셋업해야 합니다(데이터베이스).파일 시스템?)을 재기동할 필요가 있는 경우, 새로운 서버를 스핀업 할 때마다 Redis 캐시에 등록해야 합니다.
블랙리스트 대신 같은 솔루션을 사용하면 이 다른 답변에서 지적한 것처럼 세션별로 redis로 저장된 해시를 사용할 수 있습니다(다만 많은 사용자가 로그인하는 경우 더 효율적일지는 확실하지 않습니다).
너무 복잡하게 들리나요?나한테는 그래!
면책사항:나는 레디스를 써본 적이 없다.
언급URL : https://stackoverflow.com/questions/21978658/invalidating-json-web-tokens
'programing' 카테고리의 다른 글
특별한 HTML 엔티티가 포함된 문자열을 디코딩하는 올바른 방법은 무엇입니까? (0) | 2022.12.26 |
---|---|
python .replace() regex (0) | 2022.12.26 |
iFrame 내에서 요소 가져오기 (0) | 2022.12.06 |
MySQL Workbench - 쿼리 오류 문제를 진단하는 방법 (0) | 2022.12.06 |
치명적 오류: 최대 실행 시간 30초를 초과했습니다. (0) | 2022.12.06 |