내가 알고 있는 로그인 키워드는 다음과 같다. Oauth2, JWT, SpringSecurity, 자체로그인
여기서 나는
Oauth2 + JWT + SpringSecurity 로그인 방식을 구현했다 (구글, 네이버, 카카오)
로그인을 구현하는 중 난관에 봉착했다.
로그인을 하는 방법은 크게 두가지로 나눌 수 있는데
1. 프론트가 애플리케이션으로부터 인가코드를 받아 서버에 전송하는 경우
(애플리케이션 리다이렉트 URL을 프론트로 설정하는 경우)
2. 애플리케이션과 서버가 직접 통신하는 경우
(애플리케이션 리다이렉트 URL을 서버로 설정하는 경우)
보통 로그인 흐름도에 맞게 1번을 사용하여 구현을 해왔으나, 스프링 시큐리티를 도입하면
애플리케이션으로부터 인가코드를 받고 자동으로 요청을 보내 액세스 토큰을 얻을 수 있을 수 있기 때문에
2번으로 구현을 했다.
하지만, 2번을 하게 되면 프론트가 서버에 요청을 보내지 않기 때문에 서버가 리다이렉션 시켜줘야한다.
여기서 문제, 어떻게 액세스 토큰을 넘기지?
리다이렉션을 하면 페이지가 달라지기 때문에 받았던 액세스 토큰이 날라간다.
세션에 저장하기엔 JWT를 사용했기 때문에(sessionless) 무리가 있었다.
body로 보내려면 프론트가 다시 요청을 보내야한다. (그럼 1번으로 구현하는게 더 낫겠다)
처음 프로젝트를 했을땐, 파라미터로 보내는 방식을 택했다.
하지만 엑세스 토큰이 탈취되는 순간 굉장히 위험한 상황에 놓이게 된다.
이제 보안을 생각하면서 구현을 해보려고 하는데 도대체 어떻게 전달하고, 토큰관리를 해야 될까?
전달 및 토큰관리 방식
1. 쿠키
HTTP가 stateless인것을 보완하기 위해 나온 것이 쿠키라고 볼 수 있다.
서버쪽 응답 헤더에 설정하는 방식이다.
쿠키를 사용하면 XSS 공격으로부터는 비교적 안전할 수 있다.
다양한 암호옵션(암호화, 유효기간)이 있을 수 있고, 자동으로 헤더에 쿠키를 설정해 보내기 때문이다.
특히 HttpOnly를 사용하면 자바스크립트에서 쿠키에 대한 접근이 불가하므로 안전하다.
secure 옵션도 추가해서 HTTPS 통신일때만 쿠키로 보낼 수 있도록 설정할 수 있다.
* XSS : 악의적인 스크립트를 사용하여 사용자의 인증정보를 탈취, 사용자의 동의없이 서비스 이용
하지만? CSRF 공격으로부턴 여전히 취약하다.
* CSRF : 인증된 사용자의 권한을 이용하여 악성요청을 보내는 공격
2. body & param+ 로컬스토리지
CSRF 공격과 XSS 공격으로 위험할 수 있다.
정보가 네트워크를 통해 전송되는 동안 탈취될 수 있다. 특히 HTTPS가 아닌 HTTP로 요청을 보낼 경우, 네트워크에서 트래픽을 가로챈 공격자가 데이터를 탈취할 위험이 있다.
또한 로컬스토리지를 남용하여 악의적인 사용자가 로컬 스토리지에 저장된 데이터를 수정한 후 이를 서버로 전송하면, 서버가 신뢰할 수 없는 데이터를 받게 되어 보안 취약점이 발생할 수 있다.
3. 세션 ID 고정 방식
Session Fixation 보안 문제로, 공격자가 세션의 ID를 탈취하는 순간 위험해진다.
* Session Fixation : 세션 고정 공격은 공격자가 특정 세션 ID를 미리 설정하고, 사용자가 그 세션 ID를 사용하도록 유도하여, 로그인 처리된 세션 ID를 공격자가 이용하는 것
하지만 나는 JWT를 구현했기 때문에 세션리스를 적용시켰다. (사용할 수 없음)
JWT(JSON Web Token)에서 세션리스(sessionless) 인증을 사용해야 하는 이유는 서버가 클라이언트의 세션 정보를 저장하지 않고, 모든 인증을 클라이언트가 가진 토큰으로 처리하기 때문이다. 이를 통해 얻을 수 있는 주요 장점은 확장성, 효율성, 보안성 등이 있다.
방어방식
1. 쿠키 방어방식
위에서 XSS 공격을 방어하는 방식을 알아봤다.
CSRF 공격을 방어하기 위해선 어떻게 해야할까? SameSite 옵션을 사용하면 된다.
Same Site 옵션은 출발지를 검사하는 옵션이다. a에서 발급받은 쿠키는 b에서 전송한 요청에 담기지 않게 된다.
하지만 여전히 XSS + CSRF 조합의 공격이 왔을땐 위험하다.
XSS를 통해 쿠키를 탈취하지 않고 API를 호출할 경우 사용자의 브라우저에서 전송하는 요청이기 때문에 CSRF 공격을 피할 수 없다.
따라서, XSS를 아예 불가능하게 해야한다. XSS 공격을 할 수 없도록 서버는 사용자의 입력에 대해 클렌징(escape) 작업을 수행해야 한다. 그리고 제한된(등록된) 태그만 사용할 수 있도록 작업이 필요하다.
2. body & param + 로컬 스토리지 방식
암호화가 있긴 하지만, 대체적으로 보안성이 높지 않으므로 민감한 데이터 정보 저장은 피하는게 좋다.
3. 세션 ID 고정 방식
(근데 결국 세션 ID를 보낸다면 쿠키로 보내야할 수 밖에 없다)
로그인 전후 세션ID가 변경되지 않았기 때문이므로, 로그인 시점에 세션을 새로 만들어줘서 간단히 해결할 수 있다.
작성하다보니, 세션 ID 고정방식이 가장 나을 것 같다는 생각을 했다. 미리 세션 ID를 프론트와 얘기해놓고 진행하면 해결할 수 있을것 같기도 하다!
그럼 jwt 토큰사용과 세션 저장 중 어떤게 보안성이 더 높을까?
jwt토큰 vs 세션저장에 대한 생각
검색했을때 세션 저장이 더 안전하게 나온다.
만약 프론트와 얘기하지 않고 Session Fixation 문제를 해결하기 위해 로그인 전후로 세션 ID를 변경한다면,
해당 ID는 어떻게 프론트에게 전달할까? 보통 쿠키로 전달한다고 하는데, 그러면 똑같은거 아닌가?
세션저장 + 세션아이디 전달(쿠키) 관리를 할지
사용자 인증을 책임지는 jwt 토큰 (쿠키)로 관리를 할지
고민이 됐다.
해당 토큰이 유효한 토큰인지 빠르게 확인해서 서버 부하를 최소화 시키는 jwt 토큰에
쿠키 (HttpOnly, Secure, SameSite) 설정을 하고, HTTPS 통신을 사용한다면
보안을 책임질 수 있지 않을까? 라는 생각을 했다.
즉, 결국 쿠키를 사용한다면 서버부하를 최소화시키는 jwt 토큰을 선택하겠다는 말이다.
개발자가 편하면, 해커도 편하다는 말을 들은적이 있는데 맞는말인 것 같다. 로그인 부분을 해당 사안을 고려해서 강화해야겠다.
참고자료
https://joe-cho.tistory.com/11
https://campkim.tistory.com/82