이룸 프로젝트
[이룸] 240304 카카오 로그인 구현하기
쿠마냥
2024. 3. 7. 15:43
카카오 로그인은 어떻게 진행될까?
MemberController - kakaoLogin()
// 카카오 로그인
@GetMapping("/auth/callback/kakao")
public ResponseEntity<BaseDto<String>> kakaoLogin(@RequestParam String code,
HttpServletResponse response) throws UnsupportedEncodingException, JsonProcessingException {
String message = kakaoService.kakaoLogin(code, response);
return ResponseEntity.ok(new BaseDto<>(null, message, HttpStatus.OK));
}
KakaoService 클래스
의존성 주입
private final MemberRepository memberRepository;
private final RestTemplate restTemplate;
private final JwtUtil jwtUtil;
private final RefreshTokenService refreshTokenService;
필드
@Value("${kakao.client-id}")
private String kakaoClientId;
@Value("${kakao.redirect-uri}")
private String kakaoRedirectUri;
@Value("${kakao.client-secret}")
private String kakaoClientSecret;
kakaoLogin()
public String kakaoLogin(String code, HttpServletResponse response) throws JsonProcessingException, UnsupportedEncodingException {
// 카카오 액세스 토큰을 받는다.
String kakaoAccessToken = getToken(code);
// 카카오 유저 정보를 받는다.
KakaoUserInfoDto kakaoUserInfo = getKakaoUserInfo(kakaoAccessToken);
// 카카오 유저 정보로부터 멤버를 추출해낸다.
Member kakaoUser = registerKakaoUserIfNeeded(kakaoUserInfo);
// 강제 로그인시켜 인증 객체를 뽑아낸다.
Authentication authentication = forceLogin(kakaoUser);
// 이룸의 액세스 토큰을 생성해서 쿠키에 담는다.
String accessToken = jwtUtil.createAccessToken(kakaoUserInfo.getEmail(), kakaoUser.getRole());
jwtUtil.addJwtToCookie(accessToken, response, JwtUtil.AUTHORIZATION_HEADER);
// 이룸의 리프레시 토큰을 생성해서 쿠키에 담는다.
String refreshToken = jwtUtil.createRefreshToken(kakaoUserInfo.getEmail());
jwtUtil.addJwtToCookie(refreshToken, response, JwtUtil.REFRESH_TOKEN_HEADER);
// 만들어진 리프레시 토큰을 저장한다.
refreshTokenService.saveRefreshToken(kakaoUser.getEmail());
// 카카오 사용자의 닉네임을 반환한다.
return kakaoUser.getNickname();
}
getToken()
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", kakaoClientId);
body.add("redirect_uri", kakaoRedirectUri);
body.add("code", code);
body.add("client_secret", kakaoClientSecret);
// HTTP 요청 body를 구성
RequestEntity<MultiValueMap<String, String>> requestEntity = RequestEntity
.post(uri)
.headers(headers)
.body(body);
// HTTP 요청 보내기. String 타입의 requestEntity를 받겠다는 뜻.
ResponseEntity<String> response = restTemplate.exchange(
requestEntity,
String.class
);
// HTTP 응답 (JSON) -> 액세스 토큰 파싱
JsonNode jsonNode = new ObjectMapper().readTree(response.getBody());
// 파싱된 Json 객체에서 "access_token" 필드의 값(액세스 토큰)을 문자열로 추출하여 반환
return jsonNode.get("access_token").asText();
}
- 전달받은 인가 코드를 사용해 Kakao OAuth 서버로부터 액세스 토큰을 요청 → 응답으로 받은 JSON에서 액세스 토큰을 파싱하여 반환
- **MultiValueMap**이란? Map처럼 키-밸류로 저장하지만, 하나의 키에 여러 값(값의 리스트를) 매핑할 수 있음. 이중 http body를 구성하기 위해 LinkedMultiValueMap을 가용하는데, 내부적으로 LinkedHashMap과 ArrayList를 사용하여 순서를 유지하면서 여러 밸류를 저장. 즉, 입력 순서대로 키와 값이 유지되며, 하나의 키에 여러 밸류가 리스트 형태로 저장됨.
getKakaoUserInfo()
private KakaoUserInfoDto getKakaoUserInfo(String accessToken) throws JsonProcessingException {
log.info("accessToken: " + accessToken);
// 요청 URL 만들기
URI uri = UriComponentsBuilder
.fromUriString("https://kapi.kakao.com")
.path("/v2/user/me")
.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<Void> requestEntity = RequestEntity
.get(uri)
.headers(headers)
.build();
// HTTP 요청 보내기
ResponseEntity<String> response = restTemplate.exchange(
requestEntity,
String.class
);
// 받은 응답 본문을 JsonNode로 파싱한 뒤, 각 필드에서 필요한 정보 추출
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();
String profileImageUrl = jsonNode.get("kakao_account")
.get("profile").get("profile_image_url").asText();
log.info("카카오 사용자 정보: " + id + ", " + nickname + ", " + email);
// KakaoUserInfoDto에 정보 담아서 반환
return new KakaoUserInfoDto(id, nickname, email, profileImageUrl);
}
registerKakaoUserIfNeeded()
private Member registerKakaoUserIfNeeded(KakaoUserInfoDto kakaoUserInfo) {
// DB 에 중복된 Kakao Id 가 있는지 확인
Long kakaoId = kakaoUserInfo.getId();
log.info("kakaoId : "+ kakaoId);
Member kakaoUser = memberRepository.findByKakaoId(kakaoId).orElse(null);
if (kakaoUser == null) {
// 카카오 사용자 email 동일한 email 가진 회원이 있는지 확인
String kakaoEmail = kakaoUserInfo.getEmail();
Member sameEmailUser = memberRepository.findByEmail(kakaoEmail).orElse(null);
// 이미 가입되어 있는 동일한 email이 있으면
if (sameEmailUser != null) {
kakaoUser = sameEmailUser;
// 기존 회원정보에 카카오 Id 추가 + isSocialMember 컬럼을 true로 바꿔줌
kakaoUser = kakaoUser.kakaoIdUpdate(kakaoId);
} else {
// 새로운 사용자라면 멤버 객체로 만들어서 저장
kakaoUser = new Member(kakaoEmail, "", kakaoUserInfo.getNickname(), kakaoId, kakaoUserInfo.getProfileImageUrl());
}
memberRepository.save(kakaoUser);
}
return kakaoUser;
}
forceLogin()
private Authentication forceLogin(Member kakaoUser) {
UserDetails userDetails = new UserDetailsImpl(kakaoUser);
Authentication authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
return authentication;
}
- 카카오 로그인은 이룸의 스프링 시큐리티를 통과하지 않기 때문에 강제로 인증 객체를 생성해 줘야 함. UserDetails를 만들고 Authentication 객체를 생성한 다음 SecuxrityContextHolder에 담아주면 카카오 로그인 끝!
참고자료:
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api
Kakao Developers
카카오 API를 활용하여 다양한 어플리케이션을 개발해보세요. 카카오 로그인, 메시지 보내기, 친구 API, 인공지능 API 등을 제공합니다.
developers.kakao.com
브라우저와 Postman으로 카카오 OAuth 구현하기
지난번 포스팅에서 OAuth Flow 중 Authorization Code 인증의 Flow에 대해 알아보았는데, 오늘은 실제 카카오와 연동하여 OAuth 진행 후 개인 정보를 가져오는 것을 브라우저와 Postman으로 구현해 보겠습니
jedidev.tistory.com
https://kyuu-ng.tistory.com/86
|Spring & OAuth2| 카카오 로그인/회원가입 Postman으로 테스트하기
이번주는 클론코딩 주차로 우리팀은 원티드의 커뮤니티를 클론코딩 하기로하였다 ⌨️ 내가 맡은 기능은 카카오 로그인 😎 시작은 쉬웠으나 테스트부터 쉽지 않아져서 좌절하고 있는 와중에
kyuu-ng.tistory.com