📌 6.20 ~ 6.23 : 웹 미니프로젝트
📌 6.24 ~ 6.25 : 알고리즘 문제 해결
첫 만남에 어색한 팀원들과 웹 미니프로젝트를 진행했다. 하지만 팀원들이 텐션이 너무 좋아서 금방 친해졌고 하루 종일 게더에서 같이 문제를 해결하고 하니 더 많이 친해졌다.
항해99에서 첫 협업 프로젝트인 만큼 걱정을 많이 했었는데 팀원 모두가 만족하는 프로젝트 결과물을 도출하여 기뻤다.
👉 github: https://github.com/ferrariRoma/too_shorts
👉 deploy: http://sparta-fr.shop/
부랴부랴 열심히 달려온 3일을 성공적으로 끝내고 알고리즘을 만났다...
사실 알고리즘을 제일 싫어한다..
근데 또 막상 풀어보니 재밌는거 같기도 하다,,😂
JWT란
JSON WEB TOKEN
의 약자로, 사용자 인증 방식이다.
HTTP/1.1 프로토콜의 방식의 특징은 한 커넥션에 하나의 메세지만 주고 받을 수 있고 주고 받고 난 후에는 연결을 끊고 이전에 주고 받은 정보를 저장하지 않고 삭제한다.
서버는 클라이언트가 누구인지 매번 확인을 거쳐야 한다는 것이다 🔥
따라서 각각의 요청이 독립적으로 취급되어 여러 페이지에 걸쳐 흐름이 이어져야 하는 서비스를 구현하기 어려워진다. 이를 해결하기 위해 여러 사용자 인증 방식이 존재하고 사용된다.
쿠키와 세션
을 이용한 사용자 인증 방식은 아래와 같은 절차로 이루어진다.
JWT
를 이용한 토큰 인증 방식은 아래와 같은 절차로 이루어진다.
차이점이 무엇인가?🙄
쿠키 / 세션 방식은 세션을 사용하기 때문에 서버의 리소스를 사용하게 된다. 따라서 사용자가 많아질 경우 서버의 리소스를 많이 사용하게 되기 때문에 서버 과부하를 초래할 수 있다.
반면에 JWT는 token을 발행하고 클라이언트 단에서 그 토큰을 저장하고 사용하면 되기 때문에 서버의 리소스를 사용할 일이 없다.
안정성에서는 세션 기반 인증이 조금 더 유리하다고 볼 수 있다.
서버에서 모든 인증 정보를 관리하기 때문이며, 혹여 세션ID가 탈취된다고 하더라도 서버에서 해당 세션ID를 무효화하면 되기 때문이다.
반면에 토큰 기반 인증에서는 모든 인증 정보를 클라이언트에 저장하기 때문에 보안 측면에서 조금 불리하다. 또한 별도로 payload를 암호화하지 않기 때문에 payload에 민감한 정보를 저장할 수 없다. 저장할 수 있는 데이터가 제한된다는 것이다.
확장성에서는 토큰 기반 인증이 유리하다.
오늘날 웹 어플리케이션의 서버 확장은 보통 수평 확장으로 이루어지는데, 따라서 한 대가 아닌 여러 대의 서버가 요청을 처리하게 된다.
👉 수직 확장과 수평 확장
단일 서버의 스펙을 단순하게 더 좋은 것으로 업그레이드 하는 것이 수직 확장이다. CPU를 더 좋은 제품으로 교체하는 등의 작업을 예로 들 수 있다.
이러한 수직 확장 대신, 단일 서버가 처리하는 일을 여러 대의 서버가 처리하도록 하기 위해 여러 대의 서버를 더 추가하는 것이 수평 확장이다.
수평 확장은 여러 대의 서버를 하나의 서버처럼 사용하기 위한 클러스터링 작업에 대한 비용이 추가적으로 발생한다.
때문에 별도의 작업을 해주지 않는다면 세션 기반 인증 방식은 세션 불일치 문제를 겪을 수 있다.
JWT로 발행한 토큰이 제 3자에게 탈취된다면? 어머나세상에
그러면 제 3자는 그 토큰을 활용해서 서버에 접근하고 여러가지 위험한 일을 할 것이다.
이를 방지하기 위해 생각해볼 수 있는 것은 토큰의 만료시간을 짧게 해보는 것이다.
근데 그러면 사용자의 로그인 효력이 3분만 유지된다면..?? 사용자 고혈압행 답답행
따라서 Refresh token을 추가로 발행하는 방법을 사용하게 된다.
어떤 원리인가??🙄
우선 사용자를 위한 access token을 발행할 때, Refresh token
을 같이 발행한다.
access token은 만료기간을 굉장히 짧게(예를 들면 10분) 설정한다✔
Refresh token은 만료기간을 길게(예를 들면 24시간) 설정한다✔
기존과 비슷하게 access token은 클라이언트에 전송하여 클라이언트 단에서 쿠키, 웹스토리지에 저장하여 사용한다.
Refresh token은 발행하고 난 뒤, DB에 저장한다.
사용자의 access token이 10분뒤에 만료되면 Refresh token을 확인하여 Refresh token이 유효하다면 access token을 다시 발행하여 클라이언트에 전달하는 방식이다.
Refresh token은 DB에 저장하기 때문에 클라이언트 단에 저장되는 access token에 비해 탈취될 위험이 낮다. 조금이라도 access token의 탈취 위험을 낮추기 위해 나는 access token도 클라이언트단에 저장할 때 쿠키를 사용하는 것 보다 웹스토리지에 저장하는 것이 낫다고 생각한다.
웹스토리지가 쿠키보다 보안적으로 더 좋다고 어디서 본 것 같은데,, 저장할 수 있는 용량도 쿠키에 비해서 크고,, 그래서 나도 웹스토리지를 많이 사용하고 있다.
만약 Refresh 토큰이 탈취된다면?😢
제 3자가 Refresh token을 탈취해서 기존 사용자보다 더 빨리 access token을 새로 발행하게 된다면 문제가 발생할 수 있다.
따라서 token을 재발행 할 때 Refresh token과 access token 둘 다 새로 발행하는 방법을 권장하는 것 같다.
근데 저번에 구글링으로 찾아봤을 때, Refresh token을 발행할 때, access token을 발행할 때의 정보와 다 동일하지만 하나 다른 점이 payload를 비워서 Refresh token을 만든다고 본 것 같았는데, 만약 이 방법이면 Refresh token을 새로 발행하더라도 다른 점이 없을 것 같은데,,
이것도 하나의 방법론일 뿐이라면 다른 방법이 있을 것 같다. 나중에 알아봐야겠다..
나는 작년에 학교 졸업작품을 만들었는데 프론트엔드를 담당하였었다.
그때 내가 사용했던 프레임워크는 Vue.js였고, JWT를 사용하여 사용자 인증을 구현했었는데, 그때는 위에서 JWT를 설명했던 것과 같이 클라이언트에서 발행받은 token을 axios로 요청할 때 헤더에 넣어주어 전달하는 방식으로 구현을 했던 기억이 난다. 구글링을 해봐도 보통 그렇게 다 구현을 하는 것을 알 수 있었다.
하지만 이번 프로젝트에서는 프론트엔드는 Jquery로 구성하고 백엔드는 Flask로 구성하였는데, 프론트에서 ajax 요청할 때 헤더에 token을 따로 보내주지 않고 요청을 하였고, Flask에서 요청을 받고난 뒤, Flask에서 클라이언트의 쿠키에 저장되어 있는 토큰을 가져와서 검증하는 방식으로 구현을 했었다.
아래는 Flask에서 쿠키에 접근하는 코드이다.
token_receive = request.cookies.get('mytoken')
이 점이 이상해서 매니저님께 여쭤봤는데 프론트 서버와 백 서버가 같기 때문에 서버에서 클라이언트의 쿠키에 접근할 수 있는 것이라고 하셨다.
가만히 생각해보면 Vue.js나 React.js를 사용하여 개발할 때 나는 항상 yarn start 또는 npm run start 를 입력하여 localhost:3000을 띄우곤 했다. 그리고 서버와 통신할 때는 localhost:5000 또는 다른 포트번호 혹은 API의 Endpoint를 통해 접근했었다.
따라서 CORS에러도 발생했었고, 이것을 해결하기 위해 package.json에 proxy 설정을 해주는 등 조치를 취해 해결했었다.
프론트엔드 서버와 백엔드 서버를 분리해서 작업하고 있었던 것이다.
반면에 이번 플라스크에서는 프론트엔드 서버와 백엔드 서버를 따로 두지 않고 localhost:5000으로 같이 사용했기 때문에 이것이 가능했던 것 이었다.
Application Programming Interface의 약자로, 컴퓨터와 컴퓨터 사이의 연결이다.
우리는 API 통신을 통해 클라이언트에서 서버에 데이터를 요청하고 서버에서 응답으로 데이터를 보내주면 클라이언트에서 데이터를 받아 가공하여 사용하는 방식으로 어플리케이션을 구현한다.
GET, POST, DELETE, UPDATE 등의 method를 사용하여 ajax, fetch, axios 등을 통해 API 통신을 하게 된다.
조금 더 찾아보니 API의 역할로 아래와 같은 정보를 찾을 수 있었다.
API는 모든 접속을 표준화하기 때문에 기계/ 운영체제 등과 상관없이 누구나 동일한 액세스를 얻을 수 있단다. 음 그렇단다.
포스팅 수정 기능을 구현할 때 수정 페이지로 이동하기 위해 POST 요청으로 데이터를 넘겨주면서 render_template()을 이용하여 html 파일을 렌더링 해주려고 하였는데 405 코드
에러가 떴었고
method not allowed
가 화면에 출력되었다..
기존에 POST 요청을 위해
@app.route('/api/login', methods=['POST'])
# ...생략
이렇게 했었는데, 저 에러의 해결 방법은 아래와 같았다.
@app.route('/api/login', methods=['GET', 'POST'])
def test():
if request.method == 'POST':
# ...생략
return render_template('test.html');
elif request.method == 'GET':
# ...생략
return render_template('test.html');
GET메서드와 POST메서드를 둘 다 다뤄서 해결을 하는 방식이었다.
이 방법으로 문제는 해결했지만 팀원이 굳이 POST요청을 할 필요가 없지 않냐고 의견을 내어서 생각해보니 정말 그럴 필요가 없었다. 왜 굳이 POST요청을 고집하고 ajax 요청에 목을 매었는지...ㅎ
그래서 따로 ajax요청 없이 location.href()로 이동하고, 이동 시 데이터를 쿼리로 넘겨주는 방식으로 아래와 같이 프로젝트를 진행하였다.
@app.route('/modify/<posting_number>')
def modify(posting_number):
# 토큰이 있을 때 nickname을 넘겨줌
try:
token_receive = request.cookies.get('mytoken')
# ...생략
return render_template('modify.html') # 일부 생략
except jwt.exceptions.DecodeError:
return render_template('login.html', state='logout')
팀원들과 같이 협업을 해보며 느낀 것은,, 같은 프로젝트를 완성하기 위해 팀원들과 같이 해결하고 공부하다 보니 역시 정말 다방면으로 많이 배우게 된다. 팀원들과 에러를 공유하고 같이 해결하는 과정에서 정말 많은 것을 배우고 경험할 수 있었다.
항상 말을 할 때도 팀원의 입장을 먼저 생각하여 말을 하게 되고, 팀원의 기분을 고려하여 정중하게 거절도 해보고 거절하는 과정에서 이유를 논리적으로 설명하여 팀원을 설득 시켜보는 등 이번 1주차에서 값진 경험을 많이 한 것 같다.
내가 추구하는 협업할 수 있는 개발자에 한 발 다가간 느낌이 들었다.😁
그리고 같이 열심히 해준 팀원들에게 정말 감사하다(샤라웃)
혼자 Git을 사용할 때는 항상 사용하던 명령어만 사용하기 때문에 별로 어려움이 없는데, 협업을 하게 되면 브랜치, 수 많은 커밋, 충돌, pull로 인한 merge 등 혼자 할 때와는 다르게 생각할게 너무 많아져서 헷갈리곤 했다.
그래서 Git에서 커밋 되돌리기, push, merge등을 할 때마다 잘못될까봐 손이 덜덜 떨렸다 ㅋㅋ
하지만 이번 프로젝트를 하면서 Git을 많이 사용해봤고, 이전부터 Git을 사용해왔던 팀원과 함께 스크린을 공유하면서 Git을 공부했는데, 이것이 많이 도움이 된 것 같다. 확실히 이전 보다는 Git에 대한 자신감이 붙었다.
이번에 제공된 Git 강의를 참고해서사용법에 대해 완벽하게 익히고 여러 프로젝트를 통해 Git을 사용해보면서 숙련도를 더 끌어올려야할 것 같다.