인증은 웹서비스를 개발할 때 필수적으로 필요한 기능이다. 사용자마다 원하는 정보, 컨텐츠를 다르기 때문에 이를 다르게 보여주려면 사용자를 인증하는 기능이 필요하고 서버에서는 누구의 요청인지 구분할 수 있어야하기 때문에 요청을 보낸 사용자가 누구인지 단서를 서버에 보내주어야 한다.
웹서비스에서 가장 많이 사용하는 요청방식 중 하나는 HTTP 프로토콜이다.
HTTP 프토토콜 환경은 Connectionless, stateless한 특성을 갖는다.
Connectionless
- 클라이언트가 요청을 한 후 응답을 받으면 그 연결을 끊음
- HTTP는 먼저 클라이언트가 request를 서버에 보내면, 서버는 클라이언트에게 요청에 맞는 response를 보내고 접속을 끊는 특성이 있음
Stateless
- 통신이 끝나면 상태를 유지하지 않음
- 연결을 끊는 순간 클라이언트와 서버의 통신이 끝나며 상태 정보는 유지하지 않는 특성이 있음
위의 두 가지 특성을 해결하기 위해 Cookie와 Session을 사용하게 된다.
해결 방안
- HTTPS를 사용해 탈취해도 정보를 읽기 힘들게 하기
- 세션에 유효기간 넣기
JWT는 'Json Web Token'의 약자로 인증에 필요한 정보들을 암호화시킨 토큰을 뜻한다. JWT는 토큰 안에 유저의 정보를 넣는데, 클라이언트 입장에서는 HTTP 헤더에 SessionID나 Token을 실어서 보내준다는 점에서는 동일하지만, 서버 측에서는 '인증을 위해 암호화를 하냐(Token)', '별도의 저장소를 이용하냐(Session)'의 차이가 발생한다.
Token을 만들기 위한 3가지 (Header, Payload, Verfy Signature)
1. Header : 위 3가지 정보를 암호화 할 방식(alg), 타입(type) 정보
2. Payload : 서버에서 보낼 데이터 정보 (유저 고유 ID, 유효기간 ..)
3. verify Signature : Base64 방식으로 인코딩한 Header, Payload, SECTRET KEY를 더한 후 서명
- JWT를 발급한 후 검정만 하면 되기 때문
- Stateless한 서버를 만들 때 강점이며, 서버를 확장하거나 유지, 보수하는데 유리
- Token을 기반으로 하는 다른 인증 시스템에 접근 가능
이미 발급된 JWT에 대해서는 돌이킬 수 없음
Session/Cookie의 경우 해당 Session을 지워버리면 그만이지만, JWT는 유효기간이 완료될 때 까지는 계속 사용 가능
해결방안
- 기존의 Access Token의 유효기간을 짧게 하고 Refresh Token이라는 새로운 토큰을 발급
Payload의 정보가 제한적이다. Payload는 따로 암호화하지 않기 때문에 디코딩하면 누구나 정보를 확인 가능
JWT의 길이가 길어서 인증 요펑이 많이질 수 록 서버의 자원낭비 발생
팀원 분들의 구현으로 로그인 기능에 대한 정보를 알 수 있었다. 이번 풀스택미니프로젝트를 통해서 로그인 기능 구현의 종류와 JWT 방식에 대해서 알 수 있었다.
### 회원가입 ###
pw_hash = hashlib.sha256(pw_receive.encode('utf-8')).hexdigest() # password 인코딩
db.user.insert_one({'id': id_receive, 'pw': pw_hash}) # user의 ID와 인코딩한 password DB에 저장
### 로그인 인증 ###
# 클라이언트에서 받은 password를 회원가입 때와 같은 방법으로 암호화
pw_hash = hashlib.sha256(pw_receive.encode('utf-8')).hexdigest()
# ID와 암호화된 password를 가지고 해당 유저를 찾음
result = db.user.find_one({'id': id_receive, 'pw': pw_hash})
# 찾으면 JWT 토큰을 만들어 발급
# JWT 토큰에는, payload와 시크릿키가 필요
# 시크릿키가 있어야 토큰을 디코딩(=풀기) 해서 payload 값을 볼 수 있습니다.
# 아래에선 id와 exp를 담았습니다. 즉, JWT 토큰을 풀면 유저ID 값을 알 수 있습니다.
# exp에는 만료시간을 넣어줍니다. 만료시간이 지나면, 시크릿키로 토큰을 풀 때 만료되었다고 에러가 납니다.
payload = {
'id': id_receive,
'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=18000) # 토큰의 만료시간 설정
}
token = jwt.encode(payload, SECRET_KEY, algorithm='HS256') # SECRET_KEY를 사용해 인코딩
패키지 : PyJWT, datetime
$.ajax({
type: 'POST',
url: '/api/login',
data: { id_give: id, pw_give: passwd },
success: function (response) {
console.log(response);
if (response['result'] == 'success') {
// 로그인이 정상적으로 되면, 토큰을 받아옵니다.
$.cookie('mytoken', response['token']); // 이 토큰을 mytoken이라는 키 값으로 쿠키에 저장합니다.
alert('로그인 완료!');
window.location.href = '/category';
} else {
// 로그인이 안되면 에러메시지를 띄웁니다.
alert(response['msg']);
}
},
});
어렵기만 하고 막막하기만 했던 JWT를 좋은 팀원 분들을 만나 모르는 부분을 물어보고 짜신 코드들을 읽어보면서 많은 것을 배울 수 있는 시간을 가졌습니다 ! 감사합니다 ! :)
쉽게 알아보는 서버 인증 1편(세션/쿠키, JWT), 그랩의 블로그
쿠키와 세션 개념, RyanGomdoriPooh