본문 바로가기
공부/프로그래밍

[react, springboot] kakao 로그인 API 연동

by demonic_ 2021. 4. 2.
반응형

카카오 개발자 사이트에 들어간다.

https://developers.kakao.com/

 

Kakao Developers

카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.

developers.kakao.com

애플리케이션 추가하기를 누른 후 정보를 입력한다

 

앱 키를 제공하는데, 여기서는 웹 연동만 할 것이기 때문에 해당 정보를 가져온다

앱 키를 제공하는데, 여기서는 웹 연동만 할 것이기 때문에 해당 정보를 가져온다

 

여기서 JavaScript 키를 가지고 진행할 예정이다. 여기에 추가 설정을 해야하는데, 카카오 로그인을 활성화 설정으로 바꾸고, Redirect URI를 등록해야 한다. 카카오 로그인 활성화는 On으로 바꾸고, redirect uri를 받을 주소를 등록해야 한다.

 

redirect uri를 등록할때 한가지 생각해야 할게 있는데, 프론트앤드 서버에 두느냐, 백앤드서버에 두느냐 일 것이다. 정답은 프론트엔드 서버에서 콜백을 받아야 한다. 카카오 로그인을 호출하고 나서 해당 브라우저를 등록한 redirect uri로 호출해주는데, 이때 백앤드로 호출하게 되면 브라우저에 백앤드 주소가 들어가게 되고, 이후 진행을 자동으로 넘기는게 불가능하다.(애초에 프론트앤드를 호출한게 아니므로) 백앤드 주소는 대체로 허용된 것만 받을 것이기 때문에 결국 불가능 하다.

 

카카오 로그인을 시도해서 유저정보를 받기까지 다음의 과정을 거친다.

1) 인가 코드 받기

2) 토큰 받기(accessToken과 refreshToken)

3) 유저정보 받기

 

때문에 프로세스를 정리하자면

프론트앤드에서 카카오 로그인으로 인가코드 요청 -> 카카오 로그인 & 서비스 이용등록 수락 등록 -> 리다이렉트 호출(프론트앤드) -> 인가코드를 이용해 토큰정보 받기(백앤드) -> 토큰정보를 이용해 유저정보 받기(백앤드) -> 유저정보 리턴(프론트앤드)

 

이런식으로 될 예정이다. 그리고 인가코드를 이용한 토큰받기, 토큰으로 유저정보 받기는 백앤드에서 1 transaction으로 진행될 예정이다. 만약 토큰받기, 유저정보 받기를 프론트앤드에서 시도한다면 토큰받기는 가능한데 유저정보 받을때 CORS 에러가 발생한다. 그래서 백앤드에서 REST API로 해야만 한다.

 

그럼 이제 호출해보자.

 

 

1) 인가코드 받기

프론트앤드에서 다음처럼 호출한다.(GET)

client_id 는 javascript의 키를 쓴다. 이후 백앤드에서 호출할땐 REST API키를 사용할 것이다.

const callKakaoLoginHandler = () => {
    router.push({
      pathname: "https://kauth.kakao.com/oauth/authorize",
      query: {
        "response_type": "code",
        "client_id": "b51a521566c14452ea05b20e986ecb2e",
        "redirect_uri": "http://localhost:5000/callback/kakao"
      }
    })
}

 

GET으로 호출하는 것이기 때문에 router를 사용했고, location.href로 실행해도 무방하다.

 

페이지가 이동하면 다음 화면이 뜬다.

동의하고 계속하기를 누르면 callback으로 등록된 위치로 호출된다. 위에 등록한것처럼 [도메인]/callback/kakao로 호출되며 해당페이지에 다음처럼 추가했다.

const CallbackKakao = () => {
  const router = useRouter();
  // 카카오에서 준 인증코드
  const {code}:ParsedUrlQuery = router.query

  useEffect(() => {
    console.log(code)
    main()
  }, [])


  const main = async () => {
    if (code === null || code === "") {
        alert("카카오에서 코드를 받는데 실패했습니다");
        return;
    } else {
      await new Promise<accessTokenKakao>((resolve) => {
        getKakaoTokenHandler(resolve, code.toString())
      });
      // await loadUserInfo(accessToken)
    }
  }
  

  const getKakaoTokenHandler = async (resolve: any, code: string) => {
    const url = "/oauth2/authorization/kakao"
    await axios.get(url, {params: {code}})
    .then((res) => {
      console.log("res: ", res)
      resolve(res.data)
    })
    .catch((error) => {
      console.log(error.response)
      resolve(null)
    })
  }

  return (
    <>
        카카오 로그인 중
    </>
  )
}


export default CallbackKakao

 

getKakaoTokenHandler 에서 코드를 이용해 서버를 호출하게 했고, 이제 백앤드에서 진행하면 된다.

 

카카오 인증을 위한 관련 코드를 생성한다.

AccessToken을 받을때 담을 객체를 만든다.

 

@Getter
public class AuthorizationKakao {
    private String access_token;
    private String token_type;
    private String refresh_token;
    private String expires_in;
    private String scope;
    private String refresh_token_expires_in;
}

RestTemplate 빈을 등록하기 위해 다음을 설정했다

@Configuration
public class EtcConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

 

Service Bean 생성

callTokenApi에서는 AccessToken을 발급받는 역할을 하고, callGetUserByAccessToken은 유저정보를 받아온다.

 

@Service
@RequiredArgsConstructor
public class Oauth2Kakao {
    private final RestTemplate restTemplate;
    private final ObjectMapper objectMapper;

    private final String kakaoOauth2ClinetId = "01a9402709eca042cce4f75e549875d2";
    private final String frontendRedirectUrl = "http://localhost:5000";


    public AuthorizationKakao callTokenApi(String code) {
        String grantType = "authorization_code";

        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("grant_type", grantType);
        params.add("client_id", kakaoOauth2ClinetId);
        params.add("redirect_uri", frontendRedirectUrl + "/callback/kakao");
        params.add("code", code);

        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);

        String url = "https://kauth.kakao.com/oauth/token";
        try {
            ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);

            AuthorizationKakao authorization = objectMapper.readValue(response.getBody(), AuthorizationKakao.class);

            return authorization;
        } catch (RestClientException | JsonProcessingException ex) {
            ex.printStackTrace();
            throw new ProcyanException(E00001);
        }
    }


    /**
     * accessToken 을 이용한 유저정보 받기
     * @return
     */
    public String callGetUserByAccessToken(String accessToken) {
        HttpHeaders headers = new HttpHeaders();
        headers.set("Authorization", "Bearer " + accessToken);
        headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);

        MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(params, headers);

        String url = "https://kapi.kakao.com/v2/user/me";
        try {
            ResponseEntity<String> response = restTemplate.postForEntity(url, request, String.class);

            // 값 리턴
            return response.getBody();
        }catch (RestClientException ex) {
            ex.printStackTrace();
            throw new ProcyanException(E00002);
        }
    }
}

 

UserController와 UserServicer 에서 해당 서비스를 호출하도록 한다.

UserController

@RestController
@RequiredArgsConstructor
public class UserController {
    private final UserService userService;

    @GetMapping("/oauth2/authorization/kakao")
    public void oauth2AuthorizationKakao(@RequestParam("code") String code) {
        userService.oauth2AuthorizationKakao(code);
    }
}

UserService

@Service
@RequiredArgsConstructor
public class UserService {
    private final Oauth2Kakao oauth2Kakao;

    // 카카오로 인증받기
    public void oauth2AuthorizationKakao(String code) {
        AuthorizationKakao authorization = oauth2Kakao.callTokenApi(code);
        String userInfoFromKakao = oauth2Kakao.callGetUserByAccessToken(authorization.getAccess_token());
        System.out.println("userInfoFromKakao = " + userInfoFromKakao);
    }
}

 

이제 프론트엔드에서 카카오 로그인 버튼을 만든다음 호출해보자.

const Login = () => {
  const router = useRouter();

  const clickKakaoLoginHandler = () => {
    console.log("kakao 로그인")
    router.push({
      pathname: "https://kauth.kakao.com/oauth/authorize",
      query: {
        "response_type": "code",
        "client_id": "b51a521566c14452ea05b20e986ecb2e",
        "redirect_uri": "http://localhost:5000/callback/kakao"
      }
    })
  }

  return (
      <div>
          <div>
            <Button
                style={{"background": "#FADF0A"}}
                onClick={clickKakaoLoginHandler}>
              카카오 로그인
            </Button>
          </div>
        </div>
      </div>
  )
}


export default Login

 

서버측 결과: 여기선 화면까지 리턴하는건 일단 보류했다. Controller에서 상황에 맞게 데이터 리턴하면 되겠다.

userInfoFromKakao = {"id":1682543849,"connected_at":"2021-04-01T22:01:02Z"}

 

 

끝.

반응형

댓글