이번 프로젝트 때에는 처음으로 백엔드와 협업을 하게 되었다.
첫 협업이라 그런지 가장 막막했던 부분은 프론트의 영역과 백엔드의 영역을 정확하게 모르겠다는 점이다.우리가 어디까지 해야하고 프론트는 백엔드와 어떻게 연결이 되는걸까..
우선 각각의 영역은 이글을 보니 도움이 되었다.
https://han-um.tistory.com/17
절차를 보면서 프론트에서 해야할 일이 어느정도 정리가 되었다.
나의 작업순서
1. 가장기본적인 뼈대를 만들자.
2. 본격적인 기능구현 : 회원가입 유효성 검증 및,로그인 아이디와 비밀번호 등 백엔드로 보낼 데이터 형식으로 준비해 놓기
3. mocking data로 클라이언트가 입력한 데이터가 잘 넘어가는지 테스트하기
4.api가 나오면 mocking data url을 api로 바꿔준다.
5. 로그인시 서버에서 보내주는 토큰을 쿠키에 저장한다.
6. 필요할때마다 쿠키에서 토큰을 꺼내 헤더에 담아서 보낸다.
처음해보는부분이 4,5,6번이었고, 이에대해 정리를 해보려고 한다.
export const loginDb = createAsyncThunk("post/loginDb",
async (db) => { //로그인 아이디와 비밀번호를 포스트요청으로 보낸다.
try {
const response = await axios.post(
`url..........`,
db
);
const accessToken = response.data.token;
return response.data;
우선 나는 리덕스 thunk라는 미들웨어를 사용했고, axios로 통신하였다.
post요청의 response를 콘솔로 찍어보면 토큰이 담겨져 있는걸 확인할 수 있다.
그전에 토큰에 대해 알아보았다.
<토큰에 대하여>
내가 사용한 토큰은 accesstoken이다.토큰에는 access토큰과 refresh토큰이 있다.
Access Token(JWT)를 통한 인증 방식의 문제는 만일 제 3자에게 탈취당할 경우 보안에 취약하다는 점이다.유효기간이 짧은 Token의 경우 그만큼 사용자는 로그인을 자주 해서 새롭게 Token을 발급받아야 하므로 불편하다. 그러나 유효기간을 늘리자면, 토큰을 탈취당했을 때 보안에 더 취약해지게 된다..
이때 “그러면 유효기간을 짧게 하면서 좋은 방법이 있지는 않을까?”라는 질문의 답이 바로 "Refresh Token"이다.
Refresh Token은 Access Token과 똑같은 형태의 JWT이다. 처음에 로그인을 완료했을 때 Access Token과 동시에 발급되는 Refresh Token은 긴 유효기간을 가지면서, Access Token이 만료됐을 때 새로 발급해주는 열쇠가 된다.(여기서 만료라는 개념은 그냥 유효기간을 지났다는 의미이다.)
예를들어, Refresh Token의 유효기간은 2주, Access Token의 유효기간은 1시간이라 한다면, 사용자는 API 요청을 하다가 1시간이 지나게 되면, 가지고 있는 Access Token은 만료된다. 그러면 Refresh Token의 유효기간 전까지는 Access Token을 새롭게 발급받을 수 있다.
따라서 최근에는 이 두개를 혼용하는 방법을 많이 사용하고 있다.
서버로 부터 받은 토큰을 변수로 만들어주었다.
const accessToken = response.data.token;
본격적으로 쿠키를 저장하는 것만이 남았다.
react-cookie라는 라이브러리가 필요하다
yarn add react-cookie
리액트 쿠키를 사용하는 방법에는 hook을 사용하는 방법과 함수를 만들어서 사용하는 방법이 있다. 내가 사용한 방법은 함수를 만들어서 사용하는 방법이고, hook을 사용한다면 공식문서를 참조하면 예시가 잘 나와있다.
쿠키를 저장,삭제 정도만 한다면 함수를 만들어 사용하는것이 편리한 방법인거 같다.
그다음 cookie.js라는 파일을 새로 만든뒤
쿠키 관련 함수를 만들어주었다.
//패키지 임포트
import { Cookies } from "react-cookie";
const cookies = new Cookies();
//쿠키에 값을 저장할때
export const setCookie = (name, value, option) => {
return cookies.set(name, value, { ...option });
};
//쿠키에 있는 값을 꺼낼때
export const getCookie = (name) => {
return cookies.get(name);
};
//쿠키를 지울때
export const removeCookie = (name) =>{
return cookies.remove(name);
}
쿠키를 삭제할때는 setcookie함수를 사용할 수도 있는데. 옵션에는 쿠키의 만료기한이 들어간다. 여기에 -1을 해주면 쿠키가 지워지게 된다.
setCookie(~~~~,~~~~,-1)
쿠키를 사용할 페이지에 함수를 임포트 해준다.
나는 axios로 서버에서 reponse를 받은뒤 response에서 토큰을꺼내 바로 쿠키에 저장을 해주었다.
export const loginDb = createAsyncThunk("post/loginDb",
async (db) => { //로그인 아이디와 비밀번호를 포스트요청으로 보낸다.
try {
const response = await axios.post(
`url`,
db
);
//reponse에서 토큰값을 꺼낸다.
const accessToken = response.data.token;
//setcookie함수의 첫번째 인자는 쿠키이름, 두번째 인자는 넣을 값이다.
setCookie("is_login", `${accessToken}`);
console.log(response);
return response.data;
쿠키가 저장되었다.
콘솔창에 해당 명령어를 치면 저장된 쿠키를 확인할 수 있다.
document.cookie
처음에 헤더에 담는다고?? 왜 헤더에 담아서 보내는 걸까 했다. 헤더에 토큰 정보를 담아서 보냄으로써 누가 요청을 했는지 알수 있게 된다.
<쿠키란 무엇일까>
쿠키는 클라이언트가 서버로 요청을 보낸뒤, 그에대한 지속 필요정보를 서버에서 쿠키에 담아 보낸다. 이에 클라이언트는 pc에 저장한 뒤 서버에 요청을 보낼 떄마다 쿠키를 같이 보낸다.그래서, 쿠키에 대한 정보를 매 통신 시 Header에 추가하여 보내기 때문에 상당한 트래픽을 발생시킨다. 결론으로는 쿠키는 클라이언트에 저장되어 보안이 취약하며, 클라이언트 pc에 저장되는것이기 때문에 브라우저를 종료해도 계속 남아있다.
(참고로,httpOnly라는 속성을 활성화하면 자바스크립트를 통해 쿠키를 조회할수 없으므로 악성 스크립트로부터 안전하다. 그대신 CSRF라는 공격에 취약해진다.)
보안을 위해서 클라이언트가 아닌 서버에서 쿠키를 직접 저장하는 방법이 있는데, 직접 저장을 할 경우 프론트와 백엔드가 같은 도메인을 사용해야 하며, https환경에서만 작동되기 때문에 로컬환경에서 테스트하기가 어려운 상황이 된다. 따라서 나와 같이 배우는 단계에서 미니프로젝트를 하는거기 때문에 프론트에서 저장하는 방법을 택하게 되었다.
쿠키외에 세션id라는 방식도 있다. 서버가 클라이언트마다 개별의 세션 ID를 부여하고, 클라이언트는 요청할 때마다 세션 ID를 서버에 전달한다. 서버는 받은 세션 ID로 클라이언트 정보를 가져와 활용한다. 장점은 쿠키보다 보안성이 뛰어나다는 것이며, 단점으로는 쿠키에 비해 서버에서 부담하는 처리량이 늘어난다는것이다. 즉, 사용자가 많아지면 서버 측 부하가 늘어난다. 이러한 특징으로 속도도 쿠키보다 느리다. 또한 브라우저가 종료되면 삭제된다.
우선 axios의 config방식부터 정리해보았다.
아래예시는 get요청과, post요청을 두가지 방식으로 정리해보았는데 모두 config방식이다.
//axios 요청방법
axios.get('url')
//config로 GET 요청
//axios.get({url, data, config}) 형식으로 작성
axios({
method: 'get',
url: 'url',
headers:
'Content-Type':'application/json',
'X-Requested-With': 'XMLHttpRequest',
})
.then((response) => {
console.log(response);
});
//config로 post요청
// axios.post({url}, {data}, {config}) 형식으로 작성
axios.post('url', // 미리 약속한 주소
{name: 'perl', status: 'cute'},
{headers: {'X-Requested-With': 'XMLHttpRequest'}}
).then(function (response) {
console.log(response);
#### })
.catch(function (error) {
console.log(error);
});
config 방식을 이용할 시 내가 원하는 옵션들을 함께 담아서 보낼 수 가 있는데 넣을 수있는 옵션들에 대한 자세한 내용은 독스에서 확인 가능하다.
//default
{headers: { 'Authorization': '내 토큰' },}
//내코드
headers: {
authorization: `Bearer ${getCookie("is_login")}`,
}
이때, authorization은 서버에서 설정함에 따라서 대문자가 될수도 있고, 소문자가 될수도 있고 하니 백엔드와 사전에 맞추는 작업이 필요하다고 한다. 서버에서 authorization이라고 설정을 해놓았는데 프론트 쪽에서 Authorization이라고 보낼경우 헤더에서는 undefined로 인식된다고 한다.
토큰 앞에는 bearer가 붙는다. bearer가 없다고 해서 문제가 되지 않고, 서버쪽에서도 바로 토큰을 받을 수 있다는 장점이 있지만, bearer를 사용하는건 암묵적 약속이라고 한다.
나는 쿠키를 저장시에 is_login이라는 이름으로 저장을 해주었다. 그래서
getcookie("쿠키이름")으로 토큰 값을 꺼내왔다.
이렇게 get요청을 서버로 보냈다. 만약 헤더를 잘못보냈거나 , 오류가 있다면 error가 반환될 것이다. 헤더가 잘 들어갔는지 확인하고 싶다면 개발자도구의 네트워크에서 헤더를 확인 할 수가 있다.
결과적으로, 내가 위에 코드와 같이 요청을 보내면 서버쪽에서 알아서 응답이 온다.(그렇게 서버쪽에서 설정을 해놓는다고 한다.) 토큰이 일치하면 catch로 들어올 것이고, 일치하지 않다면 error가 반환될 것이다.
앞쪽에서 올바른 아이디 또는 패스워드를 입력했다면 쿠키 저장까지 잘 이뤄질 것이다.만약 올바른 아이디 또는 패스워드가 아니라면??? 서버에서는 에러메시지를 반환할 것이고, 단순히 콘솔창에 에러메시지를 띄우면 유저는 알아듣지 못할것이다.
따라서 에러시의 ui를 구현해줘야 한다.
<<공부중>>.....
우선 리액트는 새로고침시마다 스테이트가 초기화 된다. 따라서 로그인을 유지하기 위해서는 해당페이지가 렌더링 될때 토큰 값을 가져와서 서버로 토큰을 보내 토큰이 유효한지 확인하는 과정을 거쳐야 한다.
유지하는 과정은 아래와 같이 구현하였다.
쿠키에 토큰이 저장되어있는지 확인 --> 헤더에 토큰담아서 get요청보내기 (유효한 토큰인지 검증하기) --> 검증성공시 페이지 보여주기
만약 검증이 실패한다면, 로그인 페이지로 이동하도록 설정하였다.
모든 페이지에서 요청을 보낼때마다 헤더를 추가하여, url을 일일이 작성하면 얼마 귀찮은 일인가. 개발자는 자고로 게을러야 하는법.
나만의 axios 세트를 만들어 저장해 두면 필요할때마다 꺼내쓸 수 있다.
독스에 나와있는 예시는 아래와 같다.
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
우선 api.js라는 파일을 따로 만들고, 이렇게 diy엑시오스를 만들어 준다.
import axios from "axios";
import { getCookie } from "../api/cookie";
// axios.create는 나만의 엑시오스 인스턴스를 만드는 메서드이다.
const api = axios.create({
//baseurl에는 반복되는 url을 넣어준다.
baseURL: "url",
//추가로 넣어야하는 옵션들을 넣어주면 되는데, 나는 헤더만 필요하여 헤더를 넣어주었다.
headers: { authorization: `Bearer ${getCookie("is_login")}` },
});
export default api; //익스포트 잊지않긔
이제 내가 엑시오스를 사용하는 곳에 diy엑시오스를 가져다 쓰기만 하면 되는것이다.
//get방식
//우선 임포트 해주고
import api from "../../api/api";
// 원래 axios자리에 인스턴스를 고대로 넣어주면 된다.
//괄호 안에는 겹치지 않는 뒷부분 url을 넣어준다.
const response = await api("/post/createPost");
//post방식
//인스턴트 뒤에 post를 붙여주고, url뒤에 내가 보낼 데이터를 추가해주면 된다.
const response = await api.post("/post/createPost",{data});
https://velog.io/@wearehplk/React-cookie
https://blog.pumpkin-raccoon.com/80
https://devbirdfeet.tistory.com/208
https://han-um.tistory.com/17
https://velog.io/@mygomi/React-%EC%87%BC%ED%95%91%EB%AA%B0-%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A9-4.-axios%EB%A1%9C-API-%ED%86%B5%EC%8B%A0%ED%95%98%EA%B8%B0-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%A9%94%EC%9D%B8-%EC%83%81%EC%84%B8%ED%8E%98%EC%9D%B4%EC%A7%80
https://velog.io/@mygomi/React-%EC%87%BC%ED%95%91%EB%AA%B0-%ED%81%B4%EB%A1%A0%EC%BD%94%EB%94%A9-4.-axios%EB%A1%9C-API-%ED%86%B5%EC%8B%A0%ED%95%98%EA%B8%B0-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EB%A9%94%EC%9D%B8-%EC%83%81%EC%84%B8%ED%8E%98%EC%9D%B4%EC%A7%80
https://r4bb1t.tistory.com/38
https://piaflu.tistory.com/107
https://velog.io/@wearehplk/React-cookie
https://tansfil.tistory.com/59