인증(Authentication)
- Authentication은 유저의 identification을 확인하는 절차
- 다시말해, 유저의 아이디와 비밀번호를 확인하는 절차
- 인증을 하기 위해선 먼저 유저의 아이디와 비밀번호를 생성할 수 있는 기능도 필요하다
로그인 절차
- 유저 회원가입 => 이디와 비밀번호 생성. 비밀번호 암호화저장.
- 유저 비밀번호 암호화 해서 Database에 저장.
- 유저 로그인 => 아이디와 비밀번호 입력
- 유저가 입력한 비밀번호를 암호화 한 후, DB에서 암호화된 비밀번호와 비교.
- 일치하면 로그인 성공, 일치하지 않으면 Error.
- 로그인 성공하면
access_token
을 클라이언트에게 전송
- 유저는 로그인 성공후 다음부터는
access_token
을 첨부해서 request를 서버에 전송함.(매번 로그인 하지 않아도 됨)
유저 비밀번호 암호화
- 유저의 비밀번호는 절대 비밀번호 그대로 Database에 저장하지 않는다
- Database가 해킹을 당할 경우 비밀번호가 그대로 유출되기 때문.
- 외부 해킹이 아니더라도 내부 개발자나 인력이 유저들의 비밀번호를 볼 수 있는 취약점 존재
- 유저의 비밀번호는 반드시 암호화 해서 저장.
- Database가 해킹당하더라도 비밀번호가 그대로 노출되지 않음.
- 비밀번호 암호화에는 단방향 해쉬 함수(one-way hash function)가 일반적으로 사용
- 단방향 해쉬 함수(one-way hash function)
: 원본메세지를 변환하여 암호화된 메세지인 다이제스트(digest)를 생성한다. 암호화된 메세지로는 원본메세지를 구할 수 없어서 단방향성(one-way)이라고 한다.
(ex. "testpassword"를 "hash256"로 해쉬 함수를 사용하면, "0b47c69b1033498d5f33f5f7d97bb6a3126134751629f4d0185c115db44c094e"라고 나온다
만약 "testwpassword2"를 똑같이 "hash256"로 해쉬 함수를 사용하면, "d34b32af5c7bc7f54153e2fdddf251550e7011e846b465e64207e8ccda4c1aeb"라는 값이 나온다 따라서 원본 비밀번호는 비슷하지만, 해쉬값은 완전히 다른 값이 나와 해킹하더라도 정보를 쉽게 알려줄수 없도록 도와준다)
In [21]: import hashlib
In [22]: m = hashlib.sha256()
In [23]: m.update(b"test password")
In [24]: m.hexdigest()
Out[24]: '0b47c69b1033498d5f33f5f7d97bb6a3126134751629f4d0185c115db44c094e'
In [25]: m = hashlib.sha256()
In [26]: m.update(b"test password2")
In [27]: m.hexdigest()
Out[27]: 'd34b32af5c7bc7f54153e2fdddf251550e7011e846b465e64207e8ccda4c1aeb'
Bcrypt
- 단방향 해쉬 함수에는 몇가지 취약점이 존재
- Rainbow table attack - 미리 해쉬값들을 계산해 놓은 테이블(Rainbow Table)을 통해 암호화값이 맞을 때까지 공격함
- 따라서 2가지 보완점 필요
- Salting
: 실제 비밀번호 이외에 추가적으로 랜덤 데이터를 더해서 해시값을 계산함
- Key Strethcing
: 단방향 해쉬값을 계산한 후 그 해쉬값을 여러번(Iteration count) 해쉬하는 것.
- Bcrypt 암호화
: Salting과 Key Stretching을 구현한 해쉬 함수중 가장 널리 사용되는 것 중 하나.
In [40]: import bcrypt
In [41]: bcrypt.hashpw(b"secrete password", bcrypt.gensalt())
Out[41]: b'$2b$12$.XIJKgAepSrI5ghrJUaJa.ogLHJHLyY8ikIC.7gDoUMkaMfzNhGo6'
In [42]: bcrypt.hashpw(b"secrete password", bcrypt.gensalt()).hex()
Out[42]: '243262243132242e6b426f39757a69666e344f563852694a43666b5165445469397448446c4d366635613542396847366d5132446d62744b70357353'
JWT(JSON Web Token)
- 유저가 로그인 성공한 후에는
access_token
이라고 하는 암호화된 유저정보를 첨부해서 response를 받게된다.
- 유저 로그인
POST /auth HTTP/1.1
Host: localhost:5000
Content-Type: application/json
{
"username": "joe",
"password": "pass"
}
HTTP/1.1 200 OK
Content-Type: application/json
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZGVudGl0eSI6MSwiaWF0IjoxNDQ0OTE3NjQwLCJuYmYiOjE0NDQ5MTc2NDAsImV4cCI6MTQ0NDkxNzk0MH0.KPmI6WSjRjlpzecPvs3q_T3cJQvAgJvaQAPtk1abC_E"
}
- 로그인에 성공한 유저는 다시 로그인하는 것이 아니라,
access_token
을 request 메세지에 넣어 서버에 보낸다.
acces_token
을 받은 서버는 복호화를 걸쳐, 해당 유저 정보가 database에 있는지 확인한다.
- JSON 데이터를 암호화하여 클라이언트-서버 간에 주고 받는 것을 JWT(JSON Web Token)이라 한다.
인가(Authorization)
- Authorization은 유저가 요청하는 request를 실행할 수 있는 권한이 있는 유저인가를 확인하는 절차
(ex. 해당유저는 고객 정보를 볼 수 있지만 수정할 권한이 없는 경우)
- Authorizationeh JWT를 통해서 구현이 가능하다.
Authorization 절차
- Authorization 절차를 통해
access_token
을 생성한다. acces_token
에는 유저 정보를 확인 할 수 있는 정보가 들어가 있어야 한다.(ex. user id)
- 유저가 request를 보낼때
access_token
을 첨부해서 보낸다.
- 서버에서는 유저가 보낸
access_token
을 복호화 한다.
- 복호화된 데이터를 통해 user id를 얻는다.
- user id를 사용해서 database에서 해당 유저의 권한(permission)을 확인한다.
- 유저가 충분한 권한을 가지고 있으면 해당 요청을 처리한다.
- 유저가 권한을 가지고 있지 않으면, Unauthorized Response(401) 혹은 다른 에러 코드를 보낸다.