✏️ OAuth2 로 API 응답 받기 V 1.
DefaultOAuth2UserService
를 상속받아 외부로부터 응답받은 값을 가공하고 처리한다.
loadUser
는 작업을 하기위한 핵심 method 이다.
- 이렇게 처리하면 로그인까지는 성공하지만,
응답받은 data 가 여러개일경우 Json 을 String 으로 변환시킨 형태로 모두 username 값으로 저장되게 된다.
- 즉, 요청한 값을 사용하기 매우 까다로워진다.
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final MemberService memberService;
@Override
@Transactional
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String providerTypeCode = userRequest.getClientRegistration().getRegistrationId().toUpperCase();
String oauthId = oAuth2User.getName();
String username = providerTypeCode + "__%s".formatted(oauthId);
Member member = memberService.whenSocialLogin(providerTypeCode, username).getData();
return new CustomOAuth2User(member.getUsername(), member.getPassword(), member.getGrantedAuthorities());
}
}
class CustomOAuth2User extends User implements OAuth2User {
public CustomOAuth2User(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
@Override
public Map<String, Object> getAttributes() {
return null;
}
@Override
public String getName() {
return getUsername();
}
}
✏️ 응답받은 JSON 파일을 Field 값으로 변환하기 V 2.1.
- 공급자로 부터 응답받은
OAuth2User oAuth2User
에는 우리가 요청한 모든 정보가 포함 되어있다.
- 이 data 뭉텡이에서 원하는 값을 추출해 원하는 field 에 담기위해선
oAuth2User
를 Map 으로 변환시켜주어야 한다.
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
Map<String, Object> attributes = user.getAttributes();
attributes
는 우리가 요청한 값이 정리되어있는데 요청값을 Key 로 원하는 값을 get 할 수 있다.
- 참고로 value 의 타입이 Object 기 때문에 알맞은 타입으로 케스팅을 해주어야 한다.
- 예제에서는
UserDetails
이라는 참조변수를 사용했지만 바로 enetity 를 생성해 값을 주입한후 처리를 해주는 것도 가능하다.
- 이 방법으로 대부분의 문제는 해결할 수 있지만,
oAuth2User.getAttributes();
로 생성한 map 이외의 장소에 우리가 요청한 값이 포함되어있는 경우도 있다.
@Service
public class NaverOAuth2UserService extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
Map<String, Object> attributes = oAuth2User.getAttributes();
UserDetails userDetails = buildUserDetails(attributes, oAuth2User);
return new DefaultOAuth2User(oAuth2User.getAuthorities(), attributes, userDetails.getUsername());
}
private UserDetails buildUserDetails(Map<String, Object> attributes, OAuth2User oAuth2User) {
String email = (String) attributes.get("email");
String name = (String) attributes.get("name");
String id = (String) oAuth2User.getAttribute("id");
String gender = (String) attributes.get("gender");
return oAuth2User.builder()
.username(email)
.password("")
.roles("USER")
.build();
}
}
✏️ getAttributes() 에 요청한 값이 존재하지 않을 때 V 2.2.
- 공급자에 따라서
Attributes
말고 Attributes
안의 value 인 response
에 한번 더 담겨있느 경우 도 있다.
- 이 경우에는 차래대로 값을 get 해줘야 한다.
Attributes
는 value 가 Object 타입이기 때문에 Map 으로 형변환을 해서 get 해줘야 한다.
Map<String, Object> attributes = user.getAttributes();
Map<String, Object> response =
(Map<String, Object>) attributes.get("response");
- 이렇게 추출한 객체로 포함되어있는 값을 추출해 변수에 담아주면 된다.
@Service
public class NaverOAuth2UserService extends DefaultOAuth2UserService {
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User user = super.loadUser(userRequest);
Map<String, Object> attributes = user.getAttributes();
Map<String, Object> response = (Map<String, Object>) attributes.get("response");
UserDetails userDetails = buildUserDetails(attributes, response);
return new DefaultOAuth2User(user.getAuthorities(), attributes, userDetails.getUsername());
}
private UserDetails buildUserDetails(Map<String, Object> attributes, Map<String, Object> response) {
String email = (String) response.get("email");
String name = (String) response.get("name");
String id = (String) response.get("id");
String gender = (String) response.get("gender");
return User.builder()
.username(email)
.password("")
.roles("USER")
.build();
}
}
✏️ 현재 프로젝트에 적용해보기
- 진행중인 프로젝트는 naver, google, kakao 에서 소셜로그인을 하고있다.
- 내가 원하는 값은 id 인데 google 과 kakao 는
oAuth2User.getName()
만으로도 id 값을 추출할 수 있지만 naver 는 별도로 값을 추출해주어야 한다.
- 즉, naver 소셜로그인만 별도로 Json 형태의 파일을 파싱해주어야 원하는 값을 얻을 수 있게된다.
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class CustomOAuth2UserService extends DefaultOAuth2UserService {
private final MemberService memberService;
@Override
@Transactional
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
OAuth2User oAuth2User = super.loadUser(userRequest);
String providerTypeCode = userRequest.getClientRegistration().getRegistrationId().toUpperCase();
String oauthId = "";
if (providerTypeCode.equals("NAVER"))
oauthId = jsonPath(oAuth2User);
else
oauthId = oAuth2User.getName();
String username = providerTypeCode + "__%s".formatted(oauthId);
Member member = memberService.whenSocialLogin(providerTypeCode, username).getData();
return new CustomOAuth2User(member.getUsername(), member.getPassword(), member.getGrantedAuthorities());
}
private String jsonPath(OAuth2User oAuth2User) {
Map<String, Object> attributes = oAuth2User.getAttributes();
Map<String, Object> response = (Map<String, Object>) attributes.get("response");
return (String) response.get("id");
}
}