Kakao Developers
카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.
developers.kakao.com
카카오 디벨로퍼스 사이트를 이용해 카카오 로그인을 사용할 수 있도록 하겠습니다!
저는 그냥 이름 대충 반달곰으로 해줬습니다 ㅋㅋㅋㅋ
아이콘 설정을 안했더니
앱 등록이 안되서
저번에 쓴 스프링 르탄이 이모티콘을 사용해주었습니다.
이렇게 설정을 마치면
비즈니스 - 개인 개발자 비즈 앱으로 설정이 가능합니다.
개인 개발자 비즈 앱 전환을 눌려줍니다.
이메일 필수 동의를 위해서 하는 거라 저렇게 설정해주고 전환해줄게요!
카카오 로그인으로 들어가서 로그인을 활성화 해주세요!
더 밑으로 내려가시면 Redirect URI 작성하는 곳이 있는데,
저는 개인적으로 개발하는 스프링 코드라 http://localhost:8080을 입력해주고 저장해주었습니다.
카카오 로그인 - 동의항목에 들어가시면
쭉 나오는 것 중에 아마 이 세개만 가능하십니다.
닉네임과 프로필 사진은
사진처럼 이렇게 설정해주시면 됩니다!
이메일을 이렇게 설정해주었어요.
카카오 디벨로퍼스 앱 설정을 마치고 나면 제 코드에서도 카카오 로그인을 할 수 있습니다!
코드를 공유해볼게요!
RestTemplateConfig
package com.sparta.myselectshop.config;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
// RestTemplate 으로 외부 API 호출 시 일정 시간이 지나도 응답이 없을 때
// 무한 대기 상태 방지를 위해 강제 종료 설정
.setConnectTimeout(Duration.ofSeconds(5)) // 5초
.setReadTimeout(Duration.ofSeconds(5)) // 5초
.build();
}
}
User
package com.sparta.myselectshop.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Setter
@NoArgsConstructor
@Table(name = "users")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false)
private String password;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
@Enumerated(value = EnumType.STRING)
private UserRoleEnum role;
private Long kakaoId;
public User(String username, String password, String email, UserRoleEnum role) {
this.username = username;
this.password = password;
this.email = email;
this.role = role;
}
public User(String username, String password, String email, UserRoleEnum role, Long kakaoId) {
this.username = username;
this.password = password;
this.email = email;
this.role = role;
this.kakaoId =kakaoId;
}
public User kakaoIdUpdate(Long kakaoId) {
this.kakaoId = kakaoId;
return this;
}
}
UserController
@GetMapping("/user/kakao/callback")
public String kakaoLogin(@RequestParam String code, HttpServletResponse response) throws JsonProcessingException {
String token = kakaoService.kakaoLogin(code);
Cookie cookie = new Cookie(JwtUtil.AUTHORIZATION_HEADER, token.substring(7));
cookie.setPath("/");
response.addCookie(cookie);
return "redirect:/";
}
KakaoUserInfoDto
package com.sparta.myselectshop.dto;
import lombok.Getter;
import lombok.NoArgsConstructor;
@Getter
@NoArgsConstructor
public class KakaoUserInfoDto {
private Long id;
private String nickname;
private String email;
public KakaoUserInfoDto(Long id, String nickname, String email) {
this.id = id;
this.nickname = nickname;
this.email = email;
}
}
KakaoService
package com.sparta.myselectshop.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.myselectshop.dto.KakaoUserInfoDto;
import com.sparta.myselectshop.entity.User;
import com.sparta.myselectshop.entity.UserRoleEnum;
import com.sparta.myselectshop.jwt.JwtUtil;
import com.sparta.myselectshop.repository.UserRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;
import java.net.URI;
import java.util.UUID;
@Slf4j(topic = "KAKAO Login")
@Service
@RequiredArgsConstructor
public class KakaoService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
private final RestTemplate restTemplate;
private final JwtUtil jwtUtil;
public String kakaoLogin(String code) throws JsonProcessingException {
// 1. "인가 코드"로 "액세스 토큰" 요청
String accessToken = getToken(code);
// 2. 토큰으로 카카오 API 호출 : "액세스 토큰"으로 "카카오 사용자 정보" 가져오기
KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(accessToken);
// 3. 필요시에 회원가입
User kakaoUser = registerKakaoUserIfNeeded(kakaoUserInfo);
// 4. JWT 토큰 반환
String createToken = jwtUtil.createToken(kakaoUser.getUsername(), kakaoUser.getRole());
return createToken;
}
private String getToken(String code) throws JsonProcessingException {
log.info("인가코드 : " + code);
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("https://kauth.kakao.com")
.path("/oauth/token")
.encode()
.build()
.toUri();
// 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", "6c6411d43828fc9c0ae68f7c5f4ad7d9");
body.add("redirect_uri", "http://localhost:8080/api/user/kakao/callback");
body.add("code", code);
RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
.post(uri)
.headers(headers)
.body(body);
// HTTP 요청 보내기
ResponseEntity<String> response = restTemplate.exchange(
requestEntity,
String.class
);
// HTTP 응답 (JSON) -> 액세스 토큰 파싱
JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
return jsonNode.get("access_token").asText();
}
private KakaoUserInfoDto getKakaoUserInfo(String accessToken) throws JsonProcessingException {
log.info("accessToken : " + accessToken);
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("https://kapi.kakao.com")
.path("/v2/user/me")
.encode()
.build()
.toUri();
// HTTP Header 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Authorization", "Bearer " + accessToken);
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
.post(uri)
.headers(headers)
.body(new LinkedMultiValueMap<>());
// HTTP 요청 보내기
ResponseEntity<String> response = restTemplate.exchange(
requestEntity,
String.class
);
JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
Long id = jsonNode.get("id").asLong();
String nickname = jsonNode.get("properties")
.get("nickname").asText();
String email = jsonNode.get("kakao_account")
.get("email").asText();
log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
return new KakaoUserInfoDto(id, nickname, email);
}
private User registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
// DB 에 중복된 Kakao Id 가 있는지 확인
Long kakaoId = kakaoUserInfo.getId();
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.kakaoIdUpdate(kakaoId);
} else {
// 신규 회원가입
// password: random UUID
String password = UUID.randomUUID().toString();
String encodedPassword = passwordEncoder.encode(password);
// email: kakao email
String email = kakaoUserInfo.getEmail();
kakaoUser = new User(kakaoUserInfo.getNickname(), encodedPassword, email, UserRoleEnum.USER, kakaoId);
}
userRepository.save(kakaoUser);
}
return kakaoUser;
}
}
UserRepository
package com.sparta.myselectshop.repository;
import com.sparta.myselectshop.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
Optional<User> findByEmail(String email);
Optional<User> findByKakaoId(Long kakaoId);
}
이렇게 코드를 짜보았습니다~
질문은 언제나 환영입니다!!
'Spring' 카테고리의 다른 글
[Spring] 스프링 카카오 소셜 로그인 구현 | 깃 카카오 소셜 로그인 코드 (5) | 2024.09.25 |
---|---|
[Spring] 컨테이너 및 도커 개념정리 (0) | 2024.09.19 |
[Spring] Controller 테스트 코드 작성하기 (0) | 2024.09.13 |
[Spring] AOP 관점지향 프로그래밍 | 횡단 관심사 분리 (0) | 2024.09.10 |
[Spring] 단위 테스트 | 테스트 코드 작성법 (0) | 2024.09.10 |