Skip to content

Commit

Permalink
[feat] #17 oauth2 관련_CustomOAuth2User, OAuthAttributes, Service, user…
Browse files Browse the repository at this point in the history
…Info 생성
  • Loading branch information
dogsub committed Jan 18, 2025
1 parent 8638fde commit 7e2a890
Show file tree
Hide file tree
Showing 7 changed files with 217 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class Member extends BaseTimeEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
@Column(nullable = false, unique = true)
private String email;

@Column(nullable = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public enum ErrorCode {
EXPIRED_JWT_TOKEN(401, "EXPIRED_JWT_TOKEN", "만료된 JWT 토큰입니다."),
UNSUPPORTED_JWT_TOKEN(401, "UNSUPPORTED_JWT_TOKEN", "지원되지 않는 JWT 토큰입니다."),
ILLEGAL_JWT(400, "ILLEGAL_JWT", "JWT 토큰 핸들러 컴팩트 오류입니다."),
EMAIL_NOT_PROVIDED(400, "EMAIL_NOT_PROVIDED", "이메일이 제공되지 않았습니다."),

//작업
DELETE_FORBIDDEN(403, "DELETE_FORBIDDEN", "삭제 권한이 없습니다.");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.wedit.weditapp.global.oauth2.domain;

import com.wedit.weditapp.domain.shared.MemberRole;
import com.wedit.weditapp.domain.shared.MemberStatus;
import lombok.Getter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.core.user.DefaultOAuth2User;

import javax.management.relation.Role;
import java.util.Collection;
import java.util.Map;

@Getter
public class CustomOAuth2User extends DefaultOAuth2User {
private String email;
private MemberRole role;
private MemberStatus status;
public CustomOAuth2User(Collection<? extends GrantedAuthority> authorities,
Map<String, Object> attributes, String nameAttributeKey,
String email, MemberRole role, MemberStatus status) {
super(authorities, attributes, nameAttributeKey);
this.email = email;
this.role = role;
this.status = status;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.wedit.weditapp.global.oauth2.dto;

import com.wedit.weditapp.global.oauth2.userInfo.KakaoOAuth2UserInfo;
import com.wedit.weditapp.global.oauth2.userInfo.OAuth2UserInfo;
import lombok.Builder;
import lombok.Getter;
import org.springframework.security.core.userdetails.User;

import java.util.Map;
import java.util.UUID;

@Getter
public class OAuthAttributes {

private String nameAttributeKey; // OAuth2 로그인 진행 시 키가 되는 필드 값, PK와 같은 의미
private OAuth2UserInfo oauth2UserInfo; // 소셜 타입별 로그인 유저 정보(닉네임, 이메일, 프로필 사진 등등)

@Builder
private OAuthAttributes(String nameAttributeKey, OAuth2UserInfo oauth2UserInfo) {
this.nameAttributeKey = nameAttributeKey;
this.oauth2UserInfo = oauth2UserInfo;
}

public static OAuthAttributes ofKakao(String userNameAttributeName, Map<String, Object> attributes) {
return OAuthAttributes.builder()
.nameAttributeKey(userNameAttributeName)
.oauth2UserInfo(new KakaoOAuth2UserInfo(attributes))
.build();
}

// 이건 나중에 서비스나 엔티티 생성 로직에서 처리하는 게 더 나음
// public User toEntity(SocialType socialType, OAuth2UserInfo oauth2UserInfo) {
// return User.builder()
// .socialType(socialType)
// .socialId(oauth2UserInfo.getId())
// .email(UUID.randomUUID() + "@socialUser.com")
// .nickname(oauth2UserInfo.getNickname())
// .imageUrl(oauth2UserInfo.getImageUrl())
// .role(Role.GUEST)
// .build();
// }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package com.wedit.weditapp.global.oauth2.service;

import com.wedit.weditapp.domain.member.domain.Member;
import com.wedit.weditapp.domain.member.domain.repository.MemberRepository;
import com.wedit.weditapp.global.error.ErrorCode;
import com.wedit.weditapp.global.error.exception.CommonException;
import com.wedit.weditapp.global.oauth2.domain.CustomOAuth2User;
import com.wedit.weditapp.global.oauth2.dto.OAuthAttributes;
import com.wedit.weditapp.global.oauth2.userInfo.KakaoOAuth2UserInfo;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.Map;

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

private final MemberRepository memberRepository;

@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
log.info("CustomOAuth2UserService.loadUser() - 카카오 OAuth2 로그인 요청 진입");

// 1. DefaultOAuth2UserService를 사용해 카카오에서 사용자 정보(attributes)를 가져옴
OAuth2UserService<OAuth2UserRequest, OAuth2User> delegate = new DefaultOAuth2UserService();
OAuth2User oAuth2User = delegate.loadUser(userRequest);

// 2. registrationId, userNameAttributeName 추출 (카카오인 경우)
String registrationId = userRequest.getClientRegistration().getRegistrationId();
String userNameAttributeName = userRequest
.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getUserNameAttributeName();

// 3. 카카오 사용자 정보(attributes) 가져오기
Map<String, Object> attributes = oAuth2User.getAttributes();

// 카카오 전용 OAuthAttributes 생성 (ofKakao)
OAuthAttributes extractAttributes =
OAuthAttributes.ofKakao(userNameAttributeName, attributes);

// 4. DB에서 Member 조회 or 생성
Member member = getOrSaveMember(extractAttributes);

// 5. DefaultOAuth2User를 상속한 CustomOAuth2User 반환 + USER로 설정
return new CustomOAuth2User(
Collections.singleton(new SimpleGrantedAuthority("ROLE_" + member.getRole().name())),
attributes,
extractAttributes.getNameAttributeKey(),
member.getEmail(),
member.getRole(),
member.getStatus()
);
}

// DB에서 카카오 소셜 회원 식별자를 통해 Member 조회
// 없으면 새로 생성 후 저장
private Member getOrSaveMember(OAuthAttributes attributes) {
KakaoOAuth2UserInfo kakaoInfo = (KakaoOAuth2UserInfo) attributes.getOauth2UserInfo();

String nickname = validateOrSetDefaultNickname(kakaoInfo.getNickname());
String email = validateOrThrowEmail(kakaoInfo.getEmail());

return memberRepository.findByEmail(email).orElseGet(() -> saveNewMember(email, nickname));
}

// 닉네임 검증 메서드
private String validateOrSetDefaultNickname(String nickname) {
return (nickname == null || nickname.isEmpty()) ? "카카오사용자" : nickname;
}

// 이메일 검증 메서드
private String validateOrThrowEmail(String email) {
if (email == null || email.isEmpty()) {
throw new CommonException(ErrorCode.EMAIL_NOT_PROVIDED);
}
return email;
}

// 새 멤버 저장 메서드
private Member saveNewMember(String email, String nickname) {
return memberRepository.save(Member.createUser(email, nickname));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.wedit.weditapp.global.oauth2.userInfo;

import java.util.Map;

public class KakaoOAuth2UserInfo extends OAuth2UserInfo {

public KakaoOAuth2UserInfo(Map<String, Object> attributes) {
super(attributes);
}

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

@Override
public String getNickname() {
Map<String, Object> account = (Map<String, Object>) attributes.get("kakao_account");
if (account == null) return null;

Map<String, Object> profile = (Map<String, Object>) account.get("profile");
if (profile == null) return null;

return (String) profile.get("nickname");
}

@Override
public String getEmail() {
Map<String, Object> account = (Map<String, Object>) attributes.get("kakao_account");
if (account == null) return null;

return (String) account.get("email");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.wedit.weditapp.global.oauth2.userInfo;

import java.util.Map;

public abstract class OAuth2UserInfo {

protected Map<String, Object> attributes;

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

// 소셜 식별 값 : 카카오 - "id"
public abstract String getId();

public abstract String getNickname();

public abstract String getEmail();
}

0 comments on commit 7e2a890

Please sign in to comment.