본문 바로가기
Programming

Refresh Token 재사용 방지(Reuse Detection)

by 가우리언 2024. 12. 26.
728x90
반응형

 

 

Refresh Token 재사용 방지(Reuse Detection)는 Refresh Token이 도난당해 악의적인 사용자가 이를 재사용하려는 경우를 감지하고 차단하는 기술입니다. 이를 구현하려면 아래의 메커니즘을 사용할 수 있습니다.


1. Refresh Token 저장소 사용

Refresh Token의 상태를 서버에서 관리하여 사용 기록을 검증하는 방식입니다.

구현 흐름:

  1. Refresh Token 저장 및 관리:
    • Refresh Token 발급 시, 이를 서버의 데이터베이스(DB) 또는 캐시(Redis 등)에 저장합니다.
    • 저장 시 각 토큰에 고유한 식별자(UUID)와 사용자 정보, 만료 시간, 생성 시간을 기록합니다.
  2. 토큰 사용 검증:
    • 클라이언트가 Refresh Token을 전송할 때마다, 서버는 DB 또는 캐시에서 해당 토큰을 검색하여 유효성을 확인합니다.
    • 만약 같은 Refresh Token이 두 번 이상 사용되었다면, 이를 도난 또는 재사용으로 간주합니다.
  3. 재사용 방지:
    • Refresh Token이 재사용된 경우, 해당 토큰과 관련된 세션(사용자)을 강제 로그아웃하거나 모든 토큰을 무효화합니다.

구현 예시:

(1) 데이터베이스 테이블 예시:

CREATE TABLE refresh_tokens (
    token_id VARCHAR(255) PRIMARY KEY,
    user_id INT NOT NULL,
    token_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMP NOT NULL,
    expires_at TIMESTAMP NOT NULL
);

(2) 발급 시 저장:

const tokenId = generateUUID();
const refreshToken = signToken({ tokenId, userId });
await db.query(
    'INSERT INTO refresh_tokens (token_id, user_id, token_hash, created_at, expires_at) VALUES (?, ?, ?, ?, ?)',
    [tokenId, userId, hashToken(refreshToken), new Date(), expiresAt]
);

(3) 사용 시 검증:

const { tokenId } = verifyToken(refreshToken);
const storedToken = await db.query('SELECT * FROM refresh_tokens WHERE token_id = ?', [tokenId]);

if (!storedToken || storedToken.token_hash !== hashToken(refreshToken)) {
    throw new Error('Invalid or reused token');
}

// Refresh Token 재사용: 기존 세션 차단
await db.query('DELETE FROM refresh_tokens WHERE token_id = ?', [tokenId]);

728x90

2. 단회성 Refresh Token 사용

Refresh Token을 한 번 사용한 후 무효화하고, 새 Refresh Token을 발급하는 방식입니다.

구현 흐름:

  1. Refresh Token 교체:
    • 클라이언트가 Refresh Token으로 새로운 Access Token을 요청하면, 서버는 기존 Refresh Token을 폐기하고 새 Refresh Token을 발급합니다.
  2. 교체된 토큰의 만료 처리:
    • 기존 Refresh Token을 더 이상 유효하지 않도록 서버에서 관리합니다.

구현 예시:

토큰 사용 후 교체:

app.post('/refresh-token', async (req, res) => {
    const { refreshToken } = req.body;
    const { tokenId, userId } = verifyToken(refreshToken);

    // 기존 토큰 확인
    const storedToken = await db.query('SELECT * FROM refresh_tokens WHERE token_id = ?', [tokenId]);
    if (!storedToken) {
        return res.status(401).json({ message: 'Invalid token' });
    }

    // 기존 토큰 폐기
    await db.query('DELETE FROM refresh_tokens WHERE token_id = ?', [tokenId]);

    // 새로운 Refresh Token 발급
    const newTokenId = generateUUID();
    const newRefreshToken = signToken({ tokenId: newTokenId, userId });
    await db.query(
        'INSERT INTO refresh_tokens (token_id, user_id, token_hash, created_at, expires_at) VALUES (?, ?, ?, ?, ?)',
        [newTokenId, userId, hashToken(newRefreshToken), new Date(), expiresAt]
    );

    res.json({ accessToken: newAccessToken, refreshToken: newRefreshToken });
});

3. 토큰 재사용 감지 (IP/User-Agent 확인)

Refresh Token 사용 시 클라이언트의 IP 주소와 User-Agent를 기록하고, 이후 요청 시 동일한 정보인지 확인합니다.

구현 흐름:

  1. 클라이언트 정보 저장:
    • Refresh Token 발급 시 클라이언트의 IP 주소와 User-Agent를 기록합니다.
  2. 클라이언트 정보 검증:
    • Refresh Token 사용 요청 시 기록된 정보와 비교합니다.
    • 다를 경우 도난 가능성이 있으므로 차단합니다.

구현 예시:

app.post('/refresh-token', async (req, res) => {
    const { refreshToken } = req.body;
    const { tokenId } = verifyToken(refreshToken);

    const storedToken = await db.query('SELECT * FROM refresh_tokens WHERE token_id = ?', [tokenId]);
    if (!storedToken) {
        return res.status(401).json({ message: 'Invalid token' });
    }

    // IP/User-Agent 비교
    const currentIp = req.ip;
    const currentUserAgent = req.headers['user-agent'];
    if (storedToken.ip !== currentIp || storedToken.user_agent !== currentUserAgent) {
        await db.query('DELETE FROM refresh_tokens WHERE token_id = ?', [tokenId]);
        return res.status(401).json({ message: 'Token reuse detected' });
    }

    // 새 토큰 발급...
});

4. 고급: Refresh Token Throttling

토큰 요청 시도를 제한하여 공격을 방어합니다. 예를 들어, 짧은 시간 동안 여러 번 요청이 발생하면 차단합니다.

구현:

  • Redis를 사용해 IP 주소별 요청 횟수를 제한하거나, 사용자 계정별 요청 수를 기록하여 임계값 초과 시 차단합니다.

추천 조합

  • 단회성 토큰 교체 방식과 클라이언트 정보(IP/User-Agent) 검증을 함께 사용.
  • Throttle(속도 제한)로그 분석을 통해 이상 활동을 감지.

이러한 방식으로 구현하면, Refresh Token 재사용을 효과적으로 방지할 수 있습니다.

728x90
반응형