Rest-API 활용한 카카오 소셜 로그인 구현 (React,Spring boot)

dev_shim·2022년 9월 27일
4
post-thumbnail

프로젝트를 진행하면서 소셜 로그인 구현해본 과정을 정리하는 글이다.
처음 사용해본 api인 만큼 포스팅으로 남겨볼려고 노력해봤다.


REST-API를 활용한 카카오 로그인 문서


간단 요약

  • REST-API를 활용한 카카오 로그인 방법을 사용하였다.

  • React - Spring boot로 함께 작업하였다.

    	1. 프론트엔드 : 카카오로부터 인가코드를 받고 받은 인가코드를 백엔드에 넘겨준다.
    
    	2. 백엔드: 프론트로부터 인가 코드를 넘겨받고 카카오로부터 access_token을발급받는다.
      	          그리고 해당 토큰에 담긴 유저 정보를 활용해 프로젝트 전용 토큰으로 새롭게 발급 후 프론트에게 돌려준다.
                   
       

구현하는 과정에서 제일 많이 참고한 사진이다.

사진 출처

공식 문서 Link를 타고 들어가다 보면
API_KEY 와 REDIRECTION 키를 발급 받는 과정이 상세하게 나온다 .
내 애플리케이션을 생성하고 APi 키와 REDIRECTION을 URL을 설정한 뒤
KakaoLoginAPI 라는 컴포넌 트를 생성해준다

KakaoLoginAPI = `https://kauth.kakao.com/oauth/authorize?
client_id=${API_KEY}&redirect_uri=${REDIRECTION}&response_type=code`;

  /**온클릭 이벤트
   * 카카오 로그인용 새창을 띄운다.
   */
로그인폼에서 카카오 로그인을 클릭한다면 실행되는 매소드 이다.
  const openKakaoLogin = () => {
    window.open(KakaoLoginAPI, "_self");
  };

이메일-비밀번호를 입력하면 아래와 같이 동의하기 화면이 나온다.

여기서! 동의하고 계속하기를 누르면 이동되는 화면이 Redirect_URI 화면이다.
이 화면에서 보이지 않는 모든 것이 이루어진다.

function KakaoLogin() {
  const setUser = useSetRecoilState(userState);
  const navi = useNavigate();
  // 1.해당 페이지가 로딩되었다면 url 에 인가코드가 담기게 된다.  
  useEffect(() => {
    
    //2.인가코드를 추출할 변수 생성. 현재 url 주소를 가지고 있다. 
    const url = new URL(window.location.href);

    //3.위에서 만든 URL 에서 code=  라고 써진 키값을 찾아서 벨류를 반환받음.
     
    const code = url.searchParams.get("code");

    //4.위에서 얻은 인가코드를 백엔드의 카카로 로그인주소로 보냄.
    
   
     
 	  //4.위에서 얻은 인가코드를 백엔드의 카카로 로그인주소로 보냄.
    axios.get(`${REQUEST_ADDRESS}auth/kakao?code=${code}`).then((res) => {
      
     //5. ok respone 확인하고, 이후 작업 해야함(유저로그인시키기, 토큰 브라우저에 저장)
      localStorage.setItem("token", res.data.token);
      axios //서버에서 유저정보 요청하는 url 
        .get(`${REQUEST_ADDRESS}userinfo`, {
          headers: {
            //헤더에 token을 담아서 전달 
            Authorization: "Bearer " + res.data.token,
          },
        })
      		//서버에서 유휴성 검사 - > 확인되면 유저 데이터 전달해주고 프론트에서는
			// const setUser = useSetRecoilState(userState);
      		// recoilstate로 유저 데이터 저장
      		// 하고 dashboard로 이동 시키기  로그인끝!
        .then((response) => {
          setUser(response.data);
          navi("/dashboard");
        });
    });
  }, []);
  return <div>KakaoLogin</div>;
}

REQUEST_ADDRESS는 내 애플리케이션 서버 주소이고 컨트롤러에 매핑된 주소로 인가코드가 서버로 넘어오게된다

프론트에서 넘어온 인가코드로 access_token을 발급받는
getKAKAOAccessToken을 실행한다.

/* 카카오 로그인 */
    public String[] getKaKaoAccessToken(String code) {

        String access_Token = "";
        String refresh_Token = "";
        String reqURL = "https://kauth.kakao.com/oauth/token";

        String result = null;
        String id_token = null;
        try {
            URL url = new URL(reqURL);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();

            //POST 요청을 위해 기본값이 false인 setDoOutput을 true로
            conn.setRequestMethod("POST");
            conn.setDoOutput(true);

            //POST 요청에 필요로 요구하는 파라미터 스트림을 통해 전송
            BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(conn.getOutputStream()));
            StringBuilder sb = new StringBuilder();
            sb.append("grant_type=authorization_code");
            sb.append("&client_id=REST_API_KEY"); // TODO REST_API_KEY 입력
            sb.append("&redirect_uri=redirect_uri"); // TODO 인가코드 받은 redirect_uri 입력
            System.out.println("code = " + code);
            sb.append("&code=" + code);
            bw.write(sb.toString());
            bw.flush();

            //결과 코드가 200이라면 성공
            int responseCode = conn.getResponseCode();
            System.out.println("responseCode : " + responseCode);
            //요청을 통해 얻은 JSON타입의 Response 메세지 읽어오기
            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line = "";
            result = "";

            while ((line = br.readLine()) != null) {
                result += line;
            }
            // bearer 토큰 값만 추출(log에 찍히는 값의 이름은 id_Token)
            System.out.println("response body : " + result);
            String[] temp = result.split(",");
            id_token = temp[3].substring(11);
            System.out.println("idToken = " + id_token);


//            Gson 라이브러리에 포함된 클래스로 JSON파싱 객체 생성
            JsonParser parser = new JsonParser();
            JsonElement element = parser.parse(result);

            access_Token = element.getAsJsonObject().get("access_token").getAsString();
            refresh_Token = element.getAsJsonObject().get("refresh_token").getAsString();

            System.out.println("access_token : " + access_Token);
            System.out.println("refresh_token : " + refresh_Token);

            br.close();
            bw.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
        String[] arrTokens = new String[3];
        arrTokens[0] = access_Token;
        arrTokens[1] = refresh_Token;
        arrTokens[2] = id_token;

        return arrTokens;
    }

arrTokens을 보면 access_Token, refresh_Token, id_token 담긴 배열이다.

유저정보를 받아올수 있는 access_token을 담을 변수를 생성해주고 createKakaoUser 매개변수에 담아준다


 public UserDTO createKakaoUser(String token) throws IOException {
		
        //1.유저 정보를 요청할 url
        String reqURL = "https://kapi.kakao.com/v2/user/me";

        //2.access_token을 이용하여 사용자 정보 조회
        URL url = new URL(reqURL);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();

        conn.setRequestMethod("POST");
        conn.setDoOutput(true);
        conn.setRequestProperty("Authorization", "Bearer " + token); //전송할 header 작성, access_token전송

        //결과 코드가 200이라면 성공
        int responseCode = conn.getResponseCode();
        System.out.println("responseCode : " + responseCode);

        //요청을 통해 얻은 JSON타입의 Response 메세지 읽어오기
        BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
        String line = "";
        String result = "";

        while ((line = br.readLine()) != null) {
            result += line;
        }

        System.out.println("response body : " + result);

        //Gson 라이브러리로 JSON파싱
        JsonElement element = JsonParser.parseString(result);

        Long id = element.getAsJsonObject().get("id").getAsLong();
        boolean hasEmail = element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("has_email").getAsBoolean();
        //사용자의 이름
        String nickname = element.getAsJsonObject().get("properties").getAsJsonObject().get("nickname").getAsString();
        //사용자의 이메일
        String email = "";
        if (hasEmail) {
            email = element.getAsJsonObject().get("kakao_account").getAsJsonObject().get("email").getAsString();
       }	
       	   //DB에 카카오로 로그인한 기록이 없다면 
           //카카오톡에서 전달해준 유저 정보를 토대로 
           //유저 객체 생성하고 DB에 저장
           //이후 프론트에서 요청하는 api 스펙에 맞춰
           //dto로 변환한 후에 return 해준다.
	    if (!userRepository.existsByUsername(email)) {

            UserEntity user = UserEntity.builder().username(email).nickname(nickname).realName(nickname).password("").build();
            UserEntity savedUser = userRepository.save(user);
            
            String find_user_token = tokenProvider.create(savedUser);

            return savedUser.toDTO(find_user_token);

        } else {
            //DB에 카카오로 로그인된 정보가 있다면 token 생성해서 리턴
            UserEntity byUsername = userRepository.findByUsername(email);
            String find_user_token = tokenProvider.create(byUsername);
            br.close();

            return byUsername.toDTO(find_user_token);
        }
    }

access_token 으로 사용자의 동의 항목에 따라서 가저올수있는 정보가 달라진다.

프로젝트에서는 profileimg, email, nickname만 필요로하기때문에 3개만 설정된걸 볼수있다.
(위 코드에서는 profileimg는 사용하지않았다 프론트에서 따로 저장할 예정)

사용자의 데이터를 토대로 userEntity를 생성해서 저장해준 뒤에 우리 서버의 전용 토큰을 발급해서 dto로 변환한뒤에 리턴해주었다.

  1. 프론트에서는 res의 정보를 토대로 로컬스토리지에 토큰을 저장,
  2. 헤더에 토큰을 담아 유효성 검사(서버로 보낼때)
  3. 유저정보를 조회해온뒤에 dashboard로 이동시킨다 .
  4. 최초 로그인 성공 후, 다음부터는 Token을 첨부하여 서버에 요청을 전송함으로써 매번 로그인하는 과정을 생략할 수 있다.

이렇게 소셜로그인 과정이 마무리된다..


개인적인 후기

정말 많은 시행착오가 있었는데 카카오톡 로그인을 구현함으로써 api를 사용하는 방법에 대해 공부가 많이됬다.
인증과 인가에 대한 개념도 다시한번 잡게되었고 다른 형태의 api도 다뤄보고싶다.

  • 아쉬운 점
    Refresh Token을 사용해보지 못한것

  • 해결/개선 방법
    Auth 2.0, JWT, Spring Security 공부하기 !!!


피드백 환영이에요 !!


참고 블로그 https://velog.io/@kimujin99/Spring-React-%EC%B9%B4%EC%B9%B4%EC%98%A4-%EC%86%8C%EC%85%9C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-JWT-1#3-1-usercontroller-%EB%A7%8C%EB%93%A4%EA%B8%B0,
https://data-jj.tistory.com/53

profile
개발 연습장

0개의 댓글