로그인 시 header에 Authorization이 안 뜨는 현상 | React, JWT, Spring Boot

    반응형

    오늘은 이번 사이드 프로젝트에서 동료랑 오지게 삽질한........ 에러에 대해 기록하려고 합니다

    문제를 궁극적으로 해결했다기 보다는 결국 조금 수정해서 다른 방식으로 우회(?) 했습니다

    혹시... 제 코드를 보고 제가 잘 못 알았거나 이유를 아신다거나 하면 설명 부탁드립니다😅

     

     

     


     

     

    1. 로그인 방식

    React 로그인이라고 검색하면 상위에 뜨는 벨로그 게시물입니다

    https://velog.io/@yaytomato/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%90%EC%84%9C-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0

     

    🍪 프론트에서 안전하게 로그인 처리하기 (ft. React)

    localStorage냐 쿠키냐 그것이 문제로다

    velog.io

    너무 친절하게 잘 설명해주셨지만

    초보자가 그!대!로! 따라하기에는... 생략된 코드가 많았습니다

    일단은 이 내용을 토대로 프론트는 React, 백엔드는 Spring Boot, JJWT 라이브러리를 사용해서 API를 작성했습니다

    1. 로그인 API
    2. 리프레쉬 토큰을 발급하는 API
    3. 로그인한 유저 정보를 불러오는 API

    이렇게 세가지가 작성 완료된 상태였습니다

     

     

    2. React 로그인 코드

    // SignIn.js
    const handleClickSubmit = (event) => {
    	event.preventDefault();
    
    	let data = {
    		email: email,
    		password: password,
    	};
    
    	axios.post("/hyd/api/user/login", data).then((response) => {
    		const { accessToken } = response.data;
    
    		axios.defaults.headers.common["Authorization"] = `Bearer ${accessToken}`;
    		
    		if (response.status === 200) {
    			history.push("/");
    		}
    
    	}).catch((error) => {
    			console.log(error);
    		});
    };

    SignIn.js 파일에 위와 같이 로그인 API를 AJAX로 받아오는 함수를 작성해줬습니다

    그리고 전역으로 axios 설정을 해줘야한대서 최상위 파일에 아래와 같이 작성했습니다

    // index.js
    import React from "react";
    import ReactDOM from "react-dom";
    import axios from "axios";
    
    axios.defaults.baseURL = "http://localhost:3000/";
    axios.defaults.withCredentials = true;

    자 이제 /hyd/api/user/login 주소로 email과 password 데이터를 POST 방식으로 보내주면

    API 요청하는 콜마다 accessToken을 header에 Authorization에 담아서 서버로 보낸다고 합니다

     

     

     

    3. 에러

    그럼 저는 여기서 당연히! 개발자도구 네트워크에서 login 헤더를 보면 Authorization가 뜰거라고 생각했습니다

    동료도 그렇게 생각을 했고요..........!

    일단 console.log로 찍어보면 나오기는 합니다

     

    로그인도 200으로 정상처리 되었고

    개발자 도구로 들어가서 Network 탭에 들어가 login header를 확인해봤습니다

    없어요!!!!!!!!!!!!! Authorization라고 써전 key가 안 보입니다.........

    원래..... 그런건가? 라고 하기에는........ 인터넷에 검색해보니 다들 뜬는 것 같습니다......

    스텍오버플로우에는 오히려 저게 뜨는게 보안상 안 좋은 느낌인데 못 지워? 라고 물어보는 것 같습니다....

    (사실  이 방식의 로그인은 처음 구현해보는거라 뭐가 정답인지 잘 모르겠습니다)

    console.log로 console.log(axios.defaults.headers.common.Authorization)라고 찍어보면 값이 잘 나옵니다

    너무 혼란스러웠습니다 거의 블로그 게시물 글 복붙하다시피 그대로 쓴건데....

    뭔가 우리만 결과가 다르게 나오는 느낌?

    아무리 검색해도 음.... 잘 모르겠고.... 해결 방법도 없고... 우리가 착각하는건가 싶고....

    쨌든 저희가 로그인한 유저의 정보를 불러오는 API가 있다고 했잖아요?

    로그인은 200으로 잘 해결 되었으니 로그인한 유저의 정보를 불러오는지 확인해보면 알겠지! 싶어서

    해당 API 주소로 들어가봤습니다

    응  null이야~^^~

    500에러에.... 서버 log를 보니까 Authorization가 null이라고 뜨고 있었습니다

    그렇게 뻘짓의 여정이 시작되었습니다

    정말 며칠이 걸렸던거 같아요

     

     

     

    3. 백엔드 코드

    일단 문제를 해결해 나가면서 백엔드 코드도 수정 되었기에 기존 백엔드 코드도 써놓겠습니다

    // 로그인 API
    @PostMapping("/login")
    public JsonNode login(@RequestBody UserVO userVO, HttpServletResponse res) 
    	throws JsonProcessingException {
    	LOGGER.info("userVO: " + userVO.getEmail());
    	ObjectMapper mapper = new ObjectMapper();
    	String tokenJson = null;
    	if (userService.isLogin(userVO)) {
    		String refreshToken = jwsService.getRefreshToken(key);
    		String accessToken = jwsService.createJws(key, userVO);
    		Cookie refreshCookie = new Cookie("refreshToken", refreshToken);
    		res.addCookie(refreshCookie);
    		tokenJson = "{\"accessToken\":\"" + accessToken + "\"}";
    	} else {
    		tokenJson = "{\"message\":" + "\"LOGIN_FAIL\"}";
    	}
    	JsonNode json = mapper.readTree(tokenJson);
    	return json;
    }
    // 로그인된 유저 정보 API
    @GetMapping("/auth/info")
    public UserVO getAuthInfo(HttpServletRequest req) {
    	String authorization = req.getHeader("Authorization");
    	LOGGER.info("authorization: " + authorization);
    	String accessToken = authorization.split("Bearer ")[1];
    	LOGGER.info(accessToken);
    	Jws<Claims> jws = jwsService.readJws(accessToken, key);
    	LOGGER.info("jws: " + jws);
    	String email = (String) jws.getBody().get("email");
    	UserVO userVO = userService.findByEmail(email);
    
    	return userVO;
    }

    리프레쉬 토근을 발급하는 API는 뺐습니다

    쨌든 이렇게... 백엔드에서 작성을 한 코드입니다

     

     

     

    4. 시도 해본 방법들

    정말 온갖 몇날며칠 검색을 해보며 시도한 방법들을 나열해보겠습니다

    4-1. axios 문서에 나온 config 기본 설정을 AJAX .then 안에다가 작성

    const handleClickSubmit = (event) => {
    	...
    	axios.defaults.baseURL = 'http://localhost:3000';
    	axios.defaults.headers.common['Authorization'] = accessToken;
    	axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded';
    	...
    }

    https://axios-http.com/docs/config_defaults

     

    Config Defaults | Axios Docs

    Config Defaults Config Defaults You can specify config defaults that will be applied to every request. Global axios defaults axios.defaults.baseURL = 'https://api.example.com'; axios.defaults.headers.common['Authorization'] = AUTH_TOKEN; axios.defaults.hea

    axios-http.com

    위 문서를 보고 그냥 따라쳐봤습니다...

    정말 황당하게도 처음에 뭐지?! 담긴거야?! 떴어!!! 떴어!!!!!!!!! 대박!!!!!!!!!!!! 하고

    다시한번 로그인을 시도했습니다만...............

    무슨 신기루였다듯이............ 다시 안 뜨기 시작했습니다......... 쭈우우우우욱..............

     

    4-2. 서버에 Access-Control-Expose-Headers 설정

    CORS 라는 문제가 제일 많더라고요

    그 중 아래 백엔드쪽에 추가해보라길래 추가해봤습니다 (어느 위치인지는 잘 모르겠습니다)

    Access-Control-Expose-Headers: Access-Token, Uid

    아무런 변화도 없었습니다 (...)

     

    4-3. @CrossOrigin 어노테이션 설정

    https://stackoverflow.com/questions/59836005/receiving-null-authorization-header-in-spring-boot-from-requests-with-angular-7

     

    Receiving null Authorization header in Spring Boot from requests with Angular 7 but Postman works fine

    I am receiving a null Authorization header when I am sending a request to a back-end controller designed with Spring Boot. But when I am sending the same request with Postman, the correct API is hi...

    stackoverflow.com

    위 질문에 두번째 답변 내용입니다

    Java Bean을 설정하거나 어노테이션을 넣거나 하라고 하는데

    동료가 Bean 설정에 미숙해서 Bean 설정 시도하다가 포기하고 아래에 어노테이션을 설정했습니다

    @CrossOrigin(origins = "http://localhost:3000", exposedHeaders = "Authorization")

    이 코드를 로그인 API, 로그인 유저 API 메소드 위에다가 작성을 해줬습니다

    origins 주소는 리액트 로컬 주소입니다

    결과는 변화가........... 당장에는 없었습니다........... 그래서 일단 어노테이션을 설정한 상태에서

    리액트 코드를 수정해봤습니다

    // SignIn.js
    const handleClickSubmit = (event) => {
        event.preventDefault();
    
        let data = {
          email: email,
          password: password,
        };
    
        axios
          .post("/hyd/api/user/login", data)
          .then((response) => {
            const { accessToken } = response.data;
    
            axios.defaults.headers.common[
              "Authorization"
            ] = `Bearer ${accessToken}`;
    
            if (response.status === 200) {
              history.push("/");
            }
    
            console.log(response);
          })
          .catch((error) => {
            console.log(error);
          });
    
        axios
          .post("/hyd/api/user/auth/info")
          .then((response) => {
            console.log(response);
          })
          .catch((error) => {
            console.log(error);
          });
      };

    마지막 부분에 로그인된 유저의 정보를 불러오는 AJAX 코드를 추가 작성해줬습니다

    그랬더니................... Authorization가 뜨기 시작했습니다!!!!!!!!!!!!!!!!!!!!!!!!!!! 😭😭😭😭😭😭

    근데... 조금 황당하게도.. 로그인 첫 시도만에 뜬건 아니었습니다

    첫 로그인 시도 -> 새로고침 안함 -> 다시 로그인 시도

    해보니까 뜬거였습니다. 참 흐름이 이상하죠?????????

    login header
    user info header

    그래도 이렇게  로그인 헤더와 로그인 유저 정보 헤더 둘 다 Authorization가 뜨기 시작했습니다!!!!!!!!!!!

     

     

     

    5. 그러나 또다른 문제 사항에 직면

    그러나 위에 말했다시피 왜 첫 시도에서는 안 뜨고 두번째 시도할때만 뜨는건지 의문이었습니다

    이 현상이 계속 되었거든요

    그래서 여러가지 실험을 해봤습니다

    🔥 (쭉 새로고침없이) 2번은 같은 아이디로 로그인 시도, 3번째부터는 다른 아이디로 로그인 시도

    새로고침 없이 쭉 로그인 시도를 했으니까 로그인한 유저 정보 API가 500에러로 뜨지 않을까? 라는 생각으로 실험해봤습니다

    그러나 200으로 유저 정보를 제대로 불러오더군요...........?

    그래서 대체 이건 무슨 현상?^^이지 싶어서 header 내용을 자세히 보니까

    그 전 로그인 정보가 그대로 넘어온 것이었습니다

    그래서 아래와 같이 리액트 코드를 수정해봤습니다

    // SignIn.js
    const handleClickSubmit = (event) => {
        event.preventDefault();
    
        let data = {
          email: email,
          password: password,
        };
    
        axios.post("/hyd/api/user/login", data).then((response) => {
          const { accessToken } = response.data;
    
          if (response.status === 200) {
            axios.defaults.headers.common[
              "Authorization"
            ] = `Bearer ${accessToken}`;
            
            axios
              .post("/hyd/api/user/auth/info")
              .then((response) => {
                console.log(response);
              })
              .catch((error) => {
                console.log(error);
              });
              
            history.push("/");
          }
        });
      };

    로그인에 성공하면 유저 정보를 불러오게 수정해줬습니다

    그랬더니!!!!!!!!!!!!! 드디어 로그인이 제대로 문제없이 구현되었습니다!!!!!!!

    header에 Authorization가 잘 명시되고 있고요!!!

    그리고 무엇보다 여기서 또 변화!!!!!!!!!

    처음에는 로그인한 유저 정보를 불러오려고 하면은 500에러에 Authorization가 null이었잖아요

    그것도 같이 해결이 되었다는 것입니다!!

    서버 Authorization 값이 잘 뜨고 있어요!!!

    후하......... 일단 저희가 보기에는 백엔드에 어노테이션 쓴게 큰 역할을 했다고 생각하고 있습니다

     

     

     

    🚑 마무리

    사실 저희가 문제가 왜 생겼는지를 정확히 파악한게 아니라

    이리저리 검색해보고 실험해보고 따라도 쳐보고... 하다가 해결된거라서

    왜 이런 현상이 생긴건지는 잘 모르겠습니다😥

    혹시 이유를 아시는분이 계시다며 댓글 꼭꼭 부탁드립니다

    저희랑 똑같은 에러를 뜬 분이 없으신것 같아서 정보를 찾기가 굉장히 힘들었는데 그래도 해결해서 기쁩니다

    (혹시... 너무 멍청스러웠다면 조용히 뒤로가기 부탁드립니다......따흙)

    반응형

    댓글