쿠키란 클라이언트 측에서는 Cookie, 서버 측에서는 Set-Cookie 라는 헤더의 값을 통해 클라이언트와 서버가 정보를 전달하는 방식이다.
Authentication 은 Authorization 과 구분된다. 인증 (Authentication) 은 아래의 여기서 설명하고 있는 키값을 통한 로그인을 의미하며 인가 (Authorization) 는 간단히 보면 이렇게 인증된 것만으로 인가여부가 결정될 수도 있으나 여기에 더해서 관리자, 일반사용자 와 같이 role 에 따른 권한 부여 절차까지를 의미한다.
cookie session 방식:
세션 쿠키 (만료 시간 지정하지 않은 쿠키) 와 서버측의 세션 저장소를 통한 인증 방식. 서버측으로 인증정보 (아이디, 비번) 을 전송하면 서버에서는 Set-Cookie 헤더를 통해 클라이언트로 암호화 해싱된 세션키 값을 내려준다. 클라이언트에서는 Cookie 헤더에 매 http 요청시 마다 암호화 해싱된 세션키값을 넣어 서버측에 요청을 보낸다. 서버는 해당 사용자의 아이디와 세션키값의 룩업테이블 (세션저장소) 의 정보를 비교하여 일치하면 인증된 것으로 간주하고 해당 사용자의 이름이나 닉네임, 나이 등의 정보를 데이터베이스로 부터 가져와서 돌려주거나 또한 글쓰기 같은 것을 허용 또는 거부같은 작업을 해준다.
token 방식:
토큰은 Json Web Token 규약을 대부분 사용한다. 클라이언트 측에서 서버측으로 인증정보 (아이디, 비번) 을 전송하면 서버에서는 토큰을 응답으로 내려준다. 해당 토큰을 Authorization: Bearer ...JWT... 와 같이 매 http 요청시 마다 Authorization 헤더에 담아 서버측에 요청을 보낸다. 서버는 해당 토큰에 담긴 정보를 해석하여 유효한 토큰일 경우 인증된 것으로 간주하고 그에 따른 작업을 수행한다.
앱, 웹브라우저 와 같이 다양한 플랫폼을 대상으로 하는 경우 토큰 방식이 더욱 유용하다. 백엔드 서버의 스케일 확장에 있어서도 Stateless 한 방식을 사용하는 것이 좋다. token 방식은 Stateless 가 가능하다. 세션을 사용할 경우 또한 서버 측에 세션 저장소 라는 것을 가지고 있어야 하며 Stateful 하다. http 가 websocket 과 다르게 클라이언트와 서버의 연결이 지속되는 것이 아니기 때문에 매번 요청시 마다 인증정보를 보내는 것에 있어서는 동일하지만 세션방식은 그 시점에 백엔드 서버가 세션저장소를 이용하여 상태를 유지하는 방식이기 때문에 Statuful 한 것이다. JWT 의 경우 토큰 안에 관련 정보가 저장되기 때문에 별도의 정보를 서버측에 필수적으로 저장할 필요는 없다.
OAuth2 또한 JWT 을 사용하지만 access_token 과 만료일이 긴 refresh_token 을 함께 사용하여 access_token 은 refresh_token 과 함께 추가정보를 통해 발급받을 수 있게 하여 보안을 강화한다. OAuth2 글을 참고하면 전체적인 구조를 볼 수 있다. thewavelet.tistory.com/30 이 글도 참고해보라. openid connect 는 인증을 위한 oauth2 의 최상위 레이어이다. 페북, 구글, 네이버, 카카오 소셜 로그인 시 나오는 화면을 openid 라고 보아도 될 것이다. oauth2 는 반면 해당 로그인 후 글을 쓰거나 관련 정보를 얻거나 하는 것에 대한 인가를 하는 부분이라 할 수 있다. 어떤 행위가 인가된 토큰을 발행하는 것. velog.io/@jakeseo_me/Oauth-2.0과-OpenID-Connect-프로토콜-정리 , d2.naver.com/helloworld/24942 이 블로그들에 괜찮은 설명들이 있다.
next.js Server Side Rendering 과 관련하여 아래와 같은 cors, credentials 문제가 고려될 수 있다. SSR 은 브라우저, 백엔드서버 외에도 프론트 서버라는 것이 기능하는 부분이 많기 때문에 프론트 서버와 백엔드 서버 간의 이슈도 고려되어야 한다.
express 백엔드 서버
// origin: true 로 해두면 * 대신 보낸 곳의 주소가 자동으로 들어가 편리하다.
// 실제로는 이렇게 cors 를 열어두면 좋지 않다. proxy 방식으로 메인 도메인으로 요청시 백엔드로 요청을 넘겨주고 받는 식으로 해준다.
// 백단 api 서버 측에서 cors 를 열어두면 해킹의 위협이 높아질 수도 있기 때문에 보통은 프록시 방식을 사용한다.
// [[[ cors 는 브라우저의 정책이다. ]]] 브라우저가 자신이 자원을 받아온 곳 외의 도메인(포트 포함) 으로 요청을 할 경우 기본적으로 그 요청 응답을 막아버린다.
// [[[ 서버 대 서버 (프론트서버, 백엔드서버)의 요청에서는 cors 라는 것이 존재하지 않는다. ]]] 브라우저를 해킹한다면 cors 가 발생하지 않을 것이다.
// 서버 측에서 res.setHeader('Access-Control-Allow-Origin', '*'); 해주는 것이 app.use(cors({origin: '*'})); 와 같은 것.
// 그냥 app.use(cors()); 해주면 위와 같다.
// 서버 측에서 Access-Control-Allow-Origin 을 헤더에 담아 응답주면 브라우저는 cors 를 허용해준다.
app.use(cors({
origin: 'http://localhost:3060',
// [[[ Cookie 또한 프론트가 백단과 도메인이 다르면 서로 전달이 안된다. credentials 가 그것도 허용해주는 옵션. ]]]
// [[[ credentials 의 경우 서버 대 서버 의 요청에도 적용된다. ]]] 즉, 기본적으로는 도메인이 다르면 쿠키가 동작하지 않는다. credentials 를 true 해주어야 한다.
// res.setHeader('Access-Control-Allow-Credentials', true); 와 같다.
// xhr 의 경우 xhr.withCredentials = true; fetch 의 경우 fetch(url, { credentials: true }); 와 의미가 같다.
// 따라서 프론트 단의 소스에서도 axios 의 옵션에 withCredentials: true 를 넣어주어야 한다. axios.defaults.withCredentials = true;
// 그리고 이렇게 withCredentials 를 true 로 하면 Access-Control-Allow-Origin 은 * 가 될 수 없다.
// 백 단과 프론트 단이 민감한 점보를 주고 받으므로 * 보다 특정 도메인을 지정해주어야 한다. * 하면 브라우저에서 에러를 준다.
credentials: true, // 기본값은 false 이다.
}));
next.js 프론트엔드 서버
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { END } from 'redux-saga';
import axios from 'axios';
import wrapper from '../store/configureStore';
import { LOAD_ME } from '../reducers/user';
import { LOAD_ARTICLES } from '../reducers/article';
const Home = () => {
const dispatch = useDispatch();
const { me } = useSelector((state) => state.user);
return (
...
);
};
// next 8 버전에서는 getInitialProps 라는 것을 사용했었는데 이는 조만간 없어질 듯 하다.
// next 9 에서는 다음이 추가되었다. getStaticProps, getStaticPath, getServerSideProps
// 이 메소드가 ssr 위해서 위의 리액트 코드 수행 전에 서버측에서 먼저 수행되어 모두 셋팅된 후 클라이언트로 내려준다.
export const getServerSideProps = wrapper.getServerSideProps(async (context) => {
console.log('getServerSideProps start');
console.log(context.req.headers);
// cors 의 경우 브라우저만의 정책이라 프론트 서버 측에는 고려하지 않아도 되지만
// credentials 의 경우 서버 대 서버의 요청에도 적용되기 때문에 이를 처리해주어야 한다. 기본적으로 도메인이 다르면 쿠키가 동작하지 않기 때문에
// credentials 를 true 해주어야 하며 현재 axios.defaults.credentials = true 로 해준 상태이기 때문에 이 또한 문제는 없다.
// 백엔드 서버에서도 Access-Control-Allow-Credentials 를 true 로 해놓았기 때문에 문제가 없다.
// 문제는 브라우저일 경우 쿠키를 자동으로 담아서 서버에 요청하지만
// ssr 을 위한 next 의 프론트 서버일 경우 이러한 것을 자동으로 해주지 않기 때문에 직접 이를 해주어야 한다.
// 즉, 이 부분에서 쿠키를 담아서 백엔드로 요청을 보내는 작업을 해주어야 한다.
const cookie = context.req ? context.req.headers.cookie : '';
// 이곳은 프론트 서버 측의 수행 로직이기 때문에 각 브라우저에서 쿠키가 셋팅되는 것과 달리 프론트 서버는 여러 사람이 하나의 프론트 서버를 사용하기 때문에
// 아래와 같이 쿠키를 우선 삭제해주고 context.req.headers.cookie 가 존재할 경우에만 axios.defaults.headers.Cookie 에 담아주도록 한다.
// context.req.headers.cookie 가 브라우저에서 프론트서버로 요청할 때의 요청 컨텍스트 (req, res 가진) 이다.
// 이렇게 '' 삭제 처리 안해줄 경우 바로 전의 브라우저에서 로그인 한 사용자의 쿠키가 그대로 적용되어
// 다른 브라우저의 사용자가 바로 전 브라우저의 사용자의 로그인을 가진 상태가 될 수가 있다.
axios.defaults.headers.Cookie = '';
if (context.req && cookie) {
axios.defaults.headers.Cookie = cookie;
}
context.store.dispatch({
type: LOAD_ME,
});
context.store.dispatch({
type: LOAD_ARTICLES,
});
// saga 에서 END 라는 액션을 불러와서 dispatch 해준다.
// 이렇게 해야 saga 에서 실제 백엔드 서버 쪽 요청 로직을 응답까지 받아서 모두 셋팅해준다.
// 이걸 안해주면 위의 saga 를 dispatch 하지만 완료될 때까지 기다리지 않고 브라우저로 전송하기 때문에 리덕스에 원하는 값이 들어있지 않게된다.
context.store.dispatch(END);
console.log('getServerSideProps end');
// configureStore.js 에 sagaTask 를 store 에 등록하였다. ssr 을 위해서.
await context.store.sagaTask.toPromise();
});
export default Home;
security.stackexchange.com/questions/81756/session-authentication-vs-token-authentication
dzone.com/articles/cookies-vs-tokens-the-definitive-guide
hackernoon.com/using-session-cookies-vs-jwt-for-authentication-sd2v3vci
auth0.com/blog/refresh-tokens-what-are-they-and-when-to-use-them/
'REACT & NODE' 카테고리의 다른 글
package-lock.json vs yarn.lock (0) | 2021.04.20 |
---|---|
oauth2 (0) | 2021.04.03 |
대륙의 실수 ( vue, antd, echarts ) (0) | 2021.04.01 |
express 와 async await 그리고 error 처리 (0) | 2021.03.20 |
React Native vs Android / Swift (0) | 2021.03.11 |