ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 카카오 로그인
    스프링 2023. 12. 31. 16:30

    properties

     

    spring.security.oauth2.client.registration.kakao.client-id = 
    spring.security.oauth2.client.registration.kakao.client-secret = 
    spring.security.oauth2.client.registration.kakao.redirect-uri = 
    spring.security.oauth2.client.registration.kakao.client-name = kakao
    spring.security.oauth2.client.registration.kakao.scope = profile_nickname,profile_image,account_email,birthday
    spring.security.oauth2.client.registration.kakao.authorization-grant-type = authorization_code
    spring.security.oauth2.client.registration.kakao.client-authentication-method = POST
    spring.security.oauth2.client.provider.kakao.authorization-uri = https://kauth.kakao.com/oauth/authorize
    spring.security.oauth2.client.provider.kakao.token-uri = https://kauth.kakao.com/oauth/token
    spring.security.oauth2.client.provider.kakao.user-info-uri = https://kapi.kakao.com/v2/user/me
    spring.security.oauth2.client.provider.kakao.user-name-attribute = id


    Controller


    @RestController
    @Slf4j
    @RequiredArgsConstructor
    public class KakaoController {

        private final KakaoUserService kakaoService;

        //카카오 로그인
        @GetMapping("api/user/kakao/callback")
        public ResponseEntity<Message> kakaoLogin(@RequestParam String code, HttpServletResponse response) throws JsonProcessingException {
            return kakaoService.kakaoLogin(code,response);
        }
        
    }


    User정보를 받아올 Dto


    @Getter
    @NoArgsConstructor
    public class KakaoUserInfoDto {
        private Long id;
        private String nickName;
        private String email;
        private String profileImage;
        private String userBirthDay;

        public KakaoUserInfoDto(Long id, String username, String email, String profileImage) {
            this.id = id;
            this.nickName = username;
            this.email = email;
            this.profileImage = profileImage;
            this.userBirthDay = userBirthDay;
        }
    }


    Service

     

    @Slf4j
    @Service
    @RequiredArgsConstructor
    public class KakaoUserService {
        private final PasswordEncoder passwordEncoder;
        private final UserRepository userRepository;
        private final JwtUtil jwtUtil;

        @Value("${spring.security.oauth2.client.registration.kakao.client-id}")
        private String kakaoClientId;

        @Value("${spring.security.oauth2.client.registration.kakao.client-secret}")
        private String kakaoClientSecret;

        @Value("${spring.security.oauth2.client.registration.kakao.redirect-uri}")
        private String kakaoRedirectUri;
    //    카카오로그인 URL
    //    kauth.kakao.com/oauth/authorize?client_id=58fc709768dcd1b5dc6a4f72874b6e2b&redirect_uri=https://kim-or-jang.shop/api/user/kakao/callback&response_type=code
    //    kauth.kakao.com/oauth/authorize?client_id=58fc709768dcd1b5dc6a4f72874b6e2b&redirect_uri=http://localhost:8080/api/user/kakao/callback&response_type=code
    //    https://accounts.kakao.com/login/?continue=https%3A%2F%2Fkauth.kakao.com%2Foauth%2Fauthorize%3Fresponse_type%3Dcode%26redirect_uri%3Dhttps%253A%252F%252Fkim-or-jang.shop%252Fapi%252Fuser%252Fkakao%252Fcallback%26through_account%3Dtrue%26client_id%3D58fc709768dcd1b5dc6a4f72874b6e2b#webTalkLogin
        @Transactional
        public ResponseEntity<Message> kakaoLogin(String code, HttpServletResponse response) throws JsonProcessingException {
            // 1. "인가 코드"로 "액세스 토큰" 요청
            String accessToken = getToken(code);    //잘 안되면 try-catch로 오류 확인해보기

            // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
            KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);

            // 3. 필요시에 회원가입
            User kakaoUser = registerKakaoUserIfNeeded(kakaoUserInfo);

            // 4. JWT 토큰 반환
            jwtUtil.createAndSetToken(response, kakaoUser.getEmail(), kakaoUser.getId());

            return new ResponseEntity<>(new Message("카카오 로그인 성공",null),HttpStatus.OK);
        }

        // 1. "인가 코드"로 "액세스 토큰" 요청(토큰 받기)
        private String getToken(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", kakaoClientId);
            body.add("client_secret", kakaoClientSecret);
            body.add("redirect_uri", kakaoRedirectUri);
    //        body.add("redirect_uri", "http://localhost:8080/api/user/kakao/callback");
            body.add("code", code);
            

            // HTTP 요청 보내기
            HttpEntity<MultiValueMap<String, String>> kakaoTokenRequest =
                    new HttpEntity<>(body, headers);
            ResponseEntity<String> response = new RestTemplate().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);
            String accessToken = String.valueOf(jsonNode.get("access_token"));
            log.info("Access Token: {}", accessToken);
            return jsonNode.get("access_token").asText();
        }

        // 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
        private KakaoUserInfoDto getKakaoUserInfo(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
            );
            String responseBody = response.getBody();
            ObjectMapper objectMapper = new ObjectMapper();
            JsonNode jsonNode = objectMapper.readTree(responseBody);
            Long id = jsonNode.get("id").asLong();
            String nickname = jsonNode.get("properties")
                    .get("nickname").asText();
            String email = jsonNode.get("kakao_account")
                    .get("email").asText();
            String profileImage = jsonNode.get("properties")
                    .get("profile_image").asText();

            log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email + ", " + profileImage);
            return new KakaoUserInfoDto(id, nickname, email, profileImage);
        }

        // 3. 필요시에 회원가입
        private User registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
            // DB 에 중복된 Kakao Id 가 있는지 확인
            Long kakaoId = kakaoUserInfo.getId();
            String profileImage = "https://kim-or-jang-calendar-profile.s3.ap-northeast-2.amazonaws.com/user.png";
            if (kakaoUserInfo.getProfileImage() != null) profileImage = kakaoUserInfo.getProfileImage();
            User kakaoUser = userRepository.findByKakaoId(kakaoId).orElse(null);
            if (kakaoUser == null) {
                // 카카오 사용자 email 동일한 email 가진 회원이 있는지 확인
                String kakaoEmail = kakaoUserInfo.getEmail();
                User sameEmailUser = userRepository.findByEmail(kakaoEmail).orElse(null);
                if (sameEmailUser != null) {
                    kakaoUser = sameEmailUser;
                    // 기존 회원정보에 카카오 Id, 프로필 이미지 추가
                    kakaoUser = kakaoUser.kakaoUpdate(kakaoId, profileImage);
                } else {
                    // 신규 회원가입
                    // password: random UUID
                    String password = UUID.randomUUID().toString();
                    String encodedPassword = passwordEncoder.encode(password);

                    // email: kakao email
                    String email = kakaoUserInfo.getEmail();

                    kakaoUser = new User(kakaoId, null, null, email, kakaoUserInfo.getNickName(), encodedPassword, profileImage,UserRoleEnum.USER);
                }
                userRepository.save(kakaoUser);
            }
            return kakaoUser;
        }
    }

    '스프링' 카테고리의 다른 글

    구글 로그인  (0) 2023.12.31
    네이버 로그인  (0) 2023.12.31
    DB접속  (0) 2023.12.31
    스프링 부트 (회원가입,로그인,토큰발급)api명세서  (0) 2023.04.25
    스프링 JWT (코드스니펫)  (0) 2023.04.20
Designed by Tistory.