[Spring Boot] 카카오 로그인 구현 (문서 찬찬히 뜯어보기)

뽀삐용·2023년 9월 27일

Spring Boot

목록 보기
3/4
post-thumbnail

※ 가장 중요한 카카오 공식문서 ※
카카오 로그인 > REST API
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api



1. 화면에 버튼 생성 (login.html)

<a th:href="@{/oauth/kakao}">
	<img th:src="@{/img/button/kakao_login_medium_narrow.png}" style="width:150px; height:40px;">
</a>

*img 다운로드 링크
https://developers.kakao.com/docs/latest/ko/kakaologin/design-guide

2. 카카오에서 받아온 정보를 저장하는 클래스 (KakaoInfo)

@Getter
@NoArgsConstructor
public class KakaoInfo {
    private Long id;
    private String nickname;
    private String email;

    public KakaoInfo(Long id, String nickname, String email) {
        this.id = id;
        this.nickname = nickname;
        this.email = email;
    }
}

3. 카카오에 인가 코드 요청(OAuthController)

@Controller
@RequiredArgsConstructor
@RequestMapping("/oauth")
public class OAuthController {

    @Value("${kakao.client.id}")
    String clientId;
    @Value("${kakao.redirect.uri}")
    String redirectUri;
    @Value("${kakao.client.secret}")
    String clientSecret;
    
    /**
     * 카카오 로그인 요청
     * @return
     */
    @GetMapping(value="/kakao")
    public String kakaoConnect() {
        StringBuffer url = new StringBuffer();
        url.append("https://kauth.kakao.com/oauth/authorize?");
        url.append("client_id="+clientId);
        url.append("&redirect_uri="+redirectUri);
        url.append("&response_type=code");
        return "redirect:" + url.toString();
    }
}

[카카오 로그인] img를 누르면..!
기본 정보에 작성된대로 Get 방식을 사용하여 URL(https://kauth.kakao.com/oauth/authorize)을 통해 카카오에 요청을 보낸다.

쿼리 파라미터로 전달해야하는 client_id, redirect_uri 은 내 애플리케이션에 설정한 값으로 변경해서 요청해야 한다.

해당 값은 application.properties에 별도로 저장한 뒤 가져오는 방식으로 구현했다. application.properties를 사용하는 법은 추가로 포스팅!

3. 토큰 받기 (OAuthService)

카카오에서 전달해준 인가 코드로 토큰을 받는다.

    public String getAccessToken(String code) throws JsonProcessingException {
        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP Body 생성
        MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
        body.add("grant_type", "authorization_code");
        body.add("client_id", clientId);
        body.add("redirect_uri", redirectUri);
        body.add("code", code);
        body.add("client_secret", clientSecret);

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest = new HttpEntity<>(body, headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kauth.kakao.com/oauth/token",
                HttpMethod.POST,
                kakaoTokenRequest,
                String.class
        );

        // HTTP 응답 (JSON) -> 액세스 토큰 파싱
        String responseBody = response.getBody();
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseBody);

        return jsonNode.get("access_token").asText();
    }

HTTP Request Message를 전달하기 위해 HttpHeaders, MultiValueMap으로 Header와 Body를 구성한다.

HTTP Body의 code부분에 바로 얻은 인가 코드를 작성줘야 하는 것이다.

카카오에서 응답으로 온 정보들 중 access_token 값만 꺼내서 리턴해줬다.


4. 액세스 토큰으로 사용자 정보 가져오기 (OAuthService)

    public KakaoInfo getKakaoInfo(String accessToken) throws JsonProcessingException {
        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer " + accessToken);
        headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> kakaoUserInfoRequest = new HttpEntity<>(headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kapi.kakao.com/v2/user/me",
                HttpMethod.POST,
                kakaoUserInfoRequest,
                String.class
        );

        // responseBody에 있는 정보 꺼내기
        String responseBody = response.getBody();
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseBody);

        Long id = jsonNode.get("id").asLong();
        String email = jsonNode.get("kakao_account").get("email").asText();
        String nickname = jsonNode.get("properties")
                .get("nickname").asText();

        return new KakaoInfo(id, nickname, email);
    }

카카오에서 보내준 정보를 만들어 둔 KakaoInfo에 담아 return 했다.

5. 카카오 사용자 정보 확인 (OAuthService)

카카오 사용자 정보를 바탕으로 member 테이블을 확인하여, 가입된 내역이 있는지 확인했다. 가입된 정보가 없으면 이메일로 임시 id를 만들고 비밀번호는 랜덤으로 생성하여 가입되도록 처리했다.

    public MemberResponse ifNeedKakaoInfo (KakaoInfo kakaoInfo) {
        // DB에 중복되는 email이 있는지 확인
        String kakaoEmail = kakaoInfo.getEmail();
        MemberResponse kakaoMember = memberService.findMemberEmail(kakaoEmail);

        // 회원가입
        if (kakaoMember == null) {
            String kakaoNickname = kakaoInfo.getNickname();
            // 이메일로 임시 id 발급
            int idx= kakaoEmail.indexOf("@");
            String kakaoId = kakaoEmail.substring(0, idx);
            // 임시 password 발급 - random UUID
            String tempPassword = UUID.randomUUID().toString();

            RegisterRequest registerMember = new RegisterRequest();
            registerMember.setId(kakaoId);
            registerMember.setPassword(tempPassword);
            registerMember.setNickname(kakaoNickname);
            registerMember.setEmail(kakaoEmail);

            memberService.saveMember(registerMember);
            // DB 재조회
            kakaoMember = memberService.findMemberEmail(kakaoEmail);
        }

        return kakaoMember;
    }

6. 세션 로그인 (OAuthController)

Service에 작성한 함수를 바탕으로 Controller를 작성하고, 세션에 값을 담아 로그인을 구현했다.

GetMapping에 작성한 주소는 애플리케이션에서 등록한 Redirect URI로 설정해야한다.

    /**
     * 카카오 로그인
     * @return
     */
    @GetMapping("/kakao/callback")
    public String kakaoCallback(String code, HttpSession session) {

        // SETP1 : 인가코드 받기
        // (카카오 인증 서버는 서비스 서버의 Redirect URI로 인가 코드를 전달합니다.)
        // System.out.println(code);

        // STEP2: 인가코드를 기반으로 토큰(Access Token) 발급
        String accessToken = null;
        try {
            accessToken = oAuthService.getAccessToken(code);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        //System.out.println("엑세스 토큰  "+accessToken);

        // STEP3: 토큰를 통해 사용자 정보 조회
        KakaoInfo kakaoInfo = null;
        try {
            kakaoInfo = oAuthService.getKakaoInfo(accessToken);
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        //System.out.println("이메일 확인 "+kakaoInfo.getEmail());

        // STEP4: 카카오 사용자 정보 확인
        MemberResponse kakaoMember = oAuthService.ifNeedKakaoInfo(kakaoInfo);

        // STEP5: 강제 로그인
        // 세션에 회원 정보 저장 & 세션 유지 시간 설정
        if (kakaoMember != null) {
            session.setAttribute("loginMember", kakaoMember);
            // session.setMaxInactiveInterval( ) : 세션 타임아웃을 설정하는 메서드
            // 로그인 유지 시간 설정 (1800초 == 30분)
            session.setMaxInactiveInterval(60 * 30);
            // 로그아웃 시 사용할 카카오토큰 추가
            session.setAttribute("kakaoToken", accessToken);
        }

        return "redirect:/";
    }

7. 로그아웃

로그인 시 로그아웃 때 사용하기 위해 생성해둔 kakaoToken을 먼저 확인한다.

    /**
     * 카카오 로그아웃
     * @return
     */
    @GetMapping("/kakao/logout")
    public String kakaoLogout(HttpSession session) {
        String accessToken = (String) session.getAttribute("kakaoToken");

        if(accessToken != null && !"".equals(accessToken)){
            try {
                oAuthService.kakaoDisconnect(accessToken);
            } catch (JsonProcessingException e) {
                throw new RuntimeException(e);
            }
            session.removeAttribute("kakaoToken");
            session.removeAttribute("loginMember");
        }else{
            System.out.println("accessToken is null");
        }

        return "redirect:/";
    }

    public void kakaoDisconnect(String accessToken) throws JsonProcessingException {
        // HTTP Header 생성
        HttpHeaders headers = new HttpHeaders();
        headers.add("Authorization", "Bearer " + accessToken);
        headers.add("Content-type", "application/x-www-form-urlencoded");

        // HTTP 요청 보내기
        HttpEntity<MultiValueMap<String, String>> kakaoLogoutRequest = new HttpEntity<>(headers);
        RestTemplate rt = new RestTemplate();
        ResponseEntity<String> response = rt.exchange(
                "https://kapi.kakao.com/v1/user/logout",
                HttpMethod.POST,
                kakaoLogoutRequest,
                String.class
        );

        // responseBody에 있는 정보를 꺼냄
        String responseBody = response.getBody();
        ObjectMapper objectMapper = new ObjectMapper();
        JsonNode jsonNode = objectMapper.readTree(responseBody);

        Long id = jsonNode.get("id").asLong();
        System.out.println("반환된 id: "+id);
    }

로그아웃 시 카카오에서 응답으로 id를 보내주므로 id까지 받으면 로그아웃이 잘된 것을 확인할 수 있다.

profile
하고 싶은 일 한 가지를 하려면

0개의 댓글