ctyptoJS
`ctyptoJS`는 암호화 표준을 제공하는 자바스크립트 라이브러리이다.
원래는 `redux-persist`를 이용해 저장한 Local Storage 데이터를 암호화하기 위해 crptoJS 라이브러리를 사용해 보려고 했다. 그런데 라이브러리 설명 페이지에 접속해 보니 위 라이브러리는 단종(Discontinued)되었다고 한다.
추가 개발이 이루어지지 않고 더 이상 유지관리 되지도 않는다고 해서, 암호화에는 다른 방법을 사용해 보기로 했다.
단순 Local Storage 암호화 방식으로는 ` React Secure Storage` 설치와 같은 방법이 있는 것 같다. (참고 블로그)
하지만 나는 `redux-persist`로 저장한 데이터를 암호화하려는 것이므로 `redux-persist`를 키워드로 잡고 보안에 대해 검색해 보았다. 그 결과 `redux-persist-transform-encrypt` 라는 라이브러리를 알게 되었다.
redux-persist-transform-encrypt
redux-persisit로 저장한 storage를 암호화시켜주는 라이브러리이다.
설명 페이지의 코드를 참고해 가며 내 프로젝트의 `rootReducer`를 수정해 주었다.
기존 코드
//rootReducer.js
import { combineReducers } from "redux";
import { persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import userReducer from "./user";
import transactionReducer from "./transactions";
import transactionAnalyticsReducer from "./transactionAnalytics";
const persistConfig = {
key: "root",
whitelist: ["user"],
storage,
};
const rootReducer = combineReducers({
user: userReducer,
transactions: transactionReducer,
transactionAnalytics: transactionAnalyticsReducer,
});
export default persistReducer(persistConfig, rootReducer);
👇
수정한 코드
//rootReducer.js
import { combineReducers } from "redux";
import { persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import userReducer from "./user";
import transactionReducer from "./transactions";
import transactionAnalyticsReducer from "./transactionAnalytics";
import { encryptTransform } from "redux-persist-transform-encrypt"; //추가
const persistConfig = {
key: "root",
whitelist: ["user"],
storage,
};
const rootReducer = combineReducers({
user: userReducer,
transactions: transactionReducer,
transactionAnalytics: transactionAnalyticsReducer,
});
//추가
const reducer = persistReducer(
{
...persistConfig,
transforms: [
encryptTransform({
secretKey: "my-super-secret-key",
}),
],
},
rootReducer,
);
export default reducer;
redux-persist-transform-encrypt의 secret ket??
그런데 npmjs 사이트의 라이브러리 설명을 읽어보니, 모든 사람에게 동일한 키를 사용하지 말라고 되어 있다.
그럼 암호를 유저마다 다르게 생성하라는 뜻인 거 같은데...
아래를 문장을 보니 사용자의 access 토큰이나 세션 키를 비밀 키로 사용하라고 되어 있다.
그런데 나의 경우 store의 user 항목에 사용자의 토큰값과 유효기간이 저장되어 있다.
`root Reducer.js`에서 비밀 키를 설정해야 되는데, `root Reducer` 아래에 있는 `userReducer`를 이용해 토큰을 가져와서 사용하는 게 가능한 일인가?🙄
심지어 암호화를 진행하려는 이유도 user 항목의 정보들을 숨기기 위해서인데, 거기에 있는 내용을 꺼내와서 비밀 키로 쓴다니 내 프로젝트 파일 구조로는 아무리 생각해도 적법한 방법이 아닌 것 같았다.
관련한 내용을 찾아보려 했지만 인터넷 상에 이 라이브러리에 대한 정보가 생각보다 많이 없다.
어쩔 수 없이 키를 환경 변수에 따로 저장하는 방식으로 보안을 유지하기로 했다.
//.env
REACT_APP_PERSISTOR_SECRET_KEY=super secret key
//rootReducer.js
const reducer = persistReducer(
{
...persistConfig,
transforms: [
encryptTransform({
secretKey: process.env.REACT_APP_PERSISTOR_SECRET_KEY, //환경 변수 사용
}),
],
},
rootReducer,
);
vercel의 배포된 어플 관리 페이지에 들어가서 환경 변수도 새로 추가해 주었다.
배포까지 잘 완료되었다.
https://social-account-book-frontend.vercel.app/
후기
처음에 토큰 정보를 Local Storage에 저장한 이유는 프로젝트 진행하며 참고했던 강의에서 그렇게 데이터를 저장했기 때문이었다. 유저 정보가 상당히 중요하게 다루어지는 데이터라는 걸 알고 있었지만, 기능 구현에 급급했기에 보안에 대한 신경을 쓰지 못했고, 그래서 데이터를 Local Storage에 저장할 때의 장단점 같은 건 추후 생각하기로 하고 미뤄두었었다.
그러다 얼마 전 멘토링 진행 중 멘토께서 왜 유저 데이터를 Local Storage에 저장했냐고, 사용자의 데이터를 Local Storage에 저장하면 큰일 난다! 는 말씀을 해주셨다. 현업에서는 대신 Session, Cookie 등을 사용한다고 한다. 물론 실무 경력이 많지 않은 주니어 분이었지만 실제 현업에서 그렇게 쓰인다니 내가 보안적인 면에 있어서는 실수를 저질렀구나 하는 생각을 했다.
그래서 이번 기회에 Local Storage에 저장해 사용하고 있던 유저 정보를 다른 방식으로 저장해 보기로 했다.
구현에 앞서 가장 먼저 공부해야 할 부분은 Cookie, Web Storage (Session Storage, Local Storage)의 공통점과 차이점에 대한 분석이었다.
우선 Cookie, Web Storage의 공통점은 둘 모두 해당 도메인에 대한 데이터를 브라우저에 저장하는 저장소라는 것이다.
Cookie
- 웹 서버가 생성하여 웹 브라우저로 전송하는 작은 정보 파일
- 일반적으로 웹사이트 접속시 사용자의 컴퓨터에 다운로드된다
- 이름, 값, 도메인 정보, 경로 정보, 만료 일자 및 시간 등의 데이터를 저장한다.
- 단점 1: 웹 사이트에서 쿠키를 설정하면 이후 모든 웹 요청은 쿠키정보를 포함하여 서버로 전송된다.
- 단점 2: 저장 용량이 작다. (최대 4KB)
- 단점 3: 쿠키는 사용자의 로컬 컴퓨터에 파일 형태로 저장되며, 따라서 클라이언트에서 쉽게 탈취/변경될 수 있어 보안에 취약하다.
- ex) 자바스크립트를 통해 조회가 가능하기 때문에, XSS공격에 취약하다.→ HttpOnly 옵션으로 자바스크립트를 이용한 접근을 차단할 수 있다.
- 다시 보지 않기 팝업 창 구현에 이용
Web Storage (Session Storage, Local Storage)
- HTML5부터 제공하는 기능
- 쿠키의 단점을 보완해 등장한 웹 스토리지를 사용한다.
- 쿠키와 기능은 유사하지만, 클라이언트에 저장만 할 뿐 서버로 전송되지 않는다.
- 지속성에 따라 Session Storage, Local Storage 로 구분한다.
- Local Storage: 브라우저에 반영구적으로 데이터 저장. 브라우저 종료 시에도 데이터가 유지됨 → 자동 로그인 기능 구현에 이용
- Session Storage: 탭 윈도우 단위로 생성됨. 탭을 닫으면 데이터가 삭제됨 → 입력 폼 정보, 비로그인 장바구니 기능 구현에 이용
세션의 장점은 Session 기간 동안만 데이터를 저장하기 때문에 보안 측면에서 유리하다는 것이다.
나의 경우 Local Storage를 사용하기는 했지만, 토큰에 만료기간을 정해 3시간 이후로는 자동 로그아웃 시키고 Local Storage 데이터를 모두 삭제하도록 하며 로컬 스토리지가 지닌 단점(데이터가 로컬에 계속 남아있어 보안에 취약할 수 있다는 단점)을 보완하려 했다.
하지만 그럼에도 멘토님께 지적받은 대로 유저 데이터가 브라우저에 그대로 노출되고 있었기에, 해당 부분을 로컬 스토리지 데이터를 암호화함으로써 개선해 보았다.
토이 프로젝트라는 이유로 보안에 관해서는 소홀하게 생각한 면이 있는데 프로젝트를 개선하기 위해 공부하는 과정에서 다양한 해킹 공격이 실제로 발생하고 있고, 그로 인해 개발자는 항상 개발하는 프로그램의 보안에 대해 경각심을 가져야 한다는 사실도 다시 한번 되새기게 되는 기회였다.
덕분에 `redux-persist`와 `redux-persist-transform-encrypt` 라이브러리를 알게 되었고, 리덕스의 정보를 로컬 스토리지에 담아 브라우저에 저장하는 방법과 그렇게 저장된 데이터를 암호화하는 방법까지 익힐 수 있었다.
또한 이름만 들어보았던 session, cookie, local storage의 개념에 대해서도 깊이 있게 공부할 수 있어 굉장히 유익한 과정이었다고 생각한다.
이번에 배웠던 개념은 꼭 잘 기억해 두었다가 다음 프로젝트에 적용해 보아야겠다.
➕
로그인 처리하는 방법 중에 Local Storage를 사용하지 않고도 로그인하는 방법이 있길래 가져와 봤다... 나중에 이 방법으로도 시도해 봐야겠다.
공부에 참고한 자료