본문 바로가기

Backend/Spring

[Spring Boot] 회원 가입 & 로그인(5)

반응형
SMALL

이번에는 소셜 로그인 코드를 작성해보도록 하겠습니다.

 

소셜 로그인을 위한 설정은 많은 블로그에서 많이 다루고 있어서 생략 하도록 하겠습니다.

 

소셜 로그인을 사용하기 위하여 build.gradle에 추가를 해줍니다.

build.gradle

 // Oauth2.0
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

 

소셜 로그인을 사용하기 위하여 OAuth2Attribute와 OAuth2UserService<OAuth2UserRequest, OAuth2User>를 상속받은 CustomOAuth2UserService를 작성하도록 하겠습니다.

 

OAuth2Attribute.java

@Getter
public class OAuth2Attribute {
    // OAuth2에서 받아온 속성 정보
    private Map<String, Object> attributes;
    private String email;
    private String name;
    private String profileImgUrl;
    private String provider;
    private String nameAttributeKey;

    @Builder
    public OAuth2Attribute(Map<String, Object> attributes, String email, String name, String profileImgUrl, String provider, String nameAttributeKey) {
        this.attributes = attributes;
        this.email = email;
        this.name = name;
        this.profileImgUrl = profileImgUrl;
        this.provider = provider;
        this.nameAttributeKey = nameAttributeKey;
    }

    // OAuth2 속성을 바탕으로 OAuth2Attribute 객체를 생성하는 정적 팩토리 메서드
    public static OAuth2Attribute of(String provider, String userNameAttributeName, Map<String, Object> attributes) {

        switch (provider) {
            case "google":
                return ofGoogle(provider, userNameAttributeName, attributes);
            case "naver":
                return ofNaver(provider, "id", attributes);
            case "kakao":
                return ofKakao(provider, "email", attributes);
            default:
                throw new DoNotSearchProviderException(ErrorCode.DO_NOT_SEARCH_PROVIDER);
        }
    }

    // Google OAuth2 속성을 사용하여 OAuth2Attribute 객체를 생성
    private static OAuth2Attribute ofGoogle(String provider, String userNameAttributeName, Map<String, Object> attributes) {
        return OAuth2Attribute.builder()
                .provider(provider)
                .name((String) attributes.get("name"))
                .email((String) attributes.get("email"))
                .profileImgUrl((String) attributes.get("picture"))
                .attributes(attributes)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    // Naver OAuth2 속성을 사용하여 OAuth2Attribute 객체를 생성
    private static OAuth2Attribute ofNaver(String provider, String userNameAttributeName, Map<String, Object> attributes) {
        Map<String, Object> response = (Map<String, Object>) attributes.get("response");

        return OAuth2Attribute.builder()
                .provider(provider)
                .name((String) response.get("name"))
                .email((String) response.get("email"))
                .profileImgUrl((String) response.get("profile_image"))
                .attributes(response)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    // Kakao OAuth2 속성을 사용하여 OAuth2Attribute 객체를 생성
    private static OAuth2Attribute ofKakao(String provider, String userNameAttributeName, Map<String, Object> attributes) {
        Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account");

        Map<String, Object> kakaoProfile = (Map<String, Object>) kakaoAccount.get("profile");

        return OAuth2Attribute.builder()
                .provider(provider)
                .name((String) kakaoProfile.get("nickname"))
                .email((String) kakaoAccount.get("email"))
                .profileImgUrl((String) kakaoProfile.get("profile_image_url"))
                .attributes(kakaoAccount)
                .nameAttributeKey(userNameAttributeName)
                .build();
    }

    // OAuth2Attribute를 기반으로 User 엔티티로 변환
    public User toOAuth2UserEntity() {
        return User.builder()
                .email(email)
                .name(name)
                .profileImgUrl(profileImgUrl)
                .role(Role.GUEST)
                .provider(provider)
                .build();
    }
}

여기서 가장 헷갈렸던 부분이 google은 get으로 바로 정보를 가져올 수 있지만 naver와 kakao는 한번 아니면 두번 벗겨내서 정보를 가져와야하고 naver는 response로 값을 받아와야하고 kakao는 kakaoAccount, kakaoAccount 내부의 kakaoProfile에서 정보를 가져와야 한다는 점이였습니다.

 

CustomOAuth2UserService.java

@RequiredArgsConstructor
@Service
public class CustomOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {

    private final UserRepository userRepository;

    @Transactional
    @Override
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        // OAuth2UserService의 구현체를 가져오기 위해 DefaultOAuth2UserService를 사용합니다.
        OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate =
                new DefaultOAuth2UserService();

        // OAuth2UserRequest를 사용하여 OAuth2UserService를 호출하여 OAuth2User 정보를 가져옵니다.
        OAuth2User oAuth2User = delegate.loadUser(userRequest);

        // OAuth2 공급자와 사용자 이름 속성을 가져옵니다.
        String registrationId = userRequest.getClientRegistration().getRegistrationId();
        String userNameAttribute = userRequest.getClientRegistration().getProviderDetails()
                .getUserInfoEndpoint().getUserNameAttributeName();

        // OAuth2User에서 속성을 추출하고, OAuth2Attribute 객체로 변환합니다.
        OAuth2Attribute oAuth2Attribute = OAuth2Attribute.of(registrationId, userNameAttribute, oAuth2User.getAttributes());

        // OAuth2Attribute를 사용하여 사용자를 저장하거나 업데이트합니다.
        User user = saveOrUpdate(oAuth2Attribute);

        // 사용자 권한과 속성을 포함한 DefaultOAuth2User 객체를 생성하여 반환합니다.
        return new DefaultOAuth2User(
                Collections.singleton(new SimpleGrantedAuthority(
                        user.getRoleKey()))
                        , oAuth2Attribute.getAttributes()
                        , oAuth2Attribute.getNameAttributeKey());
    }

    // OAuth2Attribute를 사용하여 사용자 엔티티를 저장 또는 업데이트합니다.
    private User saveOrUpdate(OAuth2Attribute oAuth2Attribute) {
        User user = userRepository.findByEmail(oAuth2Attribute.getEmail())
                .map(entity -> entity.update(oAuth2Attribute.getName(), oAuth2Attribute.getProfileImgUrl()))
                .orElse(oAuth2Attribute.toOAuth2UserEntity());

        return userRepository.save(user);
    }
}

설명은 주석을 참고 해주시면 됩니다. 저는 제공되는 정보만 사용하기 위해 따로 OAuth2User를 만들지 않았지만 다른 정보가 필요하다면 CustomOAuth2User를 만들어서 사용하셔도 무방합니다.

여기서 볼게 saveOrUpdate 입니다. saveOrUpdate로 로그인을 할 때 사용자가 이름과, 프로필을 바꿨다면 바뀐 값으로 업데이트 되게 만들었습니다.

 

User.java

public User update(String name, String profileImgUrl) {
        this.name = name;
        this.profileImgUrl = profileImgUrl;

        return this;
    }

 

다음 메소드를 도메인에 정의하여 사용했습니다.

 

SecurityConfig.java

.and()
                .oauth2Login()
                .userInfoEndpoint().userService(customOAuth2UserService);

다음과 같이 SecurityConfig 파일에 autenticationEntryPoint 아래 부분에 넣어주시면 됩니다.

 

successHandler나 failureHandler를 설정하여 로그인 성공시 이동, 로그인 실패시 이동을 설정할 수 있는데 이 부분은 아직  저도 학습하고 있는 부분이라 기회가 된다면 코드를 올려보도록 하겠습니다

 


 

[Spring Boot] 회원 가입 & 로그인(4)

이번엔 Security 설정과 Token에 대해 설정하려고 합니다. 먼저 패스워드 암호화를 적용하기 위한 Config 파일을 만들었습니다. PasswordEncoderConfig.java @Configuration public class PasswordEncoderConfig { @Bean public Pa

classruntime.tistory.com

 

[Spring Boot] 회원 가입 & 로그인(3)

로그인을 하기 위해 JWT 토큰을 사용하기로 결정 했습니다. 그래서 그전에 JWT 토큰이 무엇인지 어떻게 사용되는 것인지 먼저 간단하게 알아보려고 합니다. JWT(JSON Web Token)란 인증에 필요한 정보

classruntime.tistory.com

 

[Spring Boot]회원 가입 & 로그인(2)

이번엔 도메인 설정 이후 컨트롤러와 서비스를 만들어 보도록 하겠습니다. 먼저 반환 형식을 맞추려고 합니다. Response { "status" : "success", "message" : "회원 가입 성공", "data" : { "userId" : 1 } } 위와 같

classruntime.tistory.com

 

[Spring Boot]회원 가입 & 로그인 (1)

기본 회원 가입 및 로그인을 구현하면서 소셜 로그인도 같이 병합해서 사용해야겠다는 생각이 들어 만들어 봤습니다. ERD는 쇼핑몰을 생각하면서 만들어 봤습니다. 먼저 기본 회원 가입 ERD 입니

classruntime.tistory.com

 

 

GitHub - Llimy1/Auth_Spring

Contribute to Llimy1/Auth_Spring development by creating an account on GitHub.

github.com

 

반응형
LIST