카테고리 없음

로그인, 중복 로그인 방지

우주로그 2024. 8. 9.

1. 프로세스 개요

리프레시 토큰과 액세스 토큰을 함께 사용하여, 액세스 토큰의 유효 기간을 짧게 유지하면서도 사용자가 자주 로그인할 필요가 없도록 합니다. 리프레시 토큰은 서버에서 관리되며, 중복 로그인 방지와 함께 사용됩니다.

2. 프로세스 단계

1. 로그인 요청

  1. 사용자 로그인 요청:
    • 사용자가 자격 증명(예: 사용자명과 비밀번호)을 서버에 제출합니다.
  2. 자격 증명 확인:
    • 서버가 데이터베이스에서 자격 증명을 확인합니다.
  3. JWT(액세스 토큰) 및 리프레시 토큰 생성:
    • 자격 증명이 유효하다면, 서버는 액세스 토큰(JWT)과 리프레시 토큰을 생성합니다.
    • 액세스 토큰은 비교적 짧은 유효 기간(예: 15분)을 갖고, 리프레시 토큰은 더 긴 유효 기간(예: 7일)을 가집니다.
    • 리프레시 토큰은 고유한 식별자(JTI 또는 UUID)로 생성됩니다.
  4. Redis에 리프레시 토큰 저장:
    • Redis에 사용자 ID를 키로, 리프레시 토큰의 ID(JTI 또는 UUID)를 값으로 저장합니다. 이때 TTL(Time-to-Live)을 설정하여 리프레시 토큰이 만료되면 자동으로 삭제되도록 합니다.
     
  5. JWT 및 리프레시 토큰 반환:
    • 서버는 클라이언트에게 액세스 토큰(JWT)과 리프레시 토큰을 반환합니다. 클라이언트는 이후 요청에 액세스 토큰을 사용하고, 액세스 토큰이 만료되면 리프레시 토큰을 사용해 새 액세스 토큰을 요청합니다.
String refreshTokenId = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("user:" + userId + ":refresh", refreshTokenId, 7, TimeUnit.DAYS);

2. 인증된 요청 처리

  1. 요청 전송:
    • 클라이언트는 서버에 요청을 보낼 때 액세스 토큰(JWT)을 HTTP 헤더에 포함시켜 전송합니다.
  2. JWT 검증:
    • 서버는 JWT의 서명을 검증하고, 이 토큰이 유효한지 확인합니다.
  3. 요청 처리:
    • 액세스 토큰이 유효하면 요청을 정상 처리하고, 응답을 반환합니다.
  4. 액세스 토큰 만료:
    • 만약 액세스 토큰이 만료되었다면, 클라이언트는 리프레시 토큰을 사용해 새로운 액세스 토큰을 요청합니다.

3. 리프레시 토큰을 사용한 액세스 토큰 갱신

  1. 리프레시 토큰 요청:
    • 액세스 토큰이 만료된 후, 클라이언트는 리프레시 토큰을 서버에 제출하여 새로운 액세스 토큰을 요청합니다.
  2. Redis에서 리프레시 토큰 검증:
    • 서버는 Redis에서 해당 사용자 ID에 저장된 리프레시 토큰의 ID를 조회하고, 클라이언트가 제출한 리프레시 토큰의 ID와 비교합니다.
    • 리프레시 토큰이 유효하면, 서버는 새로운 액세스 토큰(JWT)을 발급합니다. 리프레시 토큰 자체는 그대로 유지되며, 기존의 만료 시간에 따라 만료됩니다.
     
  3. 새로운 액세스 토큰 반환:
    • 서버는 클라이언트에게 새로운 액세스 토큰을 반환합니다.
String storedRefreshTokenId = redisTemplate.opsForValue().get("user:" + userId + ":refresh");

if (storedRefreshTokenId != null && storedRefreshTokenId.equals(receivedRefreshTokenId)) {
    // 새 액세스 토큰 발급
} else {
    // 리프레시 토큰이 유효하지 않음 - 재로그인 요구
}

4. 중복 로그인 처리

  1. 새로운 로그인 발생:
    • 동일한 사용자가 다른 장치나 브라우저에서 새로운 로그인을 시도합니다.
  2. 기존 리프레시 토큰 무효화:
    • 새로운 로그인이 성공하면 Redis에 저장된 이전의 리프레시 토큰 ID를 덮어씁니다. 이전 리프레시 토큰은 더 이상 유효하지 않으며, 이를 사용하는 모든 요청은 거부됩니다.
     
  3. 새로운 JWT 및 리프레시 토큰 반환:
    • 서버는 클라이언트에게 새로운 JWT와 리프레시 토큰을 반환합니다.
String newRefreshTokenId = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("user:" + userId + ":refresh", newRefreshTokenId, 7, TimeUnit.DAYS);

  4. 기존 사용자 알림(SSE)

 

  • 서버가 클라이언트에게 실시간으로 이벤트를 전달할 수 있는 단방향 통신 방식입니다.
  • 웹소켓보다는 가볍고, 클라이언트가 서버로 연결을 유지하며 서버로부터 이벤트를 수신합니다.
  • 중복 로그인이 발생하면 서버에서 로그아웃 이벤트를 SSE로 클라이언트에게 전송할 수 있습니다.
  •  
@GetMapping("/events")
public SseEmitter handleSse() {
    SseEmitter emitter = new SseEmitter();
    // 비동기 작업이나 이벤트 발생 시 emitter.send()로 클라이언트에 데이터 전송
    return emitter;
}

 

5. 로그아웃 처리

  1. 로그아웃 요청:
    • 클라이언트가 로그아웃 요청을 서버에 전송합니다.
  2. Redis에서 리프레시 토큰 삭제:
    • 서버는 Redis에서 해당 사용자의 리프레시 토큰 정보를 삭제하여 세션을 무효화합니다.
     
  3. 응답 반환:
    • 서버는 클라이언트에게 로그아웃 성공 응답을 반환합니다.
redisTemplate.delete("user:" + userId + ":refresh");

6. 토큰 만료 처리

  1. 액세스 토큰 만료:
    • JWT는 자체적으로 만료 시간이 포함되어 있어, 만료된 액세스 토큰은 자동으로 유효성을 상실합니다.
  2. 리프레시 토큰 만료:
    • Redis에 저장된 리프레시 토큰은 TTL이 만료되면 자동으로 삭제됩니다.
  3. 재로그인 요청:
    • 리프레시 토큰이 만료되면, 클라이언트는 다시 로그인해야 하며, 이때 새로운 액세스 토큰과 리프레시 토큰이 발급됩니다.

댓글