본문 바로가기

React를 시작해보자

6) React - JWT를 이용한 로그인 인증 - 2부

반응형

앞서 까지한 내용은 단순히 로그인을 하고 로그인 이후 부터 backend api 호출할때 사용할 access token 값을 받아와

axios header에 저장하고 Home Page 화면으로 이동하는것 까지 공부했다. 

 

여기서 문제점이 브라우저에서 새로고침을 하게 되면 axios object에 가지고 있던 access token값도 사라지게 되고 

App.jsx 에 있던 isLogin state도 초기화 되면서 로그인 상태를 잃어 버리게 된다.

 

그러면 어떻게 해야 할까 ? 

 

JWT 인증방식에서 access token을 사용함과 동시에 access token 을 재 발급 받기 위해 인증 서버에서 refresh token값을 cookie에 저장하고 화면이 새로 고침 될때 refresh token api 를 호출해서 새로운 access token값을 가져오는 

자동 로그인 방식으로 처리 하고자 한다. 

물론 꼭 이방식이 아닌 다른 여러 방식이 있겠지만 보안문제와 refresh token을 따로 관리 하지 않아도 되는 편리함도 있고해서 이방식으로 진행 한다.

 

App.jsx 를 아래와 같이 변경했다. ( Auth 서버에서 로그인 부분의 refresh token Cookie 저장하는 부분은 차후에 소스를 공유하도록 한다.)

 

간단히 BackEnd의 login api 와 refresh token api 소스를 만들어 봤다. 

// Loing API
@RequestMapping(value="/login" , method=RequestMethod.POST)
public String login(@RequestBody TokenVO userData , HttpServletRequest request , HttpServletResponse response) {
  String accessToken = "";
  String refreshToken = "";

  accessToken = getToken(userData.getEmail() , 1);
  refreshToken = getToken(userData.getEmail() , 3);
  //예제로 3분짜리 Cookie를 만들어서 Client에 저장한다.
  Cookie refreshCookie = new Cookie("refreshToken" , refreshToken);
  refreshCookie.setMaxAge(3 * 60);
  response.addCookie(refreshCookie);

  return accessToken;
}

 

// Refresh API
@RequestMapping(value="/refreshToken" , method=RequestMethod.POST)
public String refreshToken(@RequestBody TokenVO userData , HttpServletRequest request , HttpServletResponse response) throws Exception{
    String accessToken = "";
    String refreshToken = "";

    Cookie [] cookies = request.getCookies();
    if(cookies != null && cookies.length > 0 ) {
    	for(Cookie cookie : cookies) {
        	if(cookie.getName().equals("refreshToken")) {
          		refreshToken = cookie.getValue();
          		if(checkClaim(refreshToken)) {
              		accessToken = getToken(userData.getEmail() , 1);
                }else {
                    throw new InvaildTokenException();
                }
            }
    	}
    }

    if(refreshToken == null || "".equals(refreshToken)) {
    	throw new InvaildTokenException();
    }

    return accessToken;
}

 

refresh api 호출을 하면 cookie 정보를 읽어서 verify 하면 access token을 갱신하는 구조로 만들어 봤다.

 

자 이제 새로 고침 할때 어떻게 로그인을 유지 하는지 대해 다시 애기하면 App.jsx  부분을 아래와 같이 수정을 해야 한다.

 

import './App.css';
import { BrowserRouter, Route } from 'react-router-dom';
import LoginPage from './pages/LoginPage';
import HomePage from './pages/HomePage';
import AuthRoute from './component/AuthRoute';
import Counnter from './component/Counter';
import { useEffect, useState } from 'react';
import axios from "axios";

function App() {
  const [isLogin , setIsLogin] = useState(false);
  const [loading , setLoading] = useState(false);

  useEffect(()=>{
      try{
        let data = {email: "devracoon@naver.com"};
        axios.post("/auth/refreshToken" ,JSON.stringify(data), {
            headers: {
              "Content-Type": `application/json`,
            }})
        .then(res =>{
            console.log("res.data.accessToken : " + res.data);
            axios.defaults.headers.common['Authorization'] = 'Bearer ' + res.data;
            setIsLogin(true);
            
        })
        .catch(ex=>{
            console.log("app silent requset fail : " + ex);
        })
        .finally(()=>{
          console.log("login request end");
          setLoading(true);
        });
    }catch(e){
        console.log(e);
    }
  },[]);

  function loginCallBack(login){
    setIsLogin(login);
  }

  if(loading){
    return (
      <div className="App">      
          <BrowserRouter>
            <AuthRoute exact isLogin={isLogin} path="/" component={HomePage} />
            <Route path="/login"  render={(props)=> <LoginPage {...props} loginCallBack={loginCallBack}/>} />
            <AuthRoute path="/counter" isLogin={isLogin} component={Counnter} />
          </BrowserRouter>
  
      </div>
    );
  }else{
    return (
      <div>
        Loading ....
      </div>
    )
    
  }
  
}

export default App;

 

return 부분을 보면 loading 부분이 추가 됐고 . useEffect 부분을 보면 App mount 되고 나면 refresh api 호출해서 

access token 받아 오는 부분이 추가 되었다. 

 

여기서 알고 가야 하는것이 useState 와 useEffect 부분이다. 와 저것만 추가 됐는데 로그인이 유지가 될까를 이해하려면 

react 의 state 관리와 re-render의 규칙에 대해 이해를 한다. 

 

먼저 useState 함수는 componet가 관리하는 상태값과 그 상태값을 변경할수 있는 함수를 배열로 리턴하는 함수 있다.

즉. const [isLogin , setIsLogin] = useState(false); 이부분은 isLogin 은 App 의 상태값이고 이 상태값의 변경은 setIsLogin으로 변경한다는 의미이고 . 이 상태값이 변경되면 App 가 re-render 된다는 뜻이다.

 

이때 useEffect는 app render 될떄 마다 한번씩 실행 된다고 생각하면 된다. 

위의 App.jsx 의 useEffect부분을 보면  아래같은 내용을 처리 된다.

// 첫 Render때한번만 실행.
useEffect(()=>{
...},[]);

// isLogin state가 변경 될때만 실행
useEffect(()=>{
...},[isLogin]);

// component가 render 될때 마다 실행
useEffect(()=>{
...});

이제 화면을 실행하고 login인을 한후 화면 새로고침을 하게 되면 잠시 Loading 문구가 보이고 Home Page 화면으로 이동하는것을 볼 수 있다.

 

여기까지 로그인을 하고 token의 refresh 부분까지 처리 되었다. 

 

다음 3부는 로그인 마지막 부분으로 access token의 주기적 갱신 부분과 refresh token이 만료 되었을때 처리 하는 부분을 추가해 보도록 하겠다.

 

웹 개발에서 보안 인증 부분이 기본 설계의 대부분이라 해서 과언이 아닐정도로 해야 하는 작업도 많고 생각해야 하는 부분도 많다. 공부를 하는 차원에서 포인트부분만 간단히 하고 넘어가고 있지만 실무에서는 좀 더 디테일한 부분을 보강할 필요가 있다.