인증과 인가
작성일
인증과 인가란?
인증
(식별가능한 정보로) 서비스에 등록된 유저의 신원을 입증하는 과정
인가
권한에 대한 허가, 인증된 사용자에 대한 자원 접근 권한 확인
결국, 인증이 인가보다 선행되어야 한다.
인증의 방법
- 인증하기 Request Header
- 인증 유지하기 Browser
- 안전하게 인증하기 Server
- 효율적으로 인증하기 Token
- 다른 채널로 인증하기 OAuth
HTTP는 무상태성
이라는 것이 매우중요
서버는 클라이언트가 보낸 요청과 이 다음 요청이 연관관계가 없다고 생각하는 것을 무상태성(Stateless)
클라이언트(사용자)는 서버(네이버)에 이미 회원가입이 돼 있는 상태라고 가정한다.
Request Header를 활용한 로그인
사용자는 아래의 주소를 통해서 서버에 로그인을 요청한다.
http://user:1q2w3e!@www.naver.com/login
브라우저는 위의 url에서 사용자 정보(id, pw)를 파싱한 후 Base64 인코더를 이용해 인코딩한다. 그리고 인코딩한 문자열을 요청 헤더 Authorization
에 넣어서 보내준다.
Authorization: Base dGE2fjdsfGsdkfjs3lssfdQ
그 후 서버에 요청을 하면 서버가 DB에 있는 값과 비교하여 유효한지 확인 후 응답을 해준다.
문제점
사용자가 매번 인증을 해야 한다. 예를 들어 사용자가 게시판에서 글을 쓸때 로그인을 한번 했다. 그런데 작성한 글을 수정하고 싶어졌다. 다시 로그인을 해야한다.
이러한 문제점을 개선하기 위해 Browser에 있는 Storage
를 사용한다.
Browser를 활용한 로그인
Storage의 종류에는 로컬스토리지, 세션스토리지, 쿠키가 있는데 이 중 쿠키로 설명.
쿠키에 사용자 정보(ID/PW)를 넣고 인증이 필요할때마다 서버로 보내준다. 매우 편리하죠! 근데 해커도 같이 편리함 ㅎㅎ;
문제점
스토리지에 사용자 정보가 있으니 해커가 가져가기 매우 편하고 클라이언트는 서버에 비해 상대적으로 보안에 취약하다는 점이 매우 큰 단점이다.
이런 두가지 단점을 해결하기 위해 (보안을 향상시키기 위해) 세션이라는 개념이 도입되었다.
Session을 활용한 로그인
세션은 인증된 사용자의 식별자와 랜덤한 문자열로 세션 ID를 만들어서 이를 응답 헤더에 넘겨준다.
이제 클라이언트 쪽에 사용자 정보를 가지고 있지 않기 때문에 해커가 정보를 가져가도 크게는 위험하지 않다. 또한, 세션의 만료기간을 정할 수 있어 만료기간이 지나게 될 경우엔 해커가 정보를 가져가더라도 유효하지 않다는게 장점이다.
그리고 세션관리는 서버에서 하기때문에 세션이 탈취되면 서버에서 삭제해버리면 더 이상 이 세션을 이용하지 못하게 된다는 보안상 이점이 있다.
그러나, 이 방법도 문제가 있다.
문제점
서버를 증설할 경우이다. 서버가 여러개 생기면 로드밸런서가 필요하다. 요청을 보내고 로드밸런서가 서버3으로 인증을 진행했다. 이제 세션은 서버 3에 있다. 그런데 같은 사용자가 다시 어떠한 요청을 보냈는데 로드밸런서가 서버2로 요청을 보냈다. 서버2에는 사용자에 대한 인증정보가 없다. 따라서 문제가 발생한다.
이 문제는 서버 하나하나 자체에서 세션을 관리해서 생긴 문제이다.
Session Storage를 활용해 문제점 해결
이 문제를 해결한 방법은 세션스토리지를 따로 둔다. 모든 서버의 세션에 대한 정보는 세션스토리지에 있다.
그런데 이 방법도 문제가 있다. (ㅠㅠ) 클라이언트가 많을 경우 문제가 생긴다. 수 많은 세션을 세션스토리지 하나에서 관리하므로 문제가 생긴다.
이제 어떻게 해야 할까?
지금까지 클라이언트, 서버, 세션스토리지에 모두 사용자의 정보를 관리할 수 있게 구성을 해봤다.
하지만 모두 문제가 생겼다. 그 이유는 HTTP와 서버자체가 지향하는 Rest API가 무상태성
을 기초로 하기 때문이다. 그런데 인증과 인가는 상태성
이다. 두 패러다임이 충돌한다. 이 패러다임을 해소해야 문제가 해결된다.
이제 사용자정보를 정보의 흐름에 맡겨보자. 정보의 흐름은 요청/응답을 의미한다.
즉, 요청과 응답에 사용자 정보를 담는다. 이를 토큰을 활용한 인증과 인가방법이라 한다.
Token을 활용한 로그인
JWT(Json Web Token)
를 이용한다.
JWT에 대해 간단한게 설명하면 시크릿 키를 사용해서 JWT를 만든다. 그리고 시크릿 키를 사용해서 JWT의 인증과정을 거친다.
JWT 내에는 민감한 정보를 두지 않는다. 그리고 시크릿 키가 중요한 만큼 노출되면 JWT 토큰 자체도 끝난다. 그래서 토큰을 사용하기 위해서는 시크릿 키를 서버 내부에 잘 관리해야 한다.
토큰(JWT) 활용하기
클라이언트가 로그인 요청을 보내고 DB가 유효성을 확인한다. 이전에는 세션스토리지와 연결을 했었는데 이제는 시크릿 키를 이용해 토큰을 생성한다.
생성한 토큰은 헤더에 넣어서 이 키를 이용해 요청을 보내고 응답을 받는 형태가 된다.
토큰에 대해 좀더 알아보면
클라이언트가 토큰을 가지고 서버에 요청을 했다. 그러면 서버는 토큰의 유효성 검사를 본인이 가진 시크릿 키로 진행한다. 유효하지 않다면 버리고 유효하다면 토큰을 디코딩하여 사용자 정보를 파악한다. (디코딩이 쉽기 때문에 절대로 비밀번호를 넣으면 안된다.)
토큰에는 이름, 만료시기, 권한에 관한 정보가 있다.
즉, 시크릿키를 통해서 유효성 검사를 통과한 토큰은 이미 인증을 받은 토큰이라는 것이다.
토큰의 장점
서버가 여러대 있어도 각각의 서버에 있는 시크릿 키로 인증을 진행하면 된다는 장점이 있다. 이 장점이 조금 더 나아가서는 확장성과도 연결되는데 3대였던 서버를 5대로 늘려도 수정없이 똑같이 진행할 수 있다. 즉, 확장성에 용이하다.
하지만 또 단점이 있다.
해킹을 당할 수 있다. 액세스 토큰을 탈취 당할 수 있는데 이를 막기 위해 만료기한을 정한다. 만료기한이 지나면 액세스 토큰을 다시 발급 받아야 하는데 이 부분이 귀찮으니까 리프레시 토큰
개념이 나온다.
흐름 정리
로그인 요청을 보내면 시크릿 키를 통해 토큰을 생성한다. 이 때 서버는 액세스 토큰과 리프레시 토큰을 같이 생성한다. 그리고 액세스 토큰은 저장하지 않고 리프레시 토큰만 저장소에 저장한다. 그 다음 이 둘을 한번에 응답헤더로 보내서 클라이언트는 액세스 토큰과 리프레시 토큰을 모두 저장하고 있다.
시간이 흘러 액세스 토큰이 만료되었다고 가정하자. 사용자는 액세스 토큰이 만료되었다는 사실도 모르고 알 필요도 없다. 사용자는 액세스 토큰이 만료되었다는 사실을 모른체 서버에 요청을 보낸다. 서버는 액세스 토큰이 만료되었다는 사실을 클라이언트에게 알린다. 이제 브라우저는 자동으로 액세스 토큰과 리프레시 토큰을 함께 서버로 보낸다. 서버는 리프레시 토큰이 유효하다면 새로 갱신한 액세스 토큰을 클라이언트에 보내게 된다. 그러면 이제 클라이언트에도 업데이트된 액세스 토큰을 사용할 수 있게 됨
결론
토큰으로 상태 관리를 하기에 따로 세션을 둘 필요가 없다. 효율성이 좋아지고 DB까지 가지 않아도 되기 때문에 속도가 빠르다는 장점이 있다. 하지만 토큰도 관리해야 하는 대상이기 때문에 보안에 있어서 신경을 써야한다.