개요
안녕하세요 웹 프론트엔드 개발자 설탕시럽입니다.
엄브렐라 프로젝트에서 Session 방식과 JWT Token 인증 방식 로그인이 무엇이고, 왜 JWT Token 인증방식으로 로그인 구현했는지와 코드를 함께 공유하려고 합니다.
로그인
시스템에서 사용자를 식별하여, 시스템에 접근하거나 동작을 수행하는 것을 제어하고 기록하기 위한 보안절차.
웹에서 HTTP를 활용한 데이터는 stateless 특징상 누가 요청하는지 시스템에서 알 수 없다. 로그인이라는 절차를 통해 파일 및 프로그램 등의 컴퓨터 시스템의 접근 권한을 관리할 수 있기 때문에 보안에서 중요한 역할을 한다.
따라서 로그인을 하지 않았거나 권한이 없는 유저에 대해 접근 및 동작제어를 확실하게 해야 한다. 권한 없는 자원에 대해 접근이 불가능한 구조를 설계하고, 애초에 권한/페이지가 있는지를 모르게 하는 것이 중요하다.
세션
사용자의 로그인 이후 로그아웃 혹은 로그인 만료까지의 기간
세션 방식 로그인
사용자 로그인이 유효한 시간 동안 서버에 세션 아이디를 기록해 두고 인증에 사용하는 방식.
각 클라이언트의 고유세션 ID를 부여하며, 서버에서 저장 및 관리한다.
쿠키
서버가 사용자의 웹 브라우저에 전송하는 작은 데이터 조각.
이름, 값, 만료일, 경로정보로 구성되며 사용자의 클라이언트에 저장하는 기록 데이터 파일이다.
쿠키를 활용해서 stateless인 HTTP의 상태정보를 기억하며 정보를 참조, 재사용한다.
세션 로그인 동작 및 특징
이번 프로젝트에서는 다음으로 설명드릴 JWT 토큰 방식으로 로그인을 구현했지만, 함께 고려한 세션 방식 로그인에 대해 공유하려고 합니다.
1. 유저 진입, 이후 요청의 세션이 유효한지 서버에서 검증.
2. 세션이 유효하지 않다면 로그인을 진행시켜서 세션을 갱신.
3. 세션이 유효하다면 해당 세션 ID로 가진 권한 내에서 서버에 요청.
특징
- 세션 ID를 서버에 저장 및 관리하다 보니 서버의 비용과 부담이 증가하는 방식
- 다른 방식에 비해 프론트엔드 측면에서 복잡도가 낮다.
세션 로그인 방식을 선택하지 않은 이유
- 서버의 확장성을 고려했다. 이후 화상회의 기능을 고려해보고 있는데 그 과정에서 서버가 수평 확장될 수 있기 때문이다. 또한 BFF 서버와 화상회의 서버 도입까지 고려하고 있는데 서버가 늘어날 때 세션보다 토큰 인증 방식이 비교적 자유롭고 높기에 높은 확장성을 가진다고 알고 있어서 세션을 선택하지 않았다.
- 이번 프로젝트에서 학생 개발자이지만, 최대한 보안적인 부분을 고려한 프로젝트를 만들고 싶었다. 대표적으로 CSRF와 XSS 방식에 대한 대비를 준비했는데 (이 부분은 JWT 토큰 인증 방식 포스트에서 더 자세히 다룰 예정), 백엔드 개발자와 얘기해 보면서 세션방식 보다 토큰 인증 방식에서 로직을 정리하고 방법을 정리할 수 있었다.
- 의외의 이유일지 모르지만, JWT 토큰방식이 프론트엔드 로직이 더 복잡하다고 들었다. 세션방식은 프론트엔드에서 고려해야 될 부분이 적어서 어차피 둘 다 공부할 겸, 할 일도 많은 JWT 토큰방식을 선정했다.
JWT
- JSON Web Token, RFC 규격에 정의되어 있는 토큰을 생성하는 기술
- 암호화 규칙과 토큰 타입을 정의한 Header, 데이터를 담고 있는 Payload, 암호화를 위한 Signature를 포함하고 있다.
Login을 성공하면 백엔드로부터 JWT토큰을 부여받고, 부여받은 토큰을 프론트엔드에서 이후 통신을 할 때 같이 전송한다. 간단하지만 프로젝트에서 작성한 JWT Token 관련 프론트엔드 코드는 아래와 같다
const onSubmit:SubmitHandler<FieldValues> = ({email, password}) => {
const UserData = {
"email" : email,
"password" : password,
}
axios.post(`${API_URL}/login`, UserData, {
headers:{ "Content-Type": `application/json`}
})
.then(onLoginSuccess)
.catch(onError);
}
const onLoginSuccess = (response : AxiosResponse) => {
...
const access_token = response.headers.authorization;
axios.defaults.headers.common['Authorization'] = `Bearer ${access_token}`;
...
}
CSRF와 XSS 공격 대비하기
CSRF?
사이트 간 요청 위조, 크로스 사이트 요청 위조, 특정 웹사이트가 사용자의 웹 브라우저를 신용한 상태를 이용해서 권한을 동요해서 가짜 요청을 서버에 전송하는 방식
XSS?
크로스 사이트 스크립팅, 웹 애플리케이션이 사용자로부터 입력받은 값을 제대로 검사하지 않고 사용되는 경우 공격하려는 사이트에 스크립트를 넣어 쿠키나 세션 토큰 등의 민감한 정보를 탈취하는 방식
Refresh Token
위 두 가지 공격을 대비하기 위해서 Client에 Access_Token 만 할당하는 방식으로는 대비할 수 없습니다. Access_Token이 탈취되는 것 만으로 사용자의 권한을 얻기 때문에 Refresh Token도 할당해서 두 가지 조건을 충족할 때 권한을 갖게 설정합니다. Access_Token과 같은 방식으로 주는 게 아닌, httponly secure cookie로 할당해서 client에서는 확인할 수 없지만 server에서만 확인하는 방식을 통해 두 가지 공격을 대비했습니다.
XSS 방식에 대해서는 프론트엔드에서 입력받는 데이터에 검증을 1차적으로 거치게 되며, 이 부분은 로그인 기능 이후 게시판을 만들 때 좀 집중적으로 다뤄보기로 했습니다. 혹여나 XSS 방식으로 Access Token을 탈취해서 Server로 접근을 시도해도 Refresh Token에 접근할 수 없기 때문에 어느 정도 방지해 주고 CSRF공격 방식에 대해서는 아래 그림과 같은 방식으로 공격을 막을 수 있었습니다.
느낀 점
공부해 보고 코드를 작성해 보면서, 생각보다 프론트엔드에서 작성해야 할 부분이 적다는 것을 알게 되었다.
많은 부분을 백엔드에서 작성하고 고민해야 하는 부분이었고, 프론트엔드에서는 동작원리와 방식만 이해하기 위해 노력했다.
CSRF가 사이트 탈취를 한다는 건 알겠지만, 정확히 사이트 탈취가 무엇을 뜻하는지 이해하는 게 어려웠고 Refresh Token을 통해 공격을 방지하는 방식이 이해가 되다가도 다른 의문점이 제기될 때마다 답변을 망설이는 경우가 많았다. 아직 머릿속에 제대로 이해하고 정리하지 못하고 있다는 생각이 많이 들었다.
References
원티드 3월 프리온보딩 챌린지 (https://www.wanted.co.kr/events/pre_challenge_fe_7)
XSS와 CSRF 특징 및 차이 (https://lucete1230-cyberpolice.tistory.com/23)