티스토리 뷰

728x90

이전 포스팅할 때 OAuth2UserService 객체를 SecurityConfig에 등록하였다. 이번에는 OAuth2UserService를 구현하고 로그인 정보를 받을 OAuth2User를 상속받는 객체를 생성해보자.

@Override
public String getName() {
    return this.getAttribute(this.userNameAttributeName).toString();
}

@Override
public Set<GrantedAuthority> getAuthorities() {
    return super.getAuthorities().stream().collect(Collectors.toSet());
}

@Override
public Map<String, Object> getAttributes() {
    return this.attributes;
}

이 세가지 함수를 오버라이드하여 구현해주면 된다. 카카오 로그인을 하면 나에게 부여되는 번호를 반환하는데 이정보를 getName()으로 받아올 수 있고 이는 attributes에 있다.

interface OAuth2AuthenticatedPrincipal

카카오 로그인을 진행했을 때 정보를 받아올 수 있도록 생성자를 추가해주어야 한다.

//카카오 로그인 용
public MemberContext(Member member, List<GrantedAuthority> authorities, Map<String, Object> attributes, String userNameAttributeName) {
    this(member, authorities);
    this.attributes = attributes;
    this.userNameAttributeName = userNameAttributeName;
}

Spring Security에서 제공하는 User를 상속받아 커스텀한 MemberContext의 전체 코드는 다음과 같다.

@Getter
public class MemberContext extends User implements OAuth2User {
    private final Long id;
    private final String profileImgUrl;

    private final String email;

    private Map<String, Object> attributes;
    private String userNameAttributeName;

    public MemberContext(Member member, List<GrantedAuthority> authorities) {
        super(member.getUsername(), member.getPassword(), authorities);
        this.id = member.getId();
        this.email = member.getEmail();
        this.profileImgUrl = member.getProfileImgUrl();
    }

    
    //카카오 로그인 용
    public MemberContext(Member member, List<GrantedAuthority> authorities, Map<String, Object> attributes, String userNameAttributeName) {
        this(member, authorities);
        this.attributes = attributes;
        this.userNameAttributeName = userNameAttributeName;
    }
    //카카오 로그인을 하면 나에게 부여되는 번호를 반환하는데 이정보는 attribute에 있다.
    @Override
    public String getName() {
        return this.getAttribute(this.userNameAttributeName).toString();
    }

    @Override
    public Set<GrantedAuthority> getAuthorities() {
        return super.getAuthorities().stream().collect(Collectors.toSet());
    }

    @Override
    public Map<String, Object> getAttributes() {
        return this.attributes;
    }
}

여기까지 OAuth2User에 대한 구현을 진행했다. 마지막으로 OAuth2UserService를 생성해주면 된다. 

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OAuth2UserService extends DefaultOAuth2UserService {
    @Autowired
    private MemberRepository memberRepository;

    @Override
    @Transactional
    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
        OAuth2User oAuth2User = super.loadUser(userRequest);

        String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
                .getUserNameAttributeName();
        Map<String, Object> attributes = oAuth2User.getAttributes();

        String oauthId = oAuth2User.getName();

        Member member = null;
        String oauthType = userRequest.getClientRegistration().getRegistrationId().toUpperCase();

        if (!"KAKAO".equals(oauthType)) {
            throw new OAuthTypeMatchNotFoundException();
        }

        if (isNew(oauthType, oauthId)) {
            switch (oauthType) {
                case "KAKAO" -> {
                    Map attributesProperties = (Map) attributes.get("properties");
                    Map attributesKakaoAcount = (Map) attributes.get("kakao_account");
                    String nickname = (String) attributesProperties.get("nickname");
                    //email을 안받아올 경우를 대비하여 구성, username도 따로 구성
                    String email = "%s@kakao.com".formatted(oauthId);
                    String username = "KAKAO_%s".formatted(oauthId);

                    if ((boolean) attributesKakaoAcount.get("has_email")) {
                        email = (String) attributesKakaoAcount.get("email");
                    }

                    member = Member.builder()
                            .email(email)
                            .username(username)
                            .password("")
                            .build();

                    memberRepository.save(member);
                }
            }
        }
        //이미 존재한다면 member에 담아서 리턴
        else {
            member = memberRepository.findByUsername("%s_%s".formatted(oauthType, oauthId))
                    .orElseThrow(MemberNotFoundException::new);
        }

        List<GrantedAuthority> authorities = new ArrayList<>();
        authorities.add(new SimpleGrantedAuthority("member"));
        return new MemberContext(member, authorities, attributes, userNameAttributeName);
    }

    //이미 존재하는지 검색 
    private boolean isNew(String oAuthType, String oAuthId) {
        return memberRepository.findByUsername("%s_%s".formatted(oAuthType, oAuthId)).isEmpty();
    }
}

필자도 한번 만들어놓고 필요에 따라 계속 가져와서 사용한다. 코드의 흐름은 isNew()를 통해 이미 DB에 존재하는 아이디인지 확인한 후 없으면 생성을 진행하는데 email은 필수정보가 아니기 때문에 그에 대비하여 email을 받아오지 못할 때 임의로 구성하였다. 

List<GrantedAuthority> authorities = new ArrayList<>();
authorities.add(new SimpleGrantedAuthority("member"));
return new MemberContext(member, authorities, attributes, userNameAttributeName);

이후 이전에 만들었던 카카오 로그인용 MemberContext를 return하면 끝이다.

 

userRequest에서 사용자 정보를 받아 활용했는데 중간과정인 엑세스 토큰을 받아 Resource Server에 사용자 정보를 요청하는 과정이 생략되어있다. 이는 Oauth2와 Spring Security에서 자체적으로 처리해주어 편리하게 사용할 수 있는 것 같다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함