OAuth 로그인 확장성 고려한 코드 작성하기

Sol's·2023년 1월 18일
0

팀프로젝트

목록 보기
2/25

OAuth를통해 구글로그인을 진행한 후 페이스북 로그인을 할 수 있게 하려고 시도중 페이스북의 username을 설정하는데 null값이 찍히는 상황을 만낫습니다.

문제

String providerId = oAuth2User.getAttribute("sub");

위 코드가 문제였습니다.
구글에서는 고유 아이디값이 sub지만,
페이스북에서는 고유 아이디값이 id라고 되어있습니다.

  • facebook
  • google
String providerId = oAuth2User.getAttribute("id");

우리는 위 코드처럼 providerId를 바꿔서 저장 할 수 있지만, 추후에 Naver, Kakao 로그인 기능을 확장한다면 유지보수에 굉장히 까다로워질 것입니다.

그럼 이제 이런고민을 할 수 있습니다.

어떻게 확장성을 고려한 코드를 작성 할 수 있을까?

다형성 활용

이제 Interface를 만들어 확장을 해볼것입니다.

public interface OAuth2UserInfo {
    String getProviderId();

    String getProvider();

    String getEmail();

    String getName();
}

그리고 OAuth2UserInfo를 implements하는 Google,FaceBook 클래스를 만들것입니다.

  • FacebookUserInfo
public class FacebookUserInfo implements OAuth2UserInfo{
    private Map<String, Object> attributes; // oAuth2User.getAttributes()

    public FacebookUserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return (String) attributes.get("id");
    }

    @Override
    public String getProvider() {
        return "facebook";
    }

    @Override
    public String getEmail() {
        return (String) attributes.get("email");
    }

    @Override
    public String getName() {
        return (String) attributes.get("name");
    }
}
  • GoogleUserInfo
public class GoogleUserInfo implements OAuth2UserInfo{
    private Map<String, Object> attributes; // oAuth2User.getAttributes()

    public GoogleUserInfo(Map<String, Object> attributes) {
        this.attributes = attributes;
    }

    @Override
    public String getProviderId() {
        return (String) attributes.get("sub");
    }

    @Override
    public String getProvider() {
        return "google";
    }

    @Override
    public String getEmail() {
        return (String) attributes.get("email");
    }

    @Override
    public String getName() {
        return (String) attributes.get("name");
    }
}

이제 PrincipalOauth2UserService클래스의 로직을 변경할 것입니다.

@Service
public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
    @Autowired
    private BCryptPasswordEncoder encoder;

    @Autowired
    private UserRepository userRepository;


    /**
     * 함수 종료시 @AuthenticationPrincipal 어노테이션이 만들어 집니다.
     */
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {

        /**
         * 구글로그인 버튼 클릭 -> 구글 로그인창 -> 로그인 완료 -> code를 리턴(OAuth-Client라이브러리)
         * 받은 코드를 통해 -> AccessToken요청 = (매개변수인)userRequest 정보 -> loadUser함수 호출 -> 구글로부터 회원피로필을 받을 수 있다.
         *
         */
        OAuth2User oAuth2User = super.loadUser(userRequest);

		//회원가입 강제진행
        String provider = userRequest.getClientRegistration().getRegistrationId(); //Google
        String providerId = oAuth2User.getAttribute("sub");
        String username = provider + "_" + providerId;
        String password = encoder.encode("겟인데어");
        String email = oAuth2User.getAttribute("email");
        UserRole role = UserRole.ROLE_USER;

        System.out.println("user name = " + username);
        User userEntity = userRepository.findByUsername(username);

        if (userEntity == null) {
            System.out.println("구글 로그인이 최초입니다.");
            User newUser = User.builder()
                    .username(username)
                    .password(password)
                    .email(email)
                    .role(role)
                    .provider(provider)
                    .providerId(providerId)
                    .build();
            userRepository.save(newUser);
        } else {
            System.out.println("구글 로그인이 이미 한적이 있습니다. 이미 회원입니다.");
        }

        //회원가입을 강제로 진행시킬것이다.
        //일반 로그인 -> user
        //OAuth 로그인 -> attributes라는 맵을 같이 들고 있다.
        return new PrincipalDetails(userEntity, oAuth2User.getAttributes());
    }
}

위 코드에서 회원가입 강제진행하는 부분을 바꿀것입니다.

 //회원가입 강제 진행
        OAuth2UserInfo oAuth2UserInfo = null;
        if (userRequest.getClientRegistration().getRegistrationId().equals("google")) {
            System.out.println("구글 로그인 요청");
            oAuth2UserInfo = new GoogleUserInfo(oAuth2User.getAttributes());
        } else if (userRequest.getClientRegistration().getRegistrationId().equals("facebook")) {
            System.out.println("페이스북 로그인 요청");
            oAuth2UserInfo = new FacebookUserInfo(oAuth2User.getAttributes());
        } else {
            System.out.println("우리는 구글과 페이스북만 지원합니다.");
        }

        String provider = oAuth2UserInfo.getProvider();
        String providerId = oAuth2UserInfo.getProviderId();
        String username = provider + "_" + providerId;
        String password = encoder.encode("겟인데어");
        String email = oAuth2UserInfo.getEmail();
        UserRole role = UserRole.ROLE_USER;

그리고 다시 로그인을 진행하면

username이 잘 들어오게 됩니다!

자 이제 우리는 구글과 페이스북 둘중 어느곳에서 로그인을 하던 id값이 null이 될 일은 없게 되었습니다.

정리

다형성을 활용하여 코드의 확장을 편하게 하니 확실히 유지보수가 편해졌습니다.

추후에 naver나 kakao 로그인을 추가한다면 조건문에만 추가를 하고 해당 클래스들만 만들어주면 되니 확장하는데에 스트레스를 받지 않게 될것입니다.

하지만 아직 완벽하지는 않다는 생각이 들고있습니다.

클래스가 변경이 되지 않고도 확장이 가능하게 해야하는데 아직 그단계는 아닌거 같아 더 공부를 해봐야 할 거 같습니다.

그래도 코드의 확장성까지 고려하는 것까지 생각하고있는걸 보면 처음보다 실력이 확실히 늘은것 같아서 기분이 좋습니다ㅎㅎ

profile
배우고, 생각하고, 행동해라

0개의 댓글