인증과 인가는 API에서 가장 자주 구현되는 기능중 하나이다. 인증이란 유저의 신원(아이디와 패스워드)을 확인하는 절차이며, 인가란 (로그인 한)유저의 요청에 대해 그 요청을 할 수 있는 권한이 있는 유저인가 확인하는 절차이다.
가장 먼저 회원가입 절차, 즉 사용자의 아이디와 비밀번호를 서버의 DB에 저장하는 과정이 먼저 필요할 것이다. 이때 비밀번호는 암호화되어 저장된다. 다음으로 로그인을 할 때 사용자가 입력한 아이디와 비밀번호가 DB에 있는지 확인한 후 성공할 경우, 서버에서는 클라이언트에게 허가 토큰(access token)을 전송하게 된다. 사용자는 한 번 로그인 정보를 입력한 후에는 이 토큰을 첨부해서 서버에 요청을 보내게 된다.
비밀번호 암호화에는 일반적으로 단방향 해쉬 함수(one-way hash function)가 쓰인다. 이 함수는 원본 메세지(사용자의 실제 비밀번호)를 변환하여 암호화된 메세지를 생성한다. 예를 들어 hash256이라는 해쉬 함수는 "test password"를
0b47c69b1033498d5f33f5f7d97bb6a3126134751629f4d0185c115db44c094e
라는 값으로 변환시킨다. '단방향'의 특성상 비밀번호로부터 암호화된 메세지를 만들기는 쉽지만, 반대로 암호화된 메세지를 원본 메세지로 복구할 수 없다. 그러나 원본메세지가 같으면 암호화된 메세지도 같기에 미리 해쉬 함수의 결과값들을 거대한 표로 만들어 놓고 표로부터 해킹을 할 수 있다는 취약점이 있다. 장비를 사용하여 1초당 56개의 다이제스트(해쉬 결과값)을 대입할 수 있다.
이런 문제를 보완하기 위해 일반적으로 사용하는 방법으로 salting과 key stretching을 사용한다.
salting은 실제 비밀번호에 추가적인 랜덤 데이터를 더해서 해시값을 계산하는 것이다.
key stretching은 해쉬값을 계산 한 후 그 해쉬값을 다시 해쉬하는 과정을 반복하는 방법이다. key stretching을 사용하면 1초에 50억 개 이상의 다이제스트를 표에서 비교할 수 있는 장비가 1초에 5번 정도만 비교할 수 있게 된다. 컴퓨터 성능이 향상되거나 GPU를 사용하여 해킹의 성능이 올라갈 경우, key stretching도 따라서 더 반복 해쉬를 하여 보완할 수 있다.
이 두 가지를 구현한 해쉬 함수중 가장 널리 사용되는 것이 bcrypt이며 이 함수는 만들어질 때부터 비밀번호를 단방향 암호화하기 위해 만들어진 해쉬함수이다. 이쯤에서 난 아래와 같은 궁금증이 생겼다.
데이터를 보호한다면서 모두가 동일한 bcrypt를 사용해도 되는걸까?
이 질문에 대한 답변이다. 원희님께서 알려주셨다. 케르크호프스의 원리: 링크
요약하자면 암호화 방법은 그 방법이 모든 사람에게 알려지더라도 안전해야 하는 것이 더 중요하다는 의미이다. 이런 암호화 해쉬 함수는 굉장히 만들기가 어려우며 개발자의 영역이 아니라 수학자의 영역이라고 볼 수 있다. 즉, 오픈된 방법이라도 해킹하기 어려운 암호화 방법을 채택하는 것이 바람직하는 것이다.
앞서 언급한 허가 토큰(access token)중 널리 사용되는 기술 중 하나가 JWT(JSON Web Tokens)이다. 유저가 로그인에 성공한 이후에 클라이언트와 서버와 통신할 때 인증된 유저임을 밝히기 위해 로그인 없이 저장된 token을 계속 사용하게 된다.
이 방법은 먼저 퍼포먼스 측면에서 좋다. 무거운 bcrypt call을 계속해서 할 필요가 없다. 그리고 클라이언트 측에서도 실제 아이디와 비밀번호를 저장하는 것이 아니라는 장점이 있따. 쿠키와 같은 브라우저에 토큰을 저장하며 토큰 자체가 특정한 서버에서만 사용할 수 있고 토큰에 기한 설정 등을 할 수 있어 보안 문제도 해결이 된다.
인가는 위에서 언급한 JWT를 통해서 구현될 수 있다. 사용자마다 권한이 각자 다를 수 있다. 프리미엄 구독자인지, 사이트 관리 권한자인지, 일반 고객인지 등. 토큰에서 해당 유저 정보를 얻을 수 있으므로 해당 유저가 가진 권한도 확인할 수 있다. 서버에서 클라이언트로 받은 받은 토큰을 복호화해서 user id를 얻으면 데이터베이스에서 해당 유저가 어떤 권한인지 알 수 있게 된다. 서버에서는 권한에 따라 다른 에러 코드를 보낼 수 있다.
fetch
함수 사용법위에서 이야기한 인증, 인가와 같은 경우에 백엔드와 프론트의 통신이 필요하다. 물론 페이지 렌더링을 위한 일반적인 데이터도 마찬가지이다. 웹 페이지가 백엔드로부터 데이터를 받아올 때 api를 호출하게 된다. 이 때 자바스크립트는 Web API fetch()
함수 혹은 axios 라이브러리를 사용할 수 있다. 실무에서는 axios를 사용하지만, 자바스크립트 내장 함수 fetch()
로도 웬만한 기능을 충분히 구현할 수 있다.
사실 위 두가지의 사용법 보다는 http 통신 요청과 응답, promise 개념이 더 중요하다. 이 글에서는 우선
fetch
함수의 사용법에 대해 알아본다.
api 명세서를 보고 fetch
가 어떻게 사용되는지 바로 살펴보자! fetch
함수의 default method는 get이다.
유저 정보를 가져오는 api
base url: https://api.homepage.com
endpoint: user/3
method: get
응답형태:
{
"success": boolean,
"user": {
"name": string,
"batch": number
}
}
componentDidMount() {
const { userId } = this.props;
// 만약 get method에서 parameter를 전달해야 하는 경우 아래와 같이
// query string으로 넘겨줘야 할 수도 있습니다.
fetch(`https://api.homepage.com/user?id=${userId}`)
.then(res => res.json())
.then(res => {
if (res.success) {
console.log(`${res.user.name}` 님 환영합니다);
}
});
}
post일 경우에 api 명세서와 fetch
함수 사용법이다.
유저 정보를 저장한다.
base url: https://api.homepage.com/user/signup
endpoint: /user
method: post
요청 body:
{
"name": string,
"batch": number
}
응답 body:
{
"success": boolean
}
fetch('https://api.homepage.com/user/signup', {
method: 'post',
body: JSON.stringify({
name: "janghyeon",
batch: 1
})
})
.then(res => res.json())
.then(res => {
if (res.success) alert("저장 성공!");
})
JSON.stringify()
, json()
http통신에서 주고받는 데이터는 모두 string type이다. 그리고 객체 형태의 데이터가 개발자 입장에서는 더 바람직하다. 이 두 함수 stringify()
와 json()
은 .json
형태의 데이터와 string
형태의 데이터 사이의 변환을 해주는 함수이다.
서버에서 보내주는 response를 response.json()
을 거친 후 직접 콘솔에 출력해보면 객체 형태로 데이터를 보기 쉽다. 그리고 데이터를 전송할 때는 string type으로 전송해야 하기 때문에 JSON.stringify
로 데이터를 가공해준다.
하나의 요청에서 응답 body가 올수도 있지만 백엔드 응답에서 body를 주지 않는 경우가 있을 수 있다. 만약 status code만 받고 body가 없을 경우 response.json()
을 호출하면 에가 발생하게 된다. 따라서 body의 유무에 따라 json()
함수를 사용할지 말지에 대한 로직을 추가해야 한다. body의 유무는 백엔드에서 보내주는 status code의 값에 따라 알 수 있다.