[학습일기] HTTP 서비스 인증/인가 관련 이해한 내용 정리
들어가며
인증과 인가와 관련해서 공부를 할 때 원리가 이해가 안 되어서 로그인과 CSRF와 같은 개념을 그냥 예시를 통해서 귀납적으로 학습하는 데에 그쳤다. 당연히 뭔가 답답한 느낌이 들었고 CSRF의 경우 외우고 까먹고를 반복했다.
그러다가 최근에 갑자기 이 내용이 이해가 되었다. 방금 깨달은 사람의 따끈따끈한 이해를 말아드리고 싶어서 이 블로그글을 작성하게 되었다. (이랬는데 알고 보니 틀린 이해일 수도 있습니다ㅠㅠ 혹시 틀린 부분을 발견하시면 댓글 달아주시면 감사합니다!!)
이해한 부분
네트워크 서비스의 인증 및 인가와 관련해서 이해하는 데 개인적으로 다음 활동이 굉장히 도움이 많이 되었다.
특정 사용자를 인증하기 위한 필요충분조건을 논리식으로 작성하기
특정 서비스의 사용을 인가하기 위한 필요충분조건을 논리식으로 작성하기
이 관점에서 인증 및 인가를 다음과 같이 정리하게 되었다.
인증(Authentication, Authn)
정의
위키피디아에서 찾아본 인증의 정의는 다음과 같다.
특정 사용자의 본인확인을 증명하는 과정. 다시 말해, 특정 사용자가 본인이라고 주장하는 사람이 맞는지 확인하는 과정
인증이라는 개념이 왜 필요하게 되었는지를 웹 서버의 입장이 되어 생각해보자.
일반적인 웹 서비스에서 사용하는 웹 서버의 경우 대부분의 ip로부터 요청을 받을 수 있다. 비유하자면 불특정 다수로부터 쪽지를 받는 것과 같다. 다음 쪽지를 받았다고 가정해보자.
안녕하세요, 미스터 비스트 유튜브 팀에서 연락드렸습니다.
저희가 이번에 만들고 있는 영상에 초대드리고 싶습니다.
아래의 주소에 접근해서 설명을 따라 주시면, 다음 영상에 출연하실 수 있습니다.
https://example.com?phishing=true
문제가 한두개가 아니다. 일단 미스터 비스트가 내게로 연락을 보낼 리가 없고, 글솜씨가 허접하다. (글쓴이의 글쓰기 스킬 이슈입니다.) 하지만 웹서버 입장에서 중요한 문제는 따로 있다. 본인이 알고 있는 주소에서만 접근하는 것도 아니고 불특정 다수의 ip 주소로부터 HTTP 요청을 받기 때문에 요청을 보낸 것이 실제 미스터 비스트인지 미스터 비스트인 척 하는 김 아무개씨인지를 확인할 방법이 없다. 다시 말해 웹 서비스에 가입한 특정 사용자 A가 있을 때, 본인이 사용자 A라고 주장하는 요청이 실제 사용자 A가 보낸 것인지 아닌지 확인하는 과정이 필요하다. 이런 맥락에서 인증이라는 개념이 자연스럽게 필요해진 것 같다.
간단한 웹 서비스에서의 사용자 인증
비밀번호를 통한 인증
소개 및 필요충분조건
웹 서비스를 누군가는 처음 만들었을 것이다. 만들면서 인증에 대한 문제를 다음과 같이 풀 생각을 하지 않았을까? (즉, 뇌피셜입니다.)
인증의 문제를 다음과 같이 해결하면 어떨까?
하나, 사용자 A가 가입하는 시점에 A와 웹 서비스 둘이서만 아는 비밀번호를 공유한다.
둘, 앞으로 HTTP 요청을 보낼 때 본인이 A라고 주장하며 동일한 비밀번호를 제시하면, 그 사람은 A라고 유추할 수 있다.
현재까지의 사고 과정을 필요충분조건으로 정리하면 다음과 같다.
(사용자 A 인증) <=> (HTTP 요청 시 A의 비밀번호가 제시됨)
문제점
위의 인증 방식은 조금만 생각해보면 사용성 또는 보안에서 엄청난 문제점이 있는 것을 확인할 수 있다. 웹사이트에 요청을 보낼 때마다 비밀번호를 제시해야 하기 때문이다. 사용자가 본인 페이지 내에서 이동할 때마다 비밀번호를 입력하게 하자니 사용성에서 하자가 있다. 또한 매 요청마다 평문으로 비밀번호가 전송되기 때문에 요청 중 하나라도 감청당한다면 비밀번호가 그대로 노출되게 된다.
비밀번호 및 세션 토큰을 통한 인증
소개
사용자가 웹 서비스를 사용할 때 특정 시각부터 특정 시각까지 대화가 오가는 패턴이 자주 등장한다. 대화에서와 마찬가지로 이 대화 내의 나중의 요청은 이전의 요청들에 의해 쌓인 맥락을 가지고 있다. 이렇게 특정 시간 범위 동안 존재하는 고수준 양방향 연결을 세션이라고 한다. 보통 세션은 이전 요청들에서 쌓인 맥락을 가지고 있기에 stateful하다.
이렇게 웹 서비스에서 흔하게 존재하는 세션이라는 개념을 활용하면 "매 요청마다 비밀번호가 평문으로 전송된다"는 문제를 다음과 같이 해결할 수 있다.
이미 사용자 A에 대한 세션이 있는 경우 A의 세션에 대한 접근 권한을 A의 비밀번호 대신 사용할 수 있다.
이 경우 연결이 감청당한 경우에도 세션 접근 권한 정보는 세션이 존재하는 동안만 유효하기 때문에 비밀번호가 유출되는 확률을 낮출 수 있다. (추가: 그러나 세션이 탈취당한 상황에서 이게 얼마나 의미가 있는지는 모르겠습니다.)
따라서 매번 비밀번호를 입력받는 과정을 다음의 세 단계 과정으로 대체할 수 있다.
하나, 사용자 A에 대한 아이디와 비밀번호를 받아서 서버에서 세션 저장소에 A를 위한 세션의 정보를 저장하고, 세션 정보에 접근할 수 있는 권한을 응답에 반환한다. 이를 보통 "로그인"이라고 칭한다.
둘, 로그인 상태에서는 세션 접근 권한을 비밀번호 대신 인증 수단으로 사용한다.
셋, 특정 시간이 지나거나 사용자가 직접 요청하는 경우 세션 정보를 파기한다. 이를 "로그아웃"이라고 칭한다.
토큰 소개
오해를 방지하기 위해 토큰에 대해서 짚고 넘어가보자. 위키피디아에 의하면 토큰의 정의는 다음과 같다.
특정 작업에 대한 권한을 표현하는 소프트웨어 혹은 하드웨어 객체
따라서 꼭 JWT의 형식이 아니더라도 세션 접근에 대한 권한을 표현하는 데이터를 모두 "세션 토큰"이라고 칭할 수 있는 것으로 보인다. (혹시 아니라면 알려주시면 감사드립니다!)
필요충분조건
위에서 정리한 내용을 필요충분조건으로 정리하면 다음과 같다.
(사용자 A 인증) <=> (A 비밀번호 제시) OR (현재 존재하는 A의 세션에 대한 토큰 제시)
여기서 인상적인 점이 토큰이라는 개념은 바로 뒤에 다룰 내용인 인가에 대한 내용이지만, 인증 정책의 일환으로 A의 세션에 대한 권한이 있는 사용자를 A에 대한 인증의 충분조건으로 사용했기에 여기에 등장했다는 사실이다.
정리
여기까지 인증에 대한 내용을 간단하게나마 살펴보았다. 인증으로 접근 권한도 퉁쳐서 나타내면 안 되는 걸까? 여기서는 CSRF의 사례를 들어 둘을 분리해야 하는 이유 중 하나를 살펴보겠다.
인가
정의
위키피디아에서는 다음과 같이 서술하고 있다.
리소스에 접근할 수 있는 권한을 명시하는 것
인증과 구별해야 하는 이유
여기서 "A로 인증했으면 A가 권한이 있는 작업들을 모두 허용해도 되는 것 아닌가?"라는 의문이 당연하게 들 수 있다. 이 둘을 일치시켰을 때 발생할 수 있는 문제 중 하나로 CSRF 공격을 예로 들어보겠다.
CSRF
CSRF는 Cross-Site Request Forgery의 약자로, "사이트 간 요청 위조"를 뜻한다. 쉽게 말해 S1 사이트에서 사용자 A에게 본인의 의사와는 무관하게 S2 사이트로 특정 요청을 보내도록 시키는 공격이다. 예를 들어 S1이 해커 사이트이고, S2가 은행 사이트이고, S2가 CSRF에 대한 보호가 안 되어 있는 경우, S1 사이트에서 HTML Form과 자바스크립트를 통해 S1에 접속한 사용자 A로 하여금 해커에게 송금을 하라는 요청을 S2에 보내도록 시킬 수 있다. 자세한 정보는 MDN에서 잘 설명되어 있다.
CSRF가 발생할 수 있는 이유는 사용자가 브라우저에서 S1 사이트를 방문했을 때 S2 사이트에 대신 요청을 보내도록 하는 것이 너무 쉽게 가능하기 때문이다. Form 태그의 action attribute를 활용하면 사용자로 하여금 다른 사이트에 요청을 보내도록 할 수 있고, 이 때 cookie에 저장된 인증 정보가 같이 전송되기 때문에 문제가 된다.
CSRF 발생 원인 분석 및 해결
사이트 S2 입장에서 다음과 같은 정책을 생각할 수 있다.
(사용자 A의 송금 서비스 접근 권한) <=> (사용자 A의 인증 정보)
이렇게 할 경우 문제가 안 생기는 맥락이 어디엔가 있을지도 모르지만, 안타깝게도 웹 서비스 환경에서는 위에서 기술한 이유로 인해서 요청한 사용자가 정상적으로 웹 서비스를 사용해서 나온 요청인지, CSRF 공격의 피해를 당해서 발생하는 요청인지 구분할 수 없다.
이에 대응해서 정상적으로 웹 서비스를 사용하는 사람에게만 발급해주는 토큰을 사용해서 해결할 수 있다. 구체적으로 HTML Form으로 웹 서비스를 제공하는 경우, Form의 hidden field에다가 매번 바뀌는 랜덤 문자열 토큰을 삽입해주고, Form을 제출했을 때 이 토큰을 확인함으로써 해결할 수 있다. 이 경우 토큰은 사이트 외부에서 접근 가능한 정보가 아니지만 요청에 꼭 필요한 정보이기 때문에, CSRF로 요청을 위조하는 것이 불가능하게 된다.
이를 논리식으로 나타내면 다음과 같다.
(사용자 A의 송금 서비스 접근 권한) <=> (사용자 A의 인증 정보) AND (CSRF 방지 토큰 소유)
정리하며
생각보다 시간이 많이 걸렸습니다. 잘 이해했는지 모르겠네요...
댓글을 작성해보세요.