OAuth2 로 JSON 파싱하기

알파로그·2023년 4월 17일
0

Spring Boot

목록 보기
38/57

✏️ OAuth2 로 API 응답 받기 V 1.

  • DefaultOAuth2UserService 를 상속받아 외부로부터 응답받은 값을 가공하고 처리한다.
    • loadUser 는 작업을 하기위한 핵심 method 이다.
    • 이렇게 처리하면 로그인까지는 성공하지만,
      응답받은 data 가 여러개일경우 Json 을 String 으로 변환시킨 형태로 모두 username 값으로 저장되게 된다.
      - 즉, 요청한 값을 사용하기 매우 까다로워진다.
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
@Slf4j
public class CustomOAuth2UserService extends DefaultOAuth2UserService {

		// 받아온 data 로 로그인을 하기위한 DI //
    private final MemberService memberService;

    // 소셜 로그인이 성공할 때 실행되는 method //
    @Override
    @Transactional
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        // 공급자로부터 응답받은 data
        OAuth2User oAuth2User = super.loadUser(userRequest);

        // 공급자를 식별하기 위한 값
        String providerTypeCode = userRequest.getClientRegistration().getRegistrationId().toUpperCase();

        // 응답받은 data 중 핵심 요청사항만 추출
        String oauthId = oAuth2User.getName();

        // 식별값과 핵심 요청사항을 합처서 username 으로 사용
        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);

    // OAuth2 공급자로부터 받은 JSON 데이터를 추출합니다.
    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();

        // 추출한 JSON 데이터를 이용하여 UserDetails 객체를 생성합니다.
        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");

        // UserDetails 객체를 생성하여 반환합니다.
        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");

        // 추출한 JSON 데이터를 이용하여 UserDetails 객체를 생성합니다.
        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");

        // UserDetails 객체를 생성하여 반환합니다.
        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 = "";

        // 공급자 식별값으로 naver 일경우만 json 을 파싱
        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());
    }

    //-- Json pathing --//
    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");
    }
}
profile
잘못된 내용 PR 환영

0개의 댓글