(막간 보충 공부중)
프로그래머스 연습 문제들을 풀어보는 스터디를 즉흥 결성한 후 사흘 차, 오늘도 튜터에게 찾아갔고 좋은 경험을 하였다. 특히나 현업 개발자는 어떤 사고방식으로 수정할 부분을 감지하고 리팩토링을 하는지를 소소하게 견식할 수 있어서 좋았다. 혼자 찾아갔으면 과분할 뻔 하였는데 그룹으로 찾아가니 좋았고, 첫 문제 푸는데 진짜 오랜 시간이 걸렸는데 흔쾌히 다음 문제까지 풀자 해주신 튜터께 감사했다.
한 일:
- 프로그래머스 30분 https://school.programmers.co.kr/learn/courses/30/lessons/120923 ⇒ 1시간..!
- 프로그래머스 30분 https://school.programmers.co.kr/learn/courses/30/lessons/120812 ⇒ 2시간..!
- 하다 만 TypeORM 강의 노트 독파 및 코드 작성하기.
- Refresh token에 대한 글 읽어보기 -??분 - https://stackoverflow.com/questions/32060478/is-a-refresh-token-really-necessary-when-using-jwt-token-authentication
- 튜터에게 추천받은 refresh 토큰 관련글인데, 궁금증을 반은 해결하고 반은 다시 엄청난 질문 다발이 생겨버렸다. 아래 토글 섹션에 질문들을 정리해두었다. 여러 사이트를 훑고 질문을 정리하고, 몇 주간의 검색과 구글링 경험까지 합친 결과 이런 결론을 얻게 되었다: 시간만 된다면 딱딱할지라도 언제나 공식 문서, RFC 7964 같은 문서가 최선이고 이것을 독파할 것. 블로그를 (그리고 우왕좌왕하는 stackOverflow 무리를) 찾아다니며 질 좋은 정보를 ‘선별’하는 노고보다 이게 낫다.
참고(하면 좋을) 사이트
(Matter.js - 2D 물리 엔진)
(데이터 사이언스 개괄 튜토리얼)
⇒ 처음 몇 분을 듣다가, 내가 하고 싶은 건 아무 data analysis가 아니라 특정한 도메인의 데이터를 다루고 싶은 거라는 것을 알았다. 아무 데이터나 다룬다고 하면 흥미가 전혀 없다. 무엇일지는 모르겠지만 특정한 분야의 문제에 먼저 흥미를 갖고 그걸 들여다보려다 보니 밟게 되는 스텝이 데이터 분석, 데이터 과학이게 되는 흐름이어야 할 것 같다. 내가 관심있게 되는 주제와 문제란 뭘까?
(공유받은 Node.js 책 코드 모음)
최빈값 구하기
최빈값 구하기
function solution2(array) {
// 길이가 1이면 일단 반환
if (array.length === 1) { // 0 < array의 길이 라고 했으므로.
return array[0];
}
// 나타나는 숫자마다 카운팅한 맵핑 객체 만들기
const countEachNum = {};
for (let value of array) {
if (value in countEachNum) {
countEachNum[value]++;
} else {
countEachNum[value] = 1;
}
}
// // => 이걸 더 단순하게 나타내면:
// for (const value of array) {
// countEachNum[value] = (countEachNum[value] || 0) + 1; // wow
// }
// 나타나는 숫자 종류가 한 개 뿐이면 그대로 반환
if (Object.keys(countEachNum).length === 1) {
return array[0];
}
// 카운트한 '개수'(=countEachNum의 '값'들) 기준으로 내림차 정렬 후
const entries = Object.entries(countEachNum);
entries.sort((a, b) => b[1] - a[1])
// 최빈값이 둘이면 tie로, -1 반환. 아니면 최빈값 반환
if (entries[0][1] === entries[1][1]) { // 더블 인덱싱 같이 복잡해지는 경우는 의미있는 변수명에 담아서, 읽는 사람이 이해하기 쉽도록 해주는 게 좋다.
return -1;
}
return +entries[0][0]; // 여기도, 더블 인덱싱이므로 의미있는 변수에 담고 그걸 반환시켜주면 좋다.
}
나 혼자선 이렇게 풀었던 코드를 튜터와 수정하여 이렇게 만들었다:
function solution7(arr) {
const countObj = {};
arr.forEach((value) => {
countObj[value] = countObj[value] ? ++countObj[value] : 1;
})
const max = Math.max(...Object.values(countObj));
const result = Object.entries(countObj).filter(([_, count]) => {
return count === max;
});
const [key] = result[0];
return result.length > 1 ? -1 : key; // key = result[0][0]
}
배운 것:
- result[0][0]같은 더블 인덱싱을 계속 쓰는 것은 가독성에 좋지 않다.
- 더블 인덱싱이 자꾸 나타나는 것을 피하려면, 구조 분해 할당을 이용해 가져오는 기법을 사용하면 좋다.
// 이전 return result.length > 1 ? -1 : result[0][0] // 리팩토링 후 const [key] = result[0]; return result.length > 1 ? -1 : key;
- “객체에 키가 존재하면 1을 더하고 존재하지 않으면 그 키로 1을 할당한다.” 를 3항 연산자로 간단히 표현하고자 할 때, 주의할 점: ++를 전치로 사용해줄 것!
// 이전 for (let value of array) { if (value in countEachNum) { countEachNum[value]++; } else { countEachNum[value] = 1; } } // 리팩토링 후 array.forEach((value) => { countObj[value] = countObj[value] ? ++countObj[value] : 1; })
연속된 수의 합
연속된 수의 합
나 혼자 푼 것:
function solution2(num, total) {
const result = [];
let median = Math.floor(total / num);
for (let i = 0; i < num; i++) {
if (median === total / num) { // 혹은 'num이 홀수일 때'
result.push(median - Math.floor(num / 2) + i);
} else {
result.push(median - Math.floor(num / 2) + 1 + i)
}
}
return result;
}
mob programming으로 푼 것(더 간단하고 깔끔하게 고친):
function solution7(num, total) {
const result = [];
const median = total / num;
const start = Math.ceil(median - num / 2);
for (let i = start; i < start + num; i++) {
result.push(i);
}
return result;
}
Refresh 토큰에 대하여
Refresh 토큰에 대하여
Refresh token에 대한 위의 글을 읽어보다가 생긴 궁금증을 기록해 놓았다.
For every authenticated user (in case of a mobile app, generally), a one to one mapped refresh token and access token pair is issued to the app. So at any given point in time, for a single authenticated user, there will be only one access token corresponding to a refresh token.… Identifying the anomaly, the server would destroy the refresh token in question and along with it all, it's associated access tokens will also get invalidated.
- access token은 분명히 stateless인데, refresh token과 1대 1 매핑이 된 ‘상태’를 서버가 저장하고 있는다니 이게 무슨 말이고 어떻게 구현 가능한 거지?
- 해커가 새로운 refresh-access 토큰 페어를 만들어내자마자 이상을 감지할 수 있게 된다는 건 그렇다 쳐도, 어떻게 이미 발급한 access token들까지 싹 다 파괴할 수 있다는 거지?
(읽어볼) 공식 문서 모음
- OAuth 2.0 for Browser-Based Apps
: 모바일이 아닌 웹 애플리케이션에도 OAuth(와 access 토큰)를 안전하게 사용하는 법에 관하여. 최신 동향 반영.
- OAuth 2.0 Security Best Current Practice - Refresh Token Protection
: RFC 6749에 최신 동향을 더한 best practice 권유
- RFC 6749 : The OAuth 2.0 Authorization Framework
: access / refresh 토큰 기본 (2012 정립)
이것부터 진입하기 :
+--------+ +---------------+ | |--(A)------- Authorization Grant --------->| | | | | | | |<-(B)----------- Access Token -------------| | | | & Refresh Token | | | | | | | | +----------+ | | | |--(C)---- Access Token ---->| | | | | | | | | | | |<-(D)- Protected Resource --| Resource | | Authorization | | Client | | Server | | Server | | |--(E)---- Access Token ---->| | | | | | | | | | | |<-(F)- Invalid Token Error -| | | | | | +----------+ | | | | | | | |--(G)----------- Refresh Token ----------->| | | | | | | |<-(H)----------- Access Token -------------| | +--------+ & Optional Refresh Token +---------------+ Figure 2: Refreshing an Expired Access Token The flow illustrated in Figure 2 includes the following steps: (A) The client requests an access token by authenticating with the authorization server and presenting an authorization grant. (B) The authorization server authenticates the client and validates the authorization grant, and if valid, issues an access token and a refresh token. (C) The client makes a protected resource request to the resource server by presenting the access token. (D) The resource server validates the access token, and if valid, serves the request. (E) Steps (C) and (D) repeat until the access token expires. If the client knows the access token expired, it skips to step (G); otherwise, it makes another protected resource request. (F) Since the access token is invalid, the resource server returns an invalid token error. Hardt Standards Track [Page 11]
RFC 6749 OAuth 2.0 October 2012 (G) The client requests a new access token by authenticating with the authorization server and presenting the refresh token. The client authentication requirements are based on the client type and on the authorization server policies. (H) The authorization server authenticates the client and validates the refresh token, and if valid, issues a new access token (and, optionally, a new refresh token). Steps (C), (D), (E), and (F) are outside the scope of this specification, as described inSection 7.
- " Unlike access tokens, refresh tokens are intended for use only with authorization servers and are never sent to resource servers.” ⇒ API 엔드포인트가 나뉘어져 있는 것은 다른 서버라고 볼 수 있는가?
- 그렇든지 아니든지, 어떻게 쿠키에 담겨 있는 것을 어떤 API에는 보내고 어떤 API에는 안 보낼 수가 있는지?
- " Unlike access tokens, refresh tokens are intended for use only with authorization servers and are never sent to resource servers.” ⇒ API 엔드포인트가 나뉘어져 있는 것은 다른 서버라고 볼 수 있는가?
- access와 refresh 토큰 모두 LocalStorage말고 httponly 쿠키에 담겨서 보관되어야 한다. (https://stackoverflow.com/questions/27726066/jwt-refresh-token-flow)
- ‘httponly’에 ‘담긴다’고?
- localstorage는 정확히 뭔데 그렇게 취약하다는 건지
- 어쨌든 쿠키에 담아둔다는 것은 매 요청마다 자동으로 요청 헤더에 담겨간다는 건데, 무슨 refresh 토큰은 ‘필요할 때만’ 보내서 요청하기가 된다는 건지?
- refresh 토큰의 hash를 DB에 저장해두어야 한다고 한다. 그리고선 유저가 보내온 refresh 토큰을 또 hash화해서 저장된 값과 비교하라고.
- ‘refresh token rotation’이라는 기법도 보안 향상에 도움된다고.
- access와 refresh 토큰 모두 LocalStorage말고 httponly 쿠키에 담겨서 보관되어야 한다. (https://stackoverflow.com/questions/27726066/jwt-refresh-token-flow)
- server side redering을 하는 서버는 어떻게 로그인 유저를 알아보는가?!
클라이언트로부터 토큰을 쿠키로 받아오는 방법 뿐이 맞다고 한다! 음하하하
- ‘auth API’ 도메인과 SSR서버의 도메인이 같아야 쿠키가 전달된다고 함. ⇒ 쿠키는 어떻게 ‘자동으로’ 전달이 되는가?
- 한 번 그렇게 SSR로 만들어지진 페이지를 받아서 사용하는 유저는 그 앱을 ‘Single page app’으로 사용하는 것과 같다고 하는데… 뭔가 ‘새롭게 유저 인증이 필요한 요청은 처리할 수 없는 페이지 상태’, ‘한 번 받으면 그 때 받은 데이터로만 살아가야 하는 정적 페이지’, 이정도로 이해해도 되려나?
Uploaded by N2T