- 인증과 인가는 API에서 가장 자주 구현되는 기능 중 하나이다.
- Private한 API는 물론이고 Public한 API도 기본적인 인증과 인가를 요구한다.
인증은 회원가입과 로그인을 얘기한다.
서비스를 누가? 어떻게 사용하는지 추적이 가능하도록 하기 위해 필요하다.
인증에 필요한 것은 아이디, 이메일 주소, 비밀번호 등이 있다.
가장 기본적인 로그인 절차는 다음과 같다.
access token
을 클라이언트에게 전송access token
을 첨부해서 request를 서버에 전송함으로써 매번 로그인 해도 되지 않도록 한다.유저의 private 한 정보들을 DB에 그대로 저장하면 개인정보 유출에 막대한 영향을 끼치며, 법적으로도 금지되어 있다. 그렇기 때문에 유저의 비밀번호와 같은 private 한 정보는 개발자도 알아볼 수 없도록 암호화하여 저장하여야 한다. 암호화하여 저장한 비밀번호는 DB가 해킹을 당하더라도 그대로 노출되지 않는다. 이 때 일반적으로 사용하는게 hash 함수이다. hash 함수에 대해서는 아래를 참고하도록 하자.
단방향 해시 함수는 원본 메세지를 변환하여 암호화 된 메세지인 digest
를 생성한다. 원본 메세지를 알면 암호화 된 메세지를 구분하기는 쉽지만 암호화 된 메세지로는 원본 메세지를 구할 수 없기 때문에 단방향(one-way)
라고 한다.
0b47c69b1033498d5f33f5f7d97bb6a3126134751629f4d0185c115db44c094e
값이 나온다.d34b32af5c7bc7f54153e2fdddf251550e7011e846b465e64207e8ccda4c1aeb
값이 나온다. 실제 비밀번호는 비슷하지만 해쉬 함수 값은 완전히 틀린것을 볼 수 있다. 이러한 효과를 avalance라고 하는데 비밀번호 해쉬 값을 해킹을 어렵게 만드는 하나의 요소이다.위에서 설명한 단방향 해시 함수도 몇가지 취약점이 존재한다.
Rainbow table
이라고 한다.digest
와 해킹할 대상의 digest
를 비교할 수 있다. 이런 방식으로 패스워드를 추측하면 패스워드가 충분히 길거나 복잡하지 않은 경우 비밀번호를 알아내는데 그리 긴 시간을 필요로 하지 않는다.Salting
까지 된 후에 이 값을 더 덧붙여서 더 길게 만들어서 검색하는데 걸리는 시간을 훨씬 늘려버리는 것이다.Salting
과 Key Stretching
을 구현 한 해시 함수 중 가장 널리 사용되는 것이 bcrypt
이다. bcrypt
는 처음부터 비밀번호를 단방향 암호화 하기 위해 만들어진 해시 함수이다.
위에서 설명 한 로그인 절차를 보면 로그인을 성공 했을 때 access token
이라는 것을 발급해준다. access token
은 암호화 된 유저 정보를 첨부해서 request를 보내게 된다.
access token
이란 일종의 입장 권한을 허가해주는 티켓이라고 생각하면 이해하기 쉽다. 예를 들어, 어떤 페이지에 게시물을 등록하거나 댓글을 달기 위해서는 로그인을 해야한다는 제약 조건이 존재한다고 하자. 이런 경우 로그인을 한 유저에게는 access token
이라는 티켓을 발행해주는데, 이 티켓으로 '이 유저는 로그인을 성공적으로 마쳤으니, 게시물과 댓글을 추가할 수 있는 권한을 가지고 있어' 라는 권한을 허가해주는 것을 뜻한다. 그러면 서버에서는 access token
을 복화화 해서 해당 유저 정보를 얻게 된다.
access token
을 생성하는 방법은 여러가지가 있는데, 그 중 가장 널리 사용되는 기술 중 하나가 바로 JWT
이다.JWT
는 말 그대로 유저 정보를 담은 JSON 데이터를 암호화 해서 클라이언트와 서버간에 주고 받는 것이다.JWT의 구조
헤더(header).내용(payload).서명(signature) ex : ( aaaa.bbbbb.ccccc )
헤더(header) : header 에는 두가지 정보를 지니고 있다.
→ typ
: JWT
와 같은 토큰의 타입을 지정한다.
→ alg
: 해싱 알고리즘을 지정한다. 이 알고리즘은 토큰을 검증할 때 사용되는 signature
부분에서 사용된다.
내용(payload) : 토큰에 담을 정보가 들어있다. 여기에 담는 정보의 한 조각
을 claim
이라고 부르며, 이는 name / value
의 한 쌍으로 이루어져있다. 토큰에는 여러개의 클레임들을 넣을 수 있다.
사용자 정보에 대한 내용이 필수로 들어가야한다. 내용도 반대로 출력하면 내용을 볼 수 있기 때문에 private 한 정보는 담지 않고 데이터베이스의 user_id와 같은 정보를 넣는다.
이런 클레임의 종류는 크게 세 분류로 나눠져있다.
등록된(registered) 클레임
→ 토큰에 대한 정보들을 담기 위하여 이름이 이미 정해진 클레임들이다. 모두 선택적이라는 특징이 있다.
공개(public) 클레임
→ 충돌이 방지된 이름을 가지고 있어야 한다. 충돌을 방지하기 위해서는 이름을 URI
형식으로 짓는다.
비공개(private) 클레임
→ 클라이언트와 서버 협의 하에 사용되는 클레임 이름들이다. 충돌이 일어날 수 있으니 사용할 때 유의해야한다.
서명(signature) : JWT가 원본 그대로라는 것을 확인할 때 사용하는 부분이다.
이 서명 부분에는 헤더의 인코딩값과 내용의 인코딩값을 합친 후 주어진 SECRET_KEY
로 해시를 하여 생성한다. 서명은 BASE64URL로 인코딩 된 header와 payload 그리고 JWT secret(별도 생성)을 헤더에 지정된 암호 알고리즘으로 암호화 하여 전송한다. ( 복호화 가능 )
header와 payload는 BASE64 인코딩만 한 것이므로(암호화가 아님) 누구나 원본을 볼 수 있으니 개인정보를 담아서는 안된다.
실제로 위스타그램 로그인 부분에서 bcypt
와 jwt
를 활용하여 구현해보았다.
비밀번호 암호화 및 token 발행 ( bcrypt, JWT )
성능
→ 암호 해독 호출이 심하지 않고 간단한 해시만 사용한다.
클라이언트 측 저장소
→ 쿠키와 같은 클라이언트에 실제 ID와 암호가 저장되지 않는다.
→ 토큰은 서버마다 다르며 다른 사이트에서는 재사용되지 않는다.
Authorization
은 유저가 요청하는 reuqest
를 실행할 수 있는 권한이 있는 유저인가를 확인하는 절차이다.
예를 들면 해당 유저는 고객 정보를 볼 수 있지만 수정할 수는 없는 경우가 있다.
Authorization
도 JWT를 통해서 구현 될 수 있다.
→ access token
을 통해 해당 유저 정보를 얻을 수 있기 때문에 해당 유저가 가지고 있는 권한도 확인할 수 있다.
access token
을 생성한다. access token
에는 유저 정보를 확인할 수 있는 정보가 들어가 있어야 한다 (예를 들어 user id).access token
을 첨부해서 보낸다.access token
을 복호화 한다.위에서 인증과 인가에 대한 내용을 학습했고, 그로 인해 어떻게 인증을 하고 권한을 인가하는가에 대해서 이해했다. 그래서 프론트엔드와 백엔드는 어떻게 통신하는데? 사실 이 질문에 답하기위해 위의 개념들을 학습하는게 아닐까?
이제 프론트엔드의 입장에서 한번 생각해보자.
access token
을 주고 받아서 사용자 인증을 진행한다. 로그인을 하면 access token을 받아서 프론트엔드는 어딘가에 보관하고 있다가 사용자 정보에 필요한 api 에 해당 access token을 백엔드에 보내준다.그럼 받아온 access token을 setItem
메소드를 사용하여 스토리지에 저장을 하고 request header
에 Authorization
을 보내면 된다.
이러한 과정을 거쳐서 프론트엔드와 백엔드는 서로 통신을 할 수 있다.