#0. 들어가며
우리는 앞서 로그인 기능을 구현할 때 jwt토큰을 만들고 쿠키에 넣어 클라이언트에게 전달했다.(res)
바로 그 토큰을 Access Token이라고 할 수 있는데, 여기서 액세스 토큰을 탈취당했을 경우 보안 상 취약한 경우가 많다.
(해당 토큰이 있다면 실제 로그인 한 사람이 아니더라도 그 사람의 정보에 접근할 수 있기 때문에)
해당 문제를 해결하기 위해 여러가지 방법이 있겠지만,
액세스 토큰의 만료기간을 짧게두어 액세스 토큰을 탈취당하더라도 피해를 최소화하는 방법이 있다.
그렇다면 !
만료기간이 지날 때마다 액세스 토큰을 발급 받기 위해 로그인을 해야할까?
가능은 하지만 유저에게 불편함을 줄 수 있기 때문에 리프레시 토큰(Refresh Token)이란 것을 활용한다.
보안을 위해 액세스 토큰의 만료기간을 3분으로 설정했는데,
장문의 게시글을 쓰다가 3분이 지나버렸다면?
게시글을 쓰다 말고 로그인을 다시 해야하는 상황이 생긴다..
오늘은 토큰과 관련된 내용을 간략하게나마 정리해보자 !
#1. Access Token
2023.11.28 - [Node.js 도전기] - Node.js_쿠키, 세션, 토큰 (feat.인증, 로그인)
Node.js_쿠키, 세션, 토큰 (feat.인증, 로그인)
[알아보고자 하는 것] 1. 쿠키 2. 세션 3. jwt토큰 #0. 들어가며 우리가 쿠키, 세션, 토큰을 알아야하는 이유는 인증(Authenticate) 때문이 큰 이유 중 하나일 것이다. 인증이란 말이 거창하게 들리겠지만
geniusjun6.tistory.com
앞서서 쿠키, 세션, 토큰에 대해서 간략하게 정리한 바 있는데
액세스 토큰의 경우 발급한 토큰이 서비스에 접근할 수 있도록 인증해주는 역할인 토큰이라고 생각하면 된다.
(토큰의 역할을 의미한다.)
현재 진행중인 프로젝트에서는 JWT 형식으로 만든 토큰을 사용하고 있다.
내가 아닌 다른 사용자가 나의 액세스 토큰을 탈취하여 활용할 경우
계정에 대한 제어권이 탈취한 사람에게 있기 때문에 보안에 상당히 취약하다는 단점이 있다 !
이러한 문제를 해결하기 위해 앞서 말한 것과 같이
액세스 토큰의 만료기간을 짧게 설정하여 피해를 받더라도 피해 규모를 최소화 할 수 있는 노력을 한다.
// JWT 형식으로 액세스 토큰을 만드는 예
function createAccessToken(id) {
return jwt.sign({ id }, ACCESS_TOKEN_SECRET_KEY, { expiresIn: "5m" });
};
위 예시에서는 액세스 토큰의 만료기간을 5분으로 설정하였는데,
그렇다면 5분 마다 다시 로그인을 하지 않기 위해서 리프레시 토큰이란 것을 이용해 액세스 토큰을 재발급 할 수 있다.
#2. Refresh Token
리프레시 토큰은 액세스 토큰을 재발급하는 용도로만 사용한다 !
형식은 액세스 토큰과 마찬가지이겠지만 JWT로 할수도 UUID형식이 될수도 있다.
액세스 토큰을 재발급하기 위한 역할이라면 리프레시 토큰이라고 할 수 있다.
[리프레시 토큰의 작동 로직]
1) 클라이언트에게 처음 로그인 시 액세스 토큰(만료기간 5분), 리프레시 토큰(만료기간 7일)을 모두 발급한다.
2) 클라이언트가 서비스 이용하기 위해 서버로 요청을 보내면 액세스 토큰을 확인한다.
3) 액세스 토큰의 만료기간이 지났을 경우 리프레시 토큰을 확인한다.
4) 리프레시 토큰을 확인하여 액세스 토큰을 재발급 한다.
리프레시 토큰 안에도 유저의 정보들을 넣을 수도 있는데,
어떤 정보를 넣을 것인지는 서비스의 정책에 따라 다를 수 있다.
나는 아래 코드와 같이 구성해보았다.
const tokenStroages = {};
tokenStroages[refreshToken] = {
id,
ip: req.ip, // 요청한 Ip 확인
userAgent: req.headers['user-agent'], // 어디서 요청했는지?
}
리프레시 토큰을 이용해 요청한 사용자가 옳은 이용자인지 확인하기 위해서는 리프레시 토큰을 서버(DB)에 저장해야하는데,
우선 서버를 tokenStorages로 대체했다.
서버에 저장된 리프레시 토큰 안에는 유저의 id, 요청한 ip, 어떤 프로그램으로 요청을 했는지(브라우저 등)에 대한 정보를 담았다.
추후 리프레시 토큰을 가지고 다시 요청할 경우 DB에 저장된 내용과 요청한 사람을 비교하기 위해서 !
요청한 사람의 정보가 DB와 일치한다면 새로운 액세스 토큰을 발급한다.
// 새로운 액세스토큰 발급 예시
app.post('/tokens/refresh', async (req, res) => {
const { refreshToken } = req.cookies;
if (!refreshToken) {
return res.status(400).json({ message: "RT가 존재하지 않습니다." });
}
const payload = validateToke(refreshToken, REFRESH_TOKEN_SECRET_KEY)
if (!payload) {
return res.status(401).json({ message: "RT가 정상적이지 않습니다." });
};
const userInfo = tokenStroages[refreshToken];
if (!userInfo) {
return res.status(419).json({ message: "RT정보가 서버에 존재하지 않습니다." });
};
const newAccessToken = createAccessToken(userInfo.id);
res.cookie('accessToken', newAccessToken);
return res.status(200).json({ message: "새로운 액세스 토큰을 발급하였습니다." });
})
#2. 보안을 위한 추가 사항
액세스/리프레시 모두 쿠키를 통해서 전달하는 방식으로 구현했는데
쿠키를 이용할 경우 가장 우선적으로 탈취당하지 않게끔하는 것이 중요하지 않을까 !?
쿠키를 만들 때 보안과 관련된 옵션을 추가하여 전달할 수 있다.
바로
httpOnly 라는 옵션과 secure 라는 옵션을 이용하는 것이다.
httpOnly는 쿠키에 접근할 수 있는 것이 http프로토콜을 이용할 경우에만 접근이 가능하도록 설정하는 것인데,
쿠키는 자바스크립트로도 접근이 가능하여 쉽게 쿠키 값을 빼올 수 있다.
secure라는 옵션은 https로 보안 통신일 경우에만 쿠키를 확인할 수 있도록 하는 옵션이다.
이렇게 액세스 토큰과 리프레시 토큰을 간략하게나마 정리해보았는데,
아직은 100% 이해가 된 것 같지는 않아서 추가적으로 보완하거나 다른 포스팅으로 정리해볼 수 있을 것 같다.
아마 보안과 관련된 내용과 오늘 다뤘던 토큰에 대해서는 앞으로 더 공부해서 자세한 포스팅이 될 수 있기를 기대해본다 !
'Node.js 도전기' 카테고리의 다른 글
| Node.js_객체 지향 프로그래밍 (OOP) (1) | 2023.12.05 |
|---|---|
| Node.js_트랜잭션 (Transaction) (2) | 2023.12.04 |
| Node.js_회원가입 구현하기 (0) | 2023.11.30 |
| Node.js_로그인 기능 구현하기 (0) | 2023.11.29 |
| Node.js_쿠키, 세션, 토큰 (feat.인증, 로그인) (1) | 2023.11.28 |