JSON 웹 토큰 무효화


421

내가 작업하고있는 새로운 node.js 프로젝트의 경우 쿠키 기반 세션 접근 방식에서 전환하는 것에 대해 생각하고 있습니다. JSON 웹 토큰 (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});
    });
}

-

세션 저장소 접근 방식에 대한 로그 아웃 (또는 무효화)을 위해서는 지정된 토큰으로 KeyValueStore 데이터베이스를 업데이트해야합니다.

토큰 자체에는 일반적으로 키-값 저장소에 존재하는 정보가 포함되므로 이러한 메커니즘은 토큰 기반 접근 방식에 존재하지 않는 것 같습니다.


1
'express-jwt'패키지를 사용하는 경우 isRevoked옵션을 보거나 동일한 기능을 복제 할 수 있습니다. github.com/auth0/express-jwt#revoked-tokens
Signus

1
데이터베이스에서 사용자의 액세스 상태를 확인할 수 있도록 액세스 토큰에 짧은 만료 시간을 사용하고 만료 시간이 긴 새로 고침 토큰을 사용하십시오 (블랙리스트). auth0.com/blog/…
Rohmer

또 다른 옵션은 jwt 토큰을 생성하고 동일한 IP 주소에 대해 저장된 IP와 수신 요청을 확인하는 동안 페이로드에 IP 주소를 첨부하는 것입니다. 예 : nodeJs의 req.connection.remoteAddress 고객 당 고정 IP를 발행하지 않는 ISP 제공 업체가 있습니다. 클라이언트가 인터넷에 다시 연결하지 않으면 이것이 문제가되지 않을 것이라고 생각합니다.
Gihan Sandaru

답변:


392

나도이 질문에 대해 연구 해 왔으며 아래의 아이디어 중 완전한 해결책은 없지만 다른 사람들이 아이디어를 배제하거나 다른 아이디어를 제공하는 데 도움이 될 수 있습니다.

1) 단순히 클라이언트에서 토큰을 제거하십시오

분명히 이것은 서버 측 보안에는 아무런 영향을 미치지 않지만 토큰이 존재하지 않도록하여 공격자를 막을 수 있습니다 (즉, 로그 아웃하기 전에 토큰을 도난 했어야 함).

2) 토큰 블랙리스트 생성

초기 만료 날짜까지 유효하지 않은 토큰을 저장하고 들어오는 요청과 비교할 수 있습니다. 모든 요청에 ​​대해 데이터베이스를 터치해야하기 때문에 처음부터 완전히 토큰을 사용해야하는 이유를 무효화하는 것처럼 보입니다. 로그 아웃과 만료 시간 사이에있는 토큰 만 저장하면되기 때문에 스토리지 크기는 더 작을 수 있습니다 (이것은 직감이며 컨텍스트에 따라 달라집니다).

3) 토큰 만기 시간을 짧게 유지하고 자주 회전하십시오.

토큰 만료 시간을 충분히 짧은 간격으로 유지하고 실행중인 클라이언트가 추적을 유지하고 필요한 경우 업데이트를 요청하면 번호 1은 완전한 로그 아웃 시스템으로 효과적으로 작동합니다. 이 방법의 문제점은 클라이언트 코드가 닫히는 사이 (만료 간격을 설정하는 시간에 따라) 사용자가 로그인 상태를 유지할 수 없다는 것입니다.

비상시 계획

응급 상황이 발생했거나 사용자 토큰이 손상된 경우 사용자가 로그인 자격 증명을 사용하여 기본 사용자 조회 ID를 변경할 수 있습니다. 연관된 사용자를 더 이상 찾을 수 없으므로 모든 연관된 토큰이 유효하지 않게됩니다.

또한 마지막 로그인 날짜를 토큰과 함께 포함시키는 것이 좋습니다. 따라서 먼 시간이 지난 후에 다시 로그인 할 수 있습니다.

토큰을 사용한 공격과의 유사점 / 차이점에서이 게시물은 다음과 같은 질문을 해결합니다. https://github.com/dentarg/blog/blob/master/_posts/2014-01-07-angularjs-authentication-with-cookies -vs-token.markdown


3
탁월한 접근 방식. 내 직감은 3 가지를 모두 조합하거나 타이머가 아닌 모든 "n"요청 후에 새 토큰을 요청하는 것입니다. 우리는 메모리 내 객체 스토리지에 redis를 사용하고 있으며 사례 # 2에 이것을 쉽게 사용할 수 있으며 대기 시간이 줄어 듭니다.
Aaron Wagner

2
코딩 공포 게시물 은 다음과 같은 조언을 제공합니다. 세션 베어링 쿠키 (또는 토큰)는 짧게 유지하지만 사용자에게는 보이지 않습니다. 이는 # 3과 일치하는 것으로 보입니다. 내 자신의 직감 (아마도 더 전통적이기 때문에)은 토큰 (또는 해시)이 화이트리스트 세션 데이터베이스 (# 2와 유사)의 핵심 역할을하는 것입니다.
funseiki

8
이 기사는 잘 작성되었으며 2)위 의 정교한 버전입니다 . 개인적으로 잘 작동하지만 전통적인 세션 저장소와 크게 다르지 않습니다. 스토리지 요구 사항은 더 낮을 것으로 생각되지만 여전히 데이터베이스가 필요합니다. JWT의 가장 큰 매력은 세션에 데이터베이스를 전혀 사용하지 않는 것입니다.
Matt Way

211
사용자가 비밀번호를 변경할 때 토큰을 무효화하는 일반적인 방법은 비밀번호 해시로 토큰에 서명하는 것입니다. 따라서 비밀번호가 변경되면 이전 토큰이 자동으로 확인되지 않습니다. 사용자 레코드에 마지막 로그 아웃 시간을 포함시키고 마지막 로그 아웃 시간과 비밀번호 해시의 조합을 사용하여 토큰에 서명함으로써이를 로그 아웃하도록 확장 할 수 있습니다. 토큰 서명을 확인해야 할 때마다 DB 조회가 필요하지만 어쨌든 사용자를 찾는 것 같습니다.
트래비스 테리

4
블랙리스트는 메모리에 유지하여 효율적으로 만들 수 있으므로 무효화를 기록하고 만료 된 무효화를 제거하고 서버를 시작할 때만 읽을 수 있도록 DB를 치기 만하면됩니다. 로드 밸런싱 아키텍처에서 인 메모리 블랙리스트는 10 초와 같은 짧은 간격으로 DB를 폴링하여 무효화 된 토큰의 노출을 제한 할 수 있습니다. 이러한 접근 방식을 통해 서버는 요청 당 DB 액세스없이 요청을 계속 인증 할 수 있습니다.
Joe Lapp

86

위에 게시 된 아이디어는 훌륭하지만 기존 JWT를 모두 무효화하는 매우 간단하고 쉬운 방법은 단순히 비밀을 변경하는 것입니다.

서버가 JWT를 작성하고 비밀 (JWS)로 서명 한 후이를 클라이언트로 전송하면, 단순히 비밀을 변경하면 기존 토큰이 모두 무효화되고 모든 사용자가 이전 토큰이 갑자기 유효하지 않아 인증을 위해 새 토큰을 얻도록 요구합니다. 서버에.

실제 토큰 내용 (또는 조회 ID)을 수정하지 않아도됩니다.

분명히 이것은 토큰 만료마다 위의 솔루션 중 하나가 필요한 경우 (예 : 토큰 만료 시간이 짧거나 토큰 내부에 저장된 키 무효화) 기존의 모든 토큰이 만료되기를 원하는 긴급 상황에서만 작동합니다.


9
나는이 접근법이 이상적이지 않다고 생각합니다. 그것이 작동하고 확실히 간단하지만 공개 키를 사용하는 경우를 상상해보십시오. 단일 토큰을 무효화하려는 경우 언제든지 해당 키를 다시 만들고 싶지 않을 것입니다.
Signus

1
@KijanaWoodard, 공개 / 개인 키 쌍을 사용하여 RS256 알고리즘에서 효과적으로 비밀로 서명을 검증 할 수 있습니다. 여기에 표시된 예에서는 JWT를 무효화하기 위해 비밀을 변경하는 것을 언급합니다. 이것은 a) 서명과 일치하지 않는 가짜 pubkey를 소개하거나 b) 새로운 pubkey를 생성하여 수행 할 수 있습니다. 그러한 상황에서는 이상적이지 않습니다.
Signus

1
@Signus-도착 공개 키를 비밀로 사용하지는 않지만 다른 사람들은 공개 키를 사용하여 서명을 확인할 수 있습니다.
Kijana Woodard

8
이것은 매우 나쁜 해결책입니다. JWT를 사용하는 주된 이유는 상태 비 저장 및 확장 성 때문입니다. 동적 비밀을 사용하면 상태가 나타납니다. 서비스가 여러 노드에 클러스터 된 경우 새 토큰이 발행 될 때마다 비밀을 동기화해야합니다. 쿠키 나 인증을 다시 발명하는 데이터베이스 나 기타 외부 서비스에 비밀을 저장해야합니다.
Tuomas Toivonen

5
@TuomasToivonen, 비밀로 JWT에 서명하고 동일한 비밀로 JWT를 확인할 수 있어야합니다. 따라서 비밀을 보호 된 리소스에 저장해야합니다. 비밀이 손상되면이를 변경하고 해당 변경 사항을 각 노드에 분배해야합니다. 클러스터링 / 스케일링이있는 호스팅 공급자는 일반적으로 서비스에 비밀을 저장하여 이러한 비밀을 쉽고 안정적으로 배포 할 수 있습니다.
Rohmer

67

이것은 @mattway의 답변을 뒷받침하고 지원하는 긴 의견입니다.

주어진:

이 페이지에서 제안 된 다른 솔루션 중 일부는 모든 요청에 ​​대해 데이터 스토어에 도달하는 것을지지합니다. 모든 인증 요청의 유효성을 검사하기 위해 기본 데이터 저장소에 도달하면 다른 기존 토큰 인증 메커니즘 대신 JWT를 사용해야 할 이유가 줄어 듭니다. 매번 데이터 저장소에 갈 때 상태 비 저장 대신 JWT를 기본적으로 상태 저장으로 설정했습니다.

(사이트에 많은 양의 무단 요청이 수신되는 경우 JWT는 데이터 스토어에 도달하지 않고 요청을 거부합니다. 이는 도움이됩니다. 다른 유스 케이스도있을 수 있습니다.)

주어진:

상태 비 저장 JWT 에는 다음과 같은 중요한 사용 사례에 대한 즉각 적이고 안전한 지원 을 제공 할 수있는 방법이 없기 때문에 일반적인 실제 웹 앱에 대한 상태 비 저장 JWT 인증을 얻을 수 없습니다 .

사용자 계정이 삭제 / 차단 / 일시 중지되었습니다.

사용자 비밀번호가 변경되었습니다.

사용자의 역할 또는 권한이 변경되었습니다.

관리자가 사용자를 로그 아웃했습니다.

JWT 토큰의 다른 응용 프로그램 중요 데이터는 사이트 관리자가 변경합니다.

이 경우 토큰 만료를 기다릴 수 없습니다. 토큰 무효화는 즉시 발생해야합니다. 또한 악의적 인 의도가 있든 없든 클라이언트가 이전 토큰의 사본을 보관 및 사용하지 않도록 신뢰할 수 없습니다.

따라서 @ matt-way # 2 TokenBlackList의 답변이 JWT 기반 인증에 필요한 상태를 추가하는 가장 효율적인 방법이라고 생각합니다.

만료 날짜에 도달 할 때까지이 토큰을 보유하는 블랙리스트가 있습니다. 토큰 목록은 총 사용자 수에 비해 상당히 적습니다. 만료 될 때까지 블랙리스트에 올린 토큰 만 유지하면되기 때문입니다. 무효화 된 토큰을 redis, memcached 또는 키의 만료 시간 설정을 지원하는 다른 메모리 내 데이터 저장소에 넣어 구현합니다.

초기 JWT 인증을 통과하는 모든 인증 요청에 대해 인 메모리 DB를 계속 호출해야하지만 전체 사용자 세트에 대한 키를 저장할 필요는 없습니다. 특정 사이트에 큰 영향을 줄 수도 있고 아닐 수도 있습니다.


15
나는 당신의 대답에 동의하지 않습니다. 데이터베이스를 치는 것은 상태를 유지하지 않습니다. 백엔드에 상태를 저장합니다. JWT는 각 요청에서 데이터베이스에 도달 할 필요가 없도록 작성되지 않았습니다. JWT를 사용하는 모든 주요 애플리케이션은 데이터베이스에 의해 지원됩니다. JWT는 완전히 다른 문제를 해결합니다. en.wikipedia.org/wiki/Stateless_protocol
Julian

6
@Julian 이것에 대해 조금 자세히 설명해 주시겠습니까? JWT는 실제로 어떤 문제를 해결합니까?
zero01alpha

8
@ zero01alpha 인증 : JWT를 사용하는 가장 일반적인 시나리오입니다. 사용자가 로그인하면 이후의 각 요청에는 JWT가 포함되어 사용자가 해당 토큰으로 허용되는 경로, 서비스 및 리소스에 액세스 할 수 있습니다. 정보 교환 : JSON 웹 토큰은 당사자간에 정보를 안전하게 전송하는 좋은 방법입니다. JWT에 서명 할 수 있기 때문에 발신자가 자신이 누구인지 확인할 수 있습니다.
Julian

7
@Julian 나는 당신의 의견에 동의하지 않습니다 :) JWT는 문제를 해결하여 (서비스를 위해) 주어진 고객에게 권한 정보를 제공하는 중앙 엔티티에 액세스해야합니다. 따라서 서비스 A와 서비스 B는 클라이언트 X가 무언가를 할 수있는 권한이 있는지 없는지 확인하기 위해 일부 리소스에 액세스해야합니다. 서비스 A와 B는 X로부터 토큰을받습니다. 파티). 어쨌든 JWT는 특히 둘 이상의 서비스 제공자가 제어 할 때 시스템의 서비스간에 공유 상태를 피하는 데 도움이되는 도구입니다.
LIvanov

1
또한 jwt.io/introduction에서 If the JWT contains the necessary data, the need to query the database for certain operations may be reduced, though this may not always be the case.
giantas

43

사용자 모델에 jwt 버전 번호를 기록합니다. 새로운 jwt 토큰은 버전을 이것으로 설정합니다.

jwt의 유효성을 검사 할 때 사용자의 현재 jwt 버전과 버전 번호가 동일한 지 확인하십시오.

이전 jwt를 무효화하려면 언제든지 사용자 jwt 버전 번호를 충돌 시키십시오.


15
이것은 흥미로운 아이디어입니다. 토큰의 목적 중 일부는 상태가없고 데이터베이스를 사용할 필요가 없다는 것입니다. 하드 코딩 된 버전은 충돌하기 어렵고 데이터베이스의 버전 번호는 토큰 사용의 이점 중 일부를 무효화합니다.
Stephen Smith

13
아마 당신은 이미 토큰에 사용자 ID를 저장 한 다음 사용자가 존재하는지 / API 엔드 포인트에 액세스 할 권한이 있는지 확인하기 위해 데이터베이스를 쿼리합니다. 따라서 jwt 토큰 버전 번호와 사용자의 jwt 토큰 버전을 비교하여 추가 DB 쿼리를 수행하지 않습니다.
DaftMonk

5
아마도 데이터베이스에 전혀 영향을 미치지 않는 유효성 검사와 함께 토큰을 사용할 수있는 상황이 많기 때문에 아마 말해서는 안됩니다. 그러나이 경우 피하기 어렵다고 생각합니다.
DaftMonk

11
사용자가 여러 장치에서 로그인하면 어떻게됩니까? 하나의 토큰이 모두 사용되어야합니까, 아니면 로그인하면 이전의 모든 토큰이 무효화되어야합니까?
meeDamian

10
@SergioCorrea에 동의합니다. 이것은 JWT를 다른 토큰 인증 메커니즘과 거의 동일하게 만듭니다.
Ed J

40

아직 시도하지 않았으며 다른 답변 중 일부를 기반으로 많은 정보를 사용합니다. 여기서 복잡한 점은 사용자 정보 요청 당 서버 측 데이터 저장소 호출을 피하는 것입니다. 대부분의 다른 솔루션에는 사용자 세션 저장소에 대한 요청 당 db 조회가 필요합니다. 특정 시나리오에서는 문제가되지 않지만 이러한 호출을 피하고 필요한 서버 측 상태를 매우 작게 만들기 위해 만들어졌습니다. 모든 강제 무효화 기능을 제공하기 위해 서버 쪽 세션을 다시 만들지 만 소규모입니다. 그러나 당신이 그것을하고 싶다면 여기 요점이 있습니다 :

목표 :

  • 데이터 저장소 사용을 완화하십시오 (상태 비 저장).
  • 모든 사용자를 강제로 로그 아웃하는 기능
  • 언제든지 개인을 강제로 로그 아웃하는 기능.
  • 일정 시간이 지나면 비밀번호를 다시 입력해야합니다.
  • 여러 클라이언트와 작업 할 수있는 기능
  • 사용자가 특정 클라이언트에서 로그 아웃을 클릭하면 강제로 다시 로그인 할 수 있습니다. (사용자가 자리를 비운 후 누군가가 클라이언트 토큰을 "삭제 취소"하지 못하게하려면 추가 정보에 대한 설명을 참조하십시오)

해결책:

  • 수명이 길고 (몇 시간) 클라이언트에 저장된 새로 고침 토큰과 쌍을 이룬 단명 (5m 미만) 액세스 토큰을 사용하십시오 .
  • 모든 요청은 인증 또는 새로 고침 토큰 만료 날짜를 확인합니다.
  • 액세스 토큰이 만료되면 클라이언트는 새로 고침 토큰을 사용하여 액세스 토큰을 새로 고칩니다.
  • 새로 고침 토큰 확인 중에 서버는 작은 사용자 목록 블랙리스트를 확인합니다 (발견 된 경우 새로 고침 요청 거부).
  • 클라이언트에 유효한 (만료되지 않은) 새로 고침 또는 인증 토큰이없는 경우 다른 모든 요청이 거부되므로 사용자는 다시 로그인해야합니다.
  • 로그인 요청시 사용자 데이터 저장소에서 금지 여부를 확인하십시오.
  • 로그 아웃시-해당 사용자를 세션 블랙리스트에 추가하여 다시 로그인해야합니다. 다중 디바이스 환경에서 모든 디바이스에서 로그 아웃하지 않도록 추가 정보를 저장해야하지만 디바이스 필드를 디바이스에 추가하여 수행 할 수 있습니다. 사용자 블랙리스트.
  • x 시간이 지난 후 다시 입력을하려면 인증 토큰에서 마지막 로그인 날짜를 유지하고 요청 당 확인하십시오.
  • 모든 사용자를 강제로 로그 아웃하려면 토큰 해시 키를 재설정하십시오.

사용자 테이블에 금지 된 사용자 정보가 있다고 가정하면 서버에서 블랙리스트 (상태)를 유지해야합니다. 잘못된 세션 블랙리스트-사용자 ID 목록입니다. 이 블랙리스트는 새로 고침 토큰 요청 중에 만 확인됩니다. 새로 고침 토큰 TTL 인 한 항목을 계속 사용해야합니다. 새로 고침 토큰이 만료되면 사용자는 다시 로그인해야합니다.

단점 :

  • 새로 고침 토큰 요청에서 데이터 저장소 조회를 수행해야합니다.
  • 액세스 토큰의 TTL에 유효하지 않은 토큰이 계속 작동 할 수 있습니다.

장점 :

  • 원하는 기능을 제공합니다.
  • 정상 작동 상태에서는 토큰 새로 고침 작업이 사용자에게 표시되지 않습니다.
  • 모든 요청 대신 새로 고침 요청에 대한 데이터 저장소 조회 만 수행하면됩니다. 즉, 초당 1이 아닌 15 분마다 1이됩니다.
  • 서버 측 상태를 매우 작은 블랙리스트로 최소화합니다.

이 솔루션을 사용하면 서버가 15 분마다 DB 호출 만하 기 때문에 사용자 정보에는 적어도 reddis와 같은 메모리 데이터 저장소가 필요하지 않습니다. reddis를 사용하는 경우 유효 / 무효 세션 목록을 저장하면 매우 빠르고 간단한 솔루션이됩니다. 새로 고침 토큰이 필요 없습니다. 각 인증 토큰에는 세션 ID와 장치 ID가 있으며 생성시 reddis 테이블에 저장 될 수 있고 적절할 때 무효화 될 수 있습니다. 그런 다음 모든 요청에서 확인되고 유효하지 않은 경우 거부됩니다.


한 사람이 컴퓨터에서 일어나 다른 사람이 같은 컴퓨터를 사용할 수 있도록하는 시나리오는 어떻습니까? 첫 번째 사람은 로그 아웃하고 두 번째 사람을 즉시 로그 아웃 할 것으로 예상합니다. 두 번째 사람이 평균 사용자 인 경우 클라이언트는 토큰을 삭제하여 사용자를 쉽게 차단할 수 있습니다. 그러나 두 번째 사용자가 해킹 기술을 보유한 경우 사용자는 여전히 유효한 토큰을 복구하여 첫 번째 사용자로 인증 할 시간이 있습니다. 지체없이 즉시 토큰을 무효화 할 필요가없는 것처럼 보입니다.
Joe Lapp

5
또는 sesion / local 스토리지 또는 쿠키에서 JWT를 제거 할 수 있습니다.
Kamil Kiełczewski

1
감사합니다 @ Ashtonian. 광범위한 연구를 한 후 JWT를 포기했습니다. 비밀 키를 보호하기 위해 특별한 시간을 가지지 않거나 안전한 OAuth 구현에 위임하지 않으면 JWT는 일반 세션보다 훨씬 취약합니다. 전체 보고서보기 : by.jtl.xyz/2016/06/the-unspoken-vulnerability-of-jwts.html
Joe Lapp

2
블랙리스트를 허용하려면 새로 고침 토큰을 사용해야합니다. 훌륭한 설명 : auth0.com/blog/…
Rohmer

1
이것은 짧은 수명의 액세스 토큰과 블랙리스트에 올릴 수있는 오래 지속되는 새로 고침 토큰을 결합하기 때문에 가장 좋은 대답 인 것 같습니다. 로그 아웃시 클라이언트는 액세스 토큰을 삭제하여 두 번째 사용자가 액세스 할 수 없도록합니다 (로그 아웃 후 몇 분 동안 액세스 토큰이 계속 유효하더라도). @Joe Lapp에 따르면 해커 (두 번째 사용자)는 삭제 된 후에도 액세스 토큰을 얻습니다. 어떻게?
M3RS

14

내가 고려한 접근법은 항상 iatJWT에 (발행 된) 값을 갖는 것입니다. 그런 다음 사용자가 로그 아웃하면 해당 타임 스탬프를 사용자 레코드에 저장하십시오. JWT를 검증 할 때는 iat마지막으로 로그 아웃 한 타임 스탬프와 비교하십시오 . iat이전 버전 인 경우 유효하지 않습니다. 예, DB로 가야하지만 JWT가 유효하면 항상 사용자 레코드를 가져옵니다.

내가 볼 수있는 주요 단점은 여러 브라우저에 있거나 모바일 클라이언트가있는 경우 모든 세션에서 로그 아웃한다는 것입니다.

이것은 또한 시스템의 모든 JWT를 무효화하는 훌륭한 메커니즘이 될 수 있습니다. 확인의 일부는 마지막 유효 iat시간 의 글로벌 타임 스탬프에 대한 것일 수 있습니다 .


1
좋은 생각이야! "하나의 장치"문제를 해결하려면이 기능을 로그 아웃이 아닌 비상 기능으로 만드십시오. 이전에 발행 된 모든 토큰을 무효화하는 날짜를 사용자 레코드에 저장하십시오. 같은 것 token_valid_after또는 무언가. 대박!
OneHoopyFrood

1
안녕하세요 @OneHoopyFrood 아이디어를 더 잘 이해하는 데 도움이되는 예제 코드가 있습니까? 도와 주셔서 감사합니다!
alexventuraio 2016 년

2
다른 모든 제안 된 솔루션과 마찬가지로이 솔루션은 데이터베이스 조회가 필요합니다. 이는 조회가 가장 중요한 이유이기 때문에이 질문이 존재하는 이유입니다! (성능, 확장 성). 정상적인 상황에서는 사용자 데이터를 얻기 위해 DB 조회가 필요하지 않으며 이미 클라이언트에서 가져 왔습니다.
Rob Evans

9

나는 여기 조금 늦었지만 괜찮은 해결책이 있다고 생각합니다.

데이터베이스에 비밀번호가 마지막으로 변경된 날짜와 시간을 저장하는 "last_password_change"열이 있습니다. 또한 JWT에 발행 날짜 / 시간을 저장합니다. 토큰의 유효성을 검사 할 때 토큰이 발급 된 후 비밀번호가 변경되었는지, 아직 만료되지 않았더라도 토큰이 거부되었는지 확인합니다.


1
토큰을 어떻게 거부합니까? 간단한 예제 코드를 보여줄 수 있습니까?
alexventuraio 2016 년

1
if (jwt.issue_date < user.last_pw_change) { /* not valid, redirect to login */}
Vanuan

15
db 조회가 필요합니다!
Rob Evans

5

사용자의 문서 / 레코드에서 DB의 "last_key_used"필드를 가질 수 있습니다.

사용자가 사용자로 로그인하여 전달하면 새 임의 문자열을 생성하고 last_key_used 필드에 저장 한 후 토큰에 서명 할 때 페이로드에 추가하십시오.

사용자가 토큰을 사용하여 로그인 할 때 DB의 last_key_used를 확인하여 토큰의 토큰과 일치하는지 확인하십시오.

그런 다음 사용자가 예를 들어 로그 아웃하거나 토큰을 무효화하려는 경우 해당 "last_key_used"필드를 다른 임의의 값으로 변경하면 후속 검사가 실패하므로 사용자가 사용자로 로그인하여 다시 전달해야합니다.


이것은 내가 고려한 해결책이지만 다음과 같은 단점이 있습니다. (1) 각 요청에서 DB 조회를 수행하여 무작위를 확인하거나 (세션 대신 토큰을 사용하는 이유를 무효화) 또는 새로 고침 토큰이 만료 된 후 간헐적으로 확인 (사용자가 즉시 로그 아웃하거나 세션이 즉시 종료되지 않도록 방지) (2) 로그 아웃하면 모든 브라우저와 모든 장치 (일반적으로 예상되는 동작이 아님)에서 사용자를 로그 아웃합니다.
Joe Lapp

사용자가 로그 아웃 할 때 암호를 변경하거나 모든 장치에서 로그 아웃 할 때 (제공 한 경우)에만 키를 변경할 필요가 없습니다.
NickVarcha

3

이와 같은 메모리 내 목록 유지

user_id   revoke_tokens_issued_before
-------------------------------------
123       2018-07-02T15:55:33
567       2018-07-01T12:34:21

토큰이 일주일 안에 만료되면 그보다 오래된 레코드를 정리하거나 무시하십시오. 또한 각 사용자의 최신 레코드 만 유지하십시오. 목록의 크기는 토큰을 보관하는 기간과 사용자가 토큰을 철회하는 빈도에 따라 다릅니다. 테이블이 변경 될 때만 db를 사용하십시오. 응용 프로그램이 시작될 때 메모리에 테이블을로드하십시오.


2
대부분의 프로덕션 사이트는 둘 이상의 서버에서 실행되므로이 ​​솔루션은 작동하지 않습니다. Redis 또는 유사한 interpocess 캐시를 추가하면 시스템이 상당히 복잡해지며 솔루션보다 더 많은 문제가 발생합니다.
user2555515

@ user2555515 모든 서버를 데이터베이스와 동기화 할 수 있습니다. 매번 데이터베이스를 누르는 것이 당신의 선택입니다. 어떤 문제가 발생했는지 알 수 있습니다.
Eduardo

3

------------------------이 답변에 늦었지만 누군가에게 도움이 될 수 있습니다 .-------------- -----------

클라이언트 측 에서 가장 쉬운 방법은 브라우저 스토리지에서 토큰을 제거하는 것입니다.

그러나 노드 서버에서 토큰을 제거하려면 어떻게해야합니까?

JWT 패키지의 문제점은 토큰을 파기하는 방법이나 방법을 제공하지 않는다는 것입니다. 위에서 언급 한 JWT와 관련하여 다른 방법을 사용할 수 있습니다. 그러나 여기에 나는 jwt-redis와 함께 간다.

따라서 서버 측에서 토큰을 제거하기 위해 JWT 대신 jwt-redis 패키지를 사용할 수 있습니다

이 라이브러리 (jwt-redis)는 jsonwebtoken 라이브러리의 모든 기능을 하나의 중요한 추가 기능과 함께 완전히 반복합니다. Jwt-redis를 사용하면 토큰 레이블을 redis에 저장하여 유효성을 확인할 수 있습니다. redis에 토큰 레이블이 없으면 토큰이 유효하지 않습니다. jwt-redis에서 토큰을 파괴하려면 destroy 메소드가 있습니다.

그것은 이런 식으로 작동합니다 :

1) npm에서 jwt-redis 설치

2) 만들기-

var redis = require('redis');
var JWTR =  require('jwt-redis').default;
var redisClient = redis.createClient();
var jwtr = new JWTR(redisClient);

jwtr.sign(payload, secret)
    .then((token)=>{
            // your code
    })
    .catch((error)=>{
            // error handling
    });

3) 확인 -

jwtr.verify(token, secret);

4) 파괴 -

jwtr.destroy(token)

참고 : JWT에서 제공되는 것과 동일한 방식으로 토큰 로그인 중에 expiresIn을 제공 할 수 있습니다.

누군가에게 도움이 될 수 있습니다.


2

왜 jti 주장을 사용하고 (목록에) 사용자 레코드 필드로 목록에 저장하지 않습니까 (db에 따라 다르지만 최소한 쉼표로 구분 된 목록은 괜찮습니까)? 다른 사람들이 어쨌든 사용자 레코드를 얻고 싶다고 지적 했으므로 별도의 조회가 필요하지 않으므로이 방법을 사용하면 다른 클라이언트 인스턴스에 대해 여러 개의 유효한 토큰을 가질 수 있습니다 ( "로그 아웃 어디서나"목록을 비울 수 있음)


예, 이거요 사용자 테이블과 새 (세션) 테이블간에 일대 다 관계를 설정하여 jti 클레임과 함께 메타 데이터를 저장할 수 있습니다.
Peter Lada

2
  1. 토큰에 1 일의 만료 시간을 제공하십시오
  2. 일일 블랙리스트를 유지하십시오.
  3. 무효화 된 / 로그 아웃 토큰을 블랙리스트에 넣습니다.

토큰 유효성 검사의 경우 먼저 토큰 만료 시간을 확인한 다음 토큰이 만료되지 않은 경우 블랙리스트를 확인하십시오.

긴 세션 요구를 위해서는 토큰 만료 시간을 연장하기위한 메커니즘이 있어야합니다.


4
블랙리스트에 토큰을 넣고 무국적 상태가 됨
Kerem Baydoğan

2

파티에 늦게, 약간의 연구 끝에 내 2 센트가 제공됩니다. 로그 아웃하는 동안 다음과 같은 일이 발생하는지 확인하십시오.

클라이언트 스토리지 / 세션 지우기

로그인 또는 로그 아웃이 발생할 때마다 사용자 테이블의 마지막 로그인 날짜-시간 및 로그 아웃 날짜-시간을 업데이트하십시오. 따라서 로그인 날짜 시간은 항상 로그 아웃보다 커야합니다 (또는 현재 상태가 로그인 상태이고 아직 로그 아웃되지 않은 경우 로그 아웃 날짜를 널로 유지)

블랙리스트 추가 테이블을 유지하고 정기적으로 제거하는 것보다 훨씬 간단합니다. 여러 장치를 지원하려면 OS 또는 클라이언트 세부 정보와 같은 몇 가지 추가 세부 정보가있는 로그 아웃 날짜 및 로그 아웃을 유지하기 위해 추가 테이블이 필요합니다.


2

사용자 당 고유 한 문자열과 함께 해시되는 전역 문자열

JWT 비밀 부분의 역할을하여 개인 및 글로벌 토큰 무효화를 모두 허용합니다. 요청 인증 중 DB 조회 / 읽기 비용으로 최대의 유연성. 거의 변경되지 않기 때문에 캐시하기도 쉽습니다.

예를 들면 다음과 같습니다.

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 비트 비밀을 처리하는지 확실하지 않음)


1
좀 더 자세한 내용으로 충분합니다
giantas

2
@ giantas, 마크가 의미하는 것이 서명 부분이라고 생각합니다. JWT에 서명하기 위해 단일 키만 사용하는 대신 각 클라이언트에 고유 한 키를 결합하십시오. 따라서 사용자의 모든 세션을 무효화하려면 해당 사용자의 키를 변경하고 시스템의 모든 세션을 무효화하려면 해당 글로벌 단일 키만 변경하십시오.
Tommy Aria Pradana

1

나는 다음과 같이했다 :

  1. 를 생성 한 unique hash다음 redisJWT 에 저장하십시오 . 이것은 세션 이라고 할 수 있습니다
    • 또한 특정 JWT요청한 수를 저장합니다 . jwt가 서버로 전송 될 때마다 요청이 정수로 증가합니다 . (선택 사항)

따라서 사용자가 로그인하면 고유 한 해시가 생성되어 redis에 저장되어 JWT에 주입됩니다 .

사용자가 보호 된 엔드 포인트를 방문하려고하면 JWT 에서 고유 한 세션 해시를 가져와 redis를 쿼리하여 일치하는지 확인합니다.

이를 확장하고 JWT 를 더욱 안전하게 만들 수 있습니다. 방법은 다음과 같습니다.

특정 JWT 가 만든 모든 X 요청은 새로운 고유 세션을 생성하여 JWT에 저장 한 다음 이전 세션 을 블랙리스트에 추가합니다.

이것은 JWT 가 끊임없이 변화하고 있으며 오래된 JWT 가 해킹 당하거나 도난당하는 것을 막습니다.


1
토큰에 새 해시를 주입하는 대신 토큰 자체를 해시하고 해당 값을 redis에 저장할 수 있습니다.
Frug

또한 JWT에서 확인 aud하고 jti청구 하십시오 . 올바른 길을 가고 있습니다.
Peter Lada

1

사용자 토큰을 취소하려면 DB에서 발급 된 모든 토큰을 추적하고 세션과 같은 테이블에서 유효한 토큰이 있는지 확인할 수 있습니다. 단점은 모든 요청에 ​​대해 DB를 공격한다는 것입니다.

나는 그것을 시도하지 않았지만 DB 적중을 최소로 유지하면서 토큰 해지를 허용하는 다음 방법을 제안합니다.

데이터베이스 점검 비율을 낮추려면, 결정 론적 연관성 (예를 들어, 사용자 ID의 첫 번째 숫자로 10 개의 그룹)에 따라 발행 된 모든 JWT 토큰을 X 그룹으로 나누십시오.

각 JWT 토큰에는 그룹 ID와 토큰 작성시 작성된 시간 소인이 있습니다. 예를 들어{ "group_id": 1, "timestamp": 1551861473716 }

서버는 모든 그룹 ID를 메모리에 보유하고 각 그룹에는 해당 그룹에 속한 사용자의 마지막 로그 아웃 이벤트가 발생한 시간을 나타내는 타임 스탬프가 있습니다. 예를 들어{ "group1": 1551861473714, "group2": 1551861487293, ... }

이전 그룹 타임 스탬프가있는 JWT 토큰이있는 요청은 유효성 (DB 적중)을 검사하고 유효하면 클라이언트가 나중에 사용할 수 있도록 새로운 타임 스탬프가있는 새 JWT 토큰이 발행됩니다. 토큰의 그룹 타임 스탬프가 최신이면 JWT (No DB hit)를 신뢰합니다.

그래서-

  1. 토큰에 이전 그룹 타임 스탬프가있는 경우에만 DB를 사용하여 JWT 토큰의 유효성을 검사하지만 사용자 그룹의 누군가가 로그 아웃 할 때까지 향후 요청의 유효성이 검사되지 않습니다.
  2. 그룹을 사용하여 타임 스탬프 변경 횟수를 제한합니다 (내일이없는 것처럼 로그인 및 로그 아웃하는 사용자가 있으므로 모두 대신 제한된 수의 사용자에게만 영향을 미침)
  3. 메모리에 보유 된 타임 스탬프의 양을 제한하기 위해 그룹 수를 제한합니다
  4. 토큰을 무효화하는 것은 쉬운 일입니다. 세션 테이블에서 토큰을 제거하고 사용자 그룹에 대한 새 타임 스탬프를 생성하십시오.

동일한 목록을 메모리 (c #의 응용 프로그램)에 보관할 수 있으며 각 요청에 대해 db를 칠 필요가 없습니다. 목록은 응용 프로그램 시작시 db에서로드 할 수 있습니다
dvdmn

1

"모든 장치에서 로그 아웃"옵션이 허용되는 경우 (대부분의 경우) :

  • 토큰 버전 필드를 사용자 레코드에 추가하십시오.
  • 이 필드의 값을 JWT에 저장된 클레임에 추가하십시오.
  • 사용자가 로그 아웃 할 때마다 버전을 늘리십시오.
  • 토큰의 유효성을 검사 할 때 버전 클레임을 사용자 레코드에 저장된 버전과 비교하고 동일하지 않은 경우 거부하십시오.

어쨌든 대부분의 경우 사용자 레코드를 가져 오기위한 db 여행이 필요하므로 유효성 검사 프로세스에 많은 오버 헤드가 발생하지 않습니다. 블랙리스트를 유지 관리하는 것과 달리 조인 또는 별도의 호출을 사용해야하거나 오래된 레코드를 정리해야하기 때문에 DB로드가 중요한 경우.


0

JWT를 사용할 때 모든 장치 기능에서 로그 아웃을 제공해야하는 경우 대답하겠습니다. 이 접근 방식은 각 요청에 대해 데이터베이스 조회를 사용합니다. 서버 충돌이 발생하더라도 지속성 보안 상태가 필요하기 때문입니다. 사용자 테이블에는 두 개의 열이 있습니다

  1. LastValidTime (기본값 : 생성 시간)
  2. 로그인 (기본값 : true)

사용자의 로그 아웃 요청이있을 때마다 LastValidTime을 현재 시간으로 업데이트하고 Logged-In을 false로 업데이트합니다. 로그인 요청이 있으면 LastValidTime을 변경하지 않지만 Logged-In은 true로 설정됩니다.

JWT를 만들 때 페이로드에 JWT 작성 시간이 있습니다. 서비스를 승인하면 3 가지 조건을 확인합니다.

  1. JWT가 유효합니까
  2. JWT 페이로드 작성 시간이 사용자 LastValidTime보다 큽니까?
  3. 사용자 로그인

실용적인 시나리오를 보자.

사용자 X에는 두 개의 장치 A, B가 있습니다. 오후 7시에 장치 A와 장치 B를 사용하여 서버에 로그인했습니다 (JWT 만료 시간이 12 시간이라고 말하십시오). A와 B 모두 createdTime : 7pm의 JWT를 갖습니다.

오후 9시에 그는 장치 B를 잃었습니다. 그는 즉시 장치 A에서 로그 아웃합니다. 즉, 이제 데이터베이스 X 사용자 항목의 LastValidTime은 "ThatDate : 9 : 00 : xx : xxx"이고 로그인은 "false"입니다.

9:30에 Mr.Thief는 장치 B를 사용하여 로그인을 시도합니다. Logged-In이 잘못된 경우에도 데이터베이스를 확인하여 허용하지 않습니다.

오후 10시에 Mr.X는 자신의 장치 A에서 로그인합니다. 이제 장치 A는 생성 시간이 JPM 인 오후 10시입니다. 이제 데이터베이스 로그인이 "true"로 설정되었습니다.

오후 10시 30 분에 Mr.Thief는 로그인을 시도합니다. 로그인은 사실이지만. LastValidTime은 데이터베이스에서 오후 9시이지만 B의 JWT는 시간을 오후 7 시로 만들었습니다. 그래서 그는 서비스에 액세스 할 수 없습니다. 따라서 암호없이 장치 B를 사용하면 한 장치 로그 아웃 후에 이미 작성된 JWT를 사용할 수 없습니다.


0

Keycloak과 같은 IAM 솔루션 (내가 작업 한)은 다음과 같은 토큰 해지 엔드 포인트를 제공합니다.

토큰 해지 엔드 포인트 /realms/{realm-name}/protocol/openid-connect/revoke

사용자 에이전트 (또는 사용자)를 단순히 로그 아웃하려는 경우 엔드 포인트도 호출 할 수 있습니다 (이것은 단순히 토큰을 무효화합니다). Keycloak의 경우 신뢰 당사자는 끝점을 호출하면됩니다.

/realms/{realm-name}/protocol/openid-connect/logout

더 배우고 싶다면 링크


-1

모든 토큰 확인시 DB 조회가 없으면 해결하기가 정말 어려워 보입니다. 내가 생각할 수있는 대안은 무효화 된 토큰의 블랙리스트를 서버 측에 유지하는 것입니다. 서버가 재시작시 데이터베이스를 검사하여 현재 블랙리스트를로드하도록하여 변경 사항이 발생할 때마다 데이터베이스에서 업데이트되어야합니다.

그러나 서버 메모리 (전역 변수)에 보관하면 둘 이상의 서버를 사용하는 경우 여러 서버에서 확장 할 수 없으므로 공유 Redis 캐시에 보관할 수 있습니다. 다시 시작해야 할 경우를 대비하여 데이터를 어딘가에 유지하도록 설정하고 (데이터베이스? 파일 시스템?) 새 서버가 회전 할 때마다 Redis 캐시를 구독해야합니다.

동일한 솔루션을 사용하는 블랙리스트의 대안으로, 다른 답변이 지적한 것처럼 세션 당 redis에 저장된 해시로 할 수 있습니다 (많은 사용자가 로그인하는 것이 더 효율적일지는 확실하지 않습니다).

너무 복잡하게 들립니까? 그것은 나에게한다!

면책 조항 : 나는 Redis를 사용하지 않았습니다.


-1

axios 또는 유사한 promise 기반 http 요청 lib를 사용하는 경우 .then()부품 내부의 프론트 엔드에서 토큰을 간단히 제거 할 수 있습니다 . 사용자가이 함수를 실행 한 후 응답 .then () 부분에서 시작됩니다 (서버 엔드 포인트의 결과 코드는 200이어야합니다). 사용자가 데이터를 검색하는 동안이 경로를 클릭 한 후 데이터베이스 필드 user_enabled가 false이면 토큰 삭제가 트리거되고 사용자는 즉시 로그 오프되고 보호 된 경로 / 페이지에 대한 액세스가 중지됩니다. 사용자가 영구적으로 로그온되어있는 동안 토큰이 만료 될 때까지 기다릴 필요가 없습니다.

function searchForData() {   // front-end js function, user searches for the data
    // protected route, token that is sent along http request for verification
    var validToken = 'Bearer ' + whereYouStoredToken; // token stored in the browser 

    // route will trigger destroying token when user clicks and executes this func
    axios.post('/my-data', {headers: {'Authorization': validToken}})
     .then((response) => {
   // If Admin set user_enabled in the db as false, we destroy token in the browser localStorage
       if (response.data.user_enabled === false) {  // user_enabled is field in the db
           window.localStorage.clear();  // we destroy token and other credentials
       }  
    });
     .catch((e) => {
       console.log(e);
    });
}

-3

사용자 테이블에 토큰을 저장하고 사용자 로그인시 새 토큰을 업데이트하고 인증이 사용자 현재 jwt와 같을 때.

이것이 최선의 해결책은 아니지만 저에게 효과적이라고 생각합니다.


2
물론 최고는 아닙니다! db에 액세스 할 수있는 사람은 누구나 쉽게 사용자를 가장 할 수 있습니다.
user2555515

1
@ user2555515이 솔루션은 데이터베이스에 저장된 토큰과 마찬가지로 데이터베이스에 저장된 토큰이 암호화 된 경우 제대로 작동합니다. Stateless JWTStateful JWT(세션과 매우 유사한) 차이점이 있습니다 . Stateful JWT토큰 허용 목록을 유지 관리하면 이점을 얻을 수 있습니다.
TheDarkIn1978
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.