728x90
반응형
Refresh Token
재사용 방지(Reuse Detection)는 Refresh Token
이 도난당해 악의적인 사용자가 이를 재사용하려는 경우를 감지하고 차단하는 기술입니다. 이를 구현하려면 아래의 메커니즘을 사용할 수 있습니다.
1. Refresh Token 저장소 사용
Refresh Token
의 상태를 서버에서 관리하여 사용 기록을 검증하는 방식입니다.
구현 흐름:
- Refresh Token 저장 및 관리:
Refresh Token
발급 시, 이를 서버의 데이터베이스(DB) 또는 캐시(Redis 등)에 저장합니다.- 저장 시 각 토큰에 고유한 식별자(UUID)와 사용자 정보, 만료 시간, 생성 시간을 기록합니다.
- 토큰 사용 검증:
- 클라이언트가
Refresh Token
을 전송할 때마다, 서버는 DB 또는 캐시에서 해당 토큰을 검색하여 유효성을 확인합니다. - 만약 같은
Refresh Token
이 두 번 이상 사용되었다면, 이를 도난 또는 재사용으로 간주합니다.
- 클라이언트가
- 재사용 방지:
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
을 발급하는 방식입니다.
구현 흐름:
- Refresh Token 교체:
- 클라이언트가
Refresh Token
으로 새로운Access Token
을 요청하면, 서버는 기존Refresh Token
을 폐기하고 새Refresh Token
을 발급합니다.
- 클라이언트가
- 교체된 토큰의 만료 처리:
- 기존
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를 기록하고, 이후 요청 시 동일한 정보인지 확인합니다.
구현 흐름:
- 클라이언트 정보 저장:
Refresh Token
발급 시 클라이언트의 IP 주소와 User-Agent를 기록합니다.
- 클라이언트 정보 검증:
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
반응형
'Programming' 카테고리의 다른 글
JWT에서 Refresh Token은 어떻게 넘겨주나? (2) | 2024.12.26 |
---|---|
HTTPS 통신에서 데이터가 탈취될 수 있을까? 실제 사례와 대응 방안 (0) | 2024.12.23 |
JWT 에서 토큰을 전달할때 Header의 Authorizaion 을 사용해야만 하는가? (0) | 2024.12.23 |
(V) Vlang 에서 느낌표(!)가 있는 곳에서 에러날때 해결하기 (0) | 2024.04.01 |
( ) vscode extention : v-analyzer 사용하기 (0) | 2024.03.27 |