이번 포스팅의 목적은 왜 공식문서를 볼 줄 알고 왜 봐야 되는지에 대한 제 생각을 적은 글입니다.
블로그검색을 통해 구현한 OAuth2
이 글을 쓰기 전에는 그냥 구글에 다 나와있고 내가 이미 해놨던 정보들이 있기 때문에 OAuth2에 대해 깊게 알지 않아도 복붙해서 사용만 할 줄 안다면 내가 그 기능을 완벽히 알고있다고 판단했었습니다.
이 말도 틀린 말은 아니라고 생각합니다. 결국 기능을 사용할 수 있으면 충분하니까요.
요구사항에 맞는 자료가 존재하지 않는다면 사소한 거 하나 구현할 수 없는 제 모습을 보고 다시 하나하나 구현했습니다.
처음 구글에서 본 OAuth2 코드입니다.
OAuthAttributes
이 코드를 작성할 때 그냥 이렇게 쓰면 되는 거고 이렇게 만들어서 나누면 돼 ~ 이런 식으로 구현했었습니다.
OAuthAttributes를 생성해서 하나 하나 메서드로 쪼갠 후,
Naver, Google, Kakao등 각각 다른 상황 요청을 받을 때 그 상황에 맞는 값을 생성합니다.
이걸 그대로 따라할 땐 아 그냥 이렇게 쓰는 거구나라고만 생각했습니다.
이렇게 구성하면 가독성 좋은 코드, 누가 봐도 군더더기 없는 코드라고 하는 말에 그냥 적용했습니다.
OAuth2UserService
처음 작성할 때 OAuthAttributes 클래스를 생성해서 나누는 과정이 어렵고 복잡해서 제외하고
OAuth2UserService를 바로 작성했습니다.
Google 로그인을 기준으로 작성한 코드입니다.
다음에 또 필요할 때 내가 작성한 코드, 그리고 검색을 통해 똑같이 구현하면 된다고 생각했습니다.
네이버 로그인이 필요하면 검색을 해서 따라하면 되고
카카오 로그인이 필요하면 검색을 해서 똑같이 만들면 된다고 생각했는데,
경험하지 못한 요구사항이 닥쳤을 때 해결할 수 없었기 때문에 처음부터 혼자 구현하는 연습을 했습니다.
공식문서를 보는 것이 왜 중요할까?
많은 정보들이 오고 가면서 누락된 정보나 본인이 필요 없다고 생각하는 데이터는 빼고 올리기 때문에
내가 알고 싶은 모든 정보를 다 알 수가 없습니다.
공식문서를 보고 적용할 줄 아는 힘이 있어야 나중에 어떤 시스템을 구현할 때 막힘없이 진행할 수 있다고 판단했습니다.
위에서 구현한 내용은 Google 로그인, username과 email 정보만 받을 수 있게 설정해놓은 것입니다.
저 두 개만 할 줄 아는 상황에서
누군가 Naver 로그인 구현과 nickname 정보가 필요하다고 요청했을 때 공식문서를 통해 구현해야합니다.
https://developers.naver.com/docs/login/profile/profile.md
네이버 회원 API 명세입니다.
위에서 제가 본 내용은 그냥 저렇게 하면 돼 였지만
이 공식문서에서 정확한 사용방법을 알 수 있었습니다.
제가 새롭게 작성한 OAuth2UserService 코드를 보겠습니다.
OAuth2UserService - Naver
공식 문서를 직접 볼 줄 알아야 요구사항에 맞는 값을 불러올 수 있었습니다.
이번에는 카카오를 이용해보겠습니다.
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#req-user-info
OAuth2UserService - Kakao
카카오 사용법과 네이버 사용법은 아예 달랐기 때문에, 정확하게 공식문서를 읽고 어떻게 사용하는지 파악해야 합니다.
네이버는 getAttribute("response")를 이용해서 값을 불러왔지만
카카오는 properties, kakao_account 정보를 이용해야합니다.
Properties 사용
https://developers.kakao.com/docs/latest/ko/kakaologin/prerequisite#kakao-login-preview
properties를 이용해서 얻을 수 있는 값을 설정합니다.
저는 nickname을 사용하겠습니다.
정상적으로 불러온 값을 확인할 수 있습니다.
kakao_account 사용
https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api#kakaoaccount
kakao_account를 이용해서 얻을 수 있는 값을 설정합니다.
email 값을 사용할 수 있습니다.
정상적으로 값을 불러올 수 있습니다.
추가적으로 위에 코드를 봤을 때 kakao_account를 받아왔을 때 nickname값은 얻을 수 없다는 것이
null 값으로 나타남을 알 수 있습니다.
전체코드
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Service
public class OAuth2UserServiceImpl extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
String registrationId = userRequest.getClientRegistration().getRegistrationId();
String nameAttribute = "";
OAuth2User oAuth2User = super.loadUser(userRequest);
Map<String, Object> attributes = new HashMap<>();
if (registrationId.equals("kakao")) {
attributes.put("provider", "kakao");
attributes.put("id", oAuth2User.getAttribute("id"));
Map<String, Object> propMap
= oAuth2User.getAttribute("properties");
attributes.put("nickname", propMap.get("nickname"));
Map<String, Object> accountMap
= oAuth2User.getAttribute("kakao_account");
attributes.put("email", accountMap.get("email"));
nameAttribute = "email";
}
if (registrationId.equals("naver")) {
attributes.put("provider", "naver");
Map<String, Object> responseMap = oAuth2User.getAttribute("response");
attributes.put("id", responseMap.get("id"));
attributes.put("email", responseMap.get("email"));
attributes.put("nickname", responseMap.get("nickname"));
nameAttribute = "email";
}
return new DefaultOAuth2User(
Collections.singleton(new SimpleGrantedAuthority("USER")),
attributes,
nameAttribute
);
}
}
전체코드입니다.
이번 경험을 통해 접해보지 못했던 상황이여도 공식문서를 통해 원하는 값을 찾아낼 수 있었고
이렇게 하나 하나 다 작성하면서
맨 처음 작성했던 OAuthAttribute를 사용하는 이유를 확실하게 알았습니다.
기능을 제대로 나눌 수 있고 가독성도 너무 훌륭합니다.
이게 제가 개발하면서 항상 추구했던 클린 코드의 형식입니다.