diff --git a/application/src/main/java/org/depromeet/spot/application/common/config/SecurityConfig.java b/application/src/main/java/org/depromeet/spot/application/common/config/SecurityConfig.java index bed6077c..3b6994a2 100644 --- a/application/src/main/java/org/depromeet/spot/application/common/config/SecurityConfig.java +++ b/application/src/main/java/org/depromeet/spot/application/common/config/SecurityConfig.java @@ -33,6 +33,7 @@ public class SecurityConfig { "/api/v1/members/**", "/actuator/**", "/login/oauth2/code/google/**", + "/google/**" }; @Bean diff --git a/application/src/main/java/org/depromeet/spot/application/common/jwt/JwtAuthenticationFilter.java b/application/src/main/java/org/depromeet/spot/application/common/jwt/JwtAuthenticationFilter.java index fa75d8cc..8f8e45c2 100644 --- a/application/src/main/java/org/depromeet/spot/application/common/jwt/JwtAuthenticationFilter.java +++ b/application/src/main/java/org/depromeet/spot/application/common/jwt/JwtAuthenticationFilter.java @@ -41,6 +41,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { "/api/v1/levels/info", "/kakao", "/api/v1/jwts", + "/google/callback" }; private static final Map> AUTH_METHOD_WHITELIST = @@ -54,6 +55,10 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter { "/api/v1/members/delete", Set.of("DELETE"), "/api/v1/baseball-teams", + Set.of("GET"), + "/api/v2/GOOGLE", + Set.of("GET"), + "/api/v2/KAKAO", Set.of("GET")); @Override diff --git a/application/src/main/java/org/depromeet/spot/application/member/controller/OauthController.java b/application/src/main/java/org/depromeet/spot/application/member/controller/OauthController.java index c4fca739..e238d349 100644 --- a/application/src/main/java/org/depromeet/spot/application/member/controller/OauthController.java +++ b/application/src/main/java/org/depromeet/spot/application/member/controller/OauthController.java @@ -44,29 +44,29 @@ public JwtTokenResponse create(@RequestBody @Valid RegisterV2Req request) { return new JwtTokenResponse(jwtTokenUtil.getJWTToken(memberResult)); } - @GetMapping("/api/v2/members/{snsProvider}/{token}") + @GetMapping("/api/v2/members/{snsProvider}") @ResponseStatus(HttpStatus.OK) @Operation(summary = "Member 로그인 API") public JwtTokenResponse login( @PathVariable("snsProvider") @Parameter(name = "snsProvider", description = "KAKAO/GOOGLE", required = true) SnsProvider snsProvider, - @PathVariable("token") + @RequestParam @Parameter( - name = "token", - description = "sns 카카오는 accessToken, 구글은 authToken", + name = "accessToken", + description = "sns 카카오는 accessToken", required = true) - String token) { + String accessToken) { - Member member = oauthUsecase.login(snsProvider, token); + Member member = oauthUsecase.login(snsProvider, accessToken); return new JwtTokenResponse(jwtTokenUtil.getJWTToken(member)); } // TODO : /api/v2/members를 RequestMapping으로 빼면 구글 로그인에서 4xx Exception 발생 @GetMapping("/api/v2/members/authorization/{snsProvider}") @ResponseStatus(HttpStatus.OK) - @Operation(summary = "(백엔드용)accessToken을 받아오기 위한 API") - public String getAccessToken2( + @Operation(summary = "accessToken을 받아오기 위한 API") + public String getAccessToken( @PathVariable("snsProvider") SnsProvider snsProvider, @RequestParam String code) { String token = oauthUsecase.getOauthAccessToken(snsProvider, code); log.info("snsProvider : {}", snsProvider); diff --git a/common/src/main/java/org/depromeet/spot/common/exception/member/MemberErrorCode.java b/common/src/main/java/org/depromeet/spot/common/exception/member/MemberErrorCode.java index cc815f43..47d8e2d4 100644 --- a/common/src/main/java/org/depromeet/spot/common/exception/member/MemberErrorCode.java +++ b/common/src/main/java/org/depromeet/spot/common/exception/member/MemberErrorCode.java @@ -11,6 +11,7 @@ public enum MemberErrorCode implements ErrorCode { MEMBER_NICKNAME_CONFLICT(HttpStatus.CONFLICT, "M002", "닉네임이 중복됩니다."), INVALID_LEVEL(HttpStatus.INTERNAL_SERVER_ERROR, "M003", "잘못된 레벨입니다."), INACTIVE_MEMBER(HttpStatus.GONE, "M004", "탈퇴한 유저입니다."), + MEMBER_CONFLICT(HttpStatus.BAD_REQUEST, "M005", "이미 가입된 유저입니다."), ; private final HttpStatus status; diff --git a/common/src/main/java/org/depromeet/spot/common/exception/member/MemberException.java b/common/src/main/java/org/depromeet/spot/common/exception/member/MemberException.java index 43f3eee6..eb3c2550 100644 --- a/common/src/main/java/org/depromeet/spot/common/exception/member/MemberException.java +++ b/common/src/main/java/org/depromeet/spot/common/exception/member/MemberException.java @@ -36,4 +36,11 @@ public InactiveMemberException() { super(MemberErrorCode.INACTIVE_MEMBER); } } + + public static class MemberConflictException extends MemberException { + + public MemberConflictException() { + super(MemberErrorCode.MEMBER_CONFLICT); + } + } } diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/aws/property/ObjectStorageProperties.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/aws/property/ObjectStorageProperties.java index 19dd0d5b..a8bb970a 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/aws/property/ObjectStorageProperties.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/aws/property/ObjectStorageProperties.java @@ -3,4 +3,5 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "aws.s3") -public record ObjectStorageProperties(String accessKey, String secretKey, String bucketName) {} +public record ObjectStorageProperties( + String accessKey, String secretKey, String bucketName, String basicProfileImageUrl) {} diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/entity/MemberEntity.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/entity/MemberEntity.java index 0849e47f..c97e31a1 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/entity/MemberEntity.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/entity/MemberEntity.java @@ -25,7 +25,7 @@ public class MemberEntity extends BaseEntity { // TODO : email 받아온 후 nullable = false로 바꿔야함. - @Column(name = "email", unique = true, length = 50) + @Column(name = "email", length = 50) private String email; // TODO : 이름 받아온 후 nullable = false로 바꿔야함. @@ -36,7 +36,7 @@ public class MemberEntity extends BaseEntity { private String nickname; // TODO : phone_number 받아온 후 nullable = false로 바꿔야함. - @Column(name = "phone_number", unique = true, length = 13) + @Column(name = "phone_number", length = 13) private String phoneNumber; @ManyToOne(fetch = FetchType.LAZY) @@ -52,7 +52,7 @@ public class MemberEntity extends BaseEntity { @Column(name = "sns_provider", nullable = false, length = 20) private String snsProvider; - @Column(name = "id_token", nullable = false, unique = true, length = 255) + @Column(name = "id_token", nullable = false, length = 255) private String idToken; @Column(name = "team_id", length = 10) diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberJpaRepository.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberJpaRepository.java index ecc0310f..f644722e 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberJpaRepository.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberJpaRepository.java @@ -10,7 +10,8 @@ import org.springframework.data.repository.query.Param; public interface MemberJpaRepository extends JpaRepository { - Optional findByIdToken(String idToken); + @Query("select m from MemberEntity m where m.idToken = :idToken and m.deletedAt is null") + Optional findByIdToken(@Param("idToken") String idToken); @Query("select m from MemberEntity m " + "join fetch m.level l " + "where m.id = :id") Optional findByIdWithLevel(@Param("id") Long id); diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberRepositoryImpl.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberRepositoryImpl.java index c2165f65..16035eb7 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberRepositoryImpl.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/member/repository/MemberRepositoryImpl.java @@ -1,6 +1,7 @@ package org.depromeet.spot.infrastructure.jpa.member.repository; import java.time.LocalDateTime; +import java.util.Optional; import org.depromeet.spot.common.exception.member.MemberException.MemberNotFoundException; import org.depromeet.spot.domain.member.Level; @@ -37,11 +38,8 @@ public Member updateLevel(Member member) { } @Override - public Member findByIdToken(String idToken) { - return memberJpaRepository - .findByIdToken(idToken) - .map(MemberEntity::toDomain) - .orElseThrow(MemberNotFoundException::new); + public Optional findByIdToken(String idToken) { + return memberJpaRepository.findByIdToken(idToken).map(MemberEntity::toDomain); } @Override diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/OauthRepositoryImpl.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/OauthRepositoryImpl.java index 628ef0de..a4d1bba7 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/OauthRepositoryImpl.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/OauthRepositoryImpl.java @@ -4,6 +4,7 @@ import org.depromeet.spot.common.exception.oauth.OauthException.InvalidAcessTokenException; import org.depromeet.spot.domain.member.Member; import org.depromeet.spot.domain.member.enums.SnsProvider; +import org.depromeet.spot.infrastructure.aws.property.ObjectStorageProperties; import org.depromeet.spot.infrastructure.jpa.oauth.config.OauthProperties; import org.depromeet.spot.infrastructure.jpa.oauth.entity.GoogleTokenEntity; import org.depromeet.spot.infrastructure.jpa.oauth.entity.GoogleUserInfoEntity; @@ -28,6 +29,8 @@ public class OauthRepositoryImpl implements OauthRepository { private final String BEARER = "Bearer"; private final OauthProperties properties; + private final ObjectStorageProperties objectStorageProperties; + private final String AUTHORIZATION_CODE = "authorization_code"; @Override @@ -136,18 +139,21 @@ public String getOauthAccessToken(SnsProvider snsProvider, String authorizationC @Override public Member getKakaoRegisterUserInfo(String accessToken, Member member) { KakaoUserInfoEntity userInfo = getKakaoUserInfo(accessToken); + log.info("basicProfileImage : {}", objectStorageProperties.basicProfileImageUrl()); // 회원가입 시 받은 정보를 바탕으로 member로 변환해서 리턴. - return userInfo.toKakaoDomain(member); + return userInfo.toKakaoDomain(member, objectStorageProperties.basicProfileImageUrl()); } @Override public Member getOauthRegisterUserInfo(String accessToken, Member member) { switch (member.getSnsProvider()) { case KAKAO: - return getKakaoUserInfo(accessToken).toKakaoDomain(member); + return getKakaoUserInfo(accessToken) + .toKakaoDomain(member, objectStorageProperties.basicProfileImageUrl()); default: - return getGoogleUserInfo(accessToken).toGoogleDomain(member); + return getGoogleUserInfo(accessToken) + .toGoogleDomain(member, objectStorageProperties.basicProfileImageUrl()); } } diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/entity/GoogleUserInfoEntity.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/entity/GoogleUserInfoEntity.java index f81d7b87..dc0454db 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/entity/GoogleUserInfoEntity.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/entity/GoogleUserInfoEntity.java @@ -34,11 +34,11 @@ public class GoogleUserInfoEntity extends BaseEntity { @JsonProperty("picture") public String profileImageUrl; - public Member toGoogleDomain(Member member) { + public Member toGoogleDomain(Member member, String basicProfileImageUrl) { return Member.builder() .email(email) .nickname(member.getNickname()) - .profileImage(profileImageUrl) + .profileImage(basicProfileImageUrl) .snsProvider(SnsProvider.GOOGLE) .idToken(idToken) .role(MemberRole.ROLE_USER) diff --git a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/entity/KakaoUserInfoEntity.java b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/entity/KakaoUserInfoEntity.java index c7ffb4b5..9cb76298 100644 --- a/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/entity/KakaoUserInfoEntity.java +++ b/infrastructure/src/main/java/org/depromeet/spot/infrastructure/jpa/oauth/entity/KakaoUserInfoEntity.java @@ -15,7 +15,9 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +@Slf4j @Getter @NoArgsConstructor // 역직렬화를 위한 기본 생성자 @JsonIgnoreProperties(ignoreUnknown = true) @@ -94,13 +96,13 @@ public class Profile { } } - public Member toKakaoDomain(Member member) { + public Member toKakaoDomain(Member member, String basicProfileImageUrl) { return Member.builder() .email(kakaoAccount.email) .name(kakaoAccount.name) .nickname(member.getNickname()) .phoneNumber(kakaoAccount.phoneNumber) - .profileImage(kakaoAccount.profile.profileImageUrl) + .profileImage(basicProfileImageUrl) .snsProvider(SnsProvider.KAKAO) .idToken(getId().toString()) .role(MemberRole.ROLE_USER) diff --git a/infrastructure/src/test/java/org/depromeet/spot/infrastructure/aws/PresignedUrlGeneratorTest.java b/infrastructure/src/test/java/org/depromeet/spot/infrastructure/aws/PresignedUrlGeneratorTest.java index 54d78461..df095fb3 100644 --- a/infrastructure/src/test/java/org/depromeet/spot/infrastructure/aws/PresignedUrlGeneratorTest.java +++ b/infrastructure/src/test/java/org/depromeet/spot/infrastructure/aws/PresignedUrlGeneratorTest.java @@ -20,7 +20,8 @@ class PresignedUrlGeneratorTest { @BeforeEach void init() { ObjectStorageProperties objectStorageProperties = - new ObjectStorageProperties("accessKey", "secretKey", "bucketName"); + new ObjectStorageProperties( + "accessKey", "secretKey", "bucketName", "basicProfileImageUrl"); FakeAmazonS3Config amazonS3 = new FakeAmazonS3Config(objectStorageProperties); FakeTimeUsecase fakeTimeUsecase = new FakeTimeUsecase("2024-07-09 21:00:00"); diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/member/MemberRepository.java b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/member/MemberRepository.java index b4ff5962..deb1044d 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/port/out/member/MemberRepository.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/port/out/member/MemberRepository.java @@ -1,6 +1,7 @@ package org.depromeet.spot.usecase.port.out.member; import java.time.LocalDateTime; +import java.util.Optional; import org.depromeet.spot.domain.member.Level; import org.depromeet.spot.domain.member.Member; @@ -13,7 +14,7 @@ public interface MemberRepository { Member updateLevel(Member member); - Member findByIdToken(String idToken); + Optional findByIdToken(String idToken); boolean existsByNickname(String nickname); diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/member/MemberService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/member/MemberService.java index e2c5ec3c..92804f1a 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/member/MemberService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/member/MemberService.java @@ -1,9 +1,12 @@ package org.depromeet.spot.usecase.service.member; import java.time.LocalDateTime; +import java.util.Optional; import org.depromeet.spot.common.exception.member.MemberException.InactiveMemberException; +import org.depromeet.spot.common.exception.member.MemberException.MemberConflictException; import org.depromeet.spot.common.exception.member.MemberException.MemberNicknameConflictException; +import org.depromeet.spot.common.exception.member.MemberException.MemberNotFoundException; import org.depromeet.spot.domain.member.Level; import org.depromeet.spot.domain.member.Member; import org.depromeet.spot.domain.team.BaseballTeam; @@ -39,14 +42,23 @@ public Member create(String accessToken, Member member) { } Member memberResult = oauthRepository.getKakaoRegisterUserInfo(accessToken, member); Level initialLevel = readLevelUsecase.findInitialLevel(); - // 이미 있는 유저를 검증할 필요 없음 -> 최초 시도가 로그인먼저 들어오기 때문. + + // 이미 가입된 유저 Exception + Optional existedMember = memberRepository.findByIdToken(memberResult.getIdToken()); + if (existedMember.isPresent()) { + throw new MemberConflictException(); + } + return memberRepository.save(memberResult, initialLevel); } @Override public Member login(String accessToken) { Member memberResult = oauthRepository.getLoginUserInfo(accessToken); - Member existedMember = memberRepository.findByIdToken(memberResult.getIdToken()); + Member existedMember = + memberRepository + .findByIdToken(memberResult.getIdToken()) + .orElseThrow(MemberNotFoundException::new); // 회원 탈퇴 유저일 경우 재가입 if (existedMember.getDeletedAt() != null) { @@ -71,7 +83,9 @@ public String getAccessToken(String idCode) { @Override public boolean deleteMember(String accessToken) { Member memberResult = oauthRepository.getLoginUserInfo(accessToken); - memberRepository.findByIdToken(memberResult.getIdToken()); + memberRepository + .findByIdToken(memberResult.getIdToken()) + .orElseThrow(MemberNotFoundException::new); memberRepository.deleteByIdToken(memberResult.getIdToken()); return true; diff --git a/usecase/src/main/java/org/depromeet/spot/usecase/service/oauth/OauthService.java b/usecase/src/main/java/org/depromeet/spot/usecase/service/oauth/OauthService.java index 3a18126e..28dc5a93 100644 --- a/usecase/src/main/java/org/depromeet/spot/usecase/service/oauth/OauthService.java +++ b/usecase/src/main/java/org/depromeet/spot/usecase/service/oauth/OauthService.java @@ -1,7 +1,11 @@ package org.depromeet.spot.usecase.service.oauth; +import java.util.Optional; + import org.depromeet.spot.common.exception.member.MemberException.InactiveMemberException; +import org.depromeet.spot.common.exception.member.MemberException.MemberConflictException; import org.depromeet.spot.common.exception.member.MemberException.MemberNicknameConflictException; +import org.depromeet.spot.common.exception.member.MemberException.MemberNotFoundException; import org.depromeet.spot.domain.member.Level; import org.depromeet.spot.domain.member.Member; import org.depromeet.spot.domain.member.enums.SnsProvider; @@ -31,22 +35,23 @@ public Member create(String accessToken, Member member) { Member memberResult = oauthRepository.getOauthRegisterUserInfo(accessToken, member); Level initialLevel = readLevelUsecase.findInitialLevel(); - return memberRepository.save(memberResult, initialLevel); + // 이미 가입된 유저일 경우 Exception + Optional existedMember = memberRepository.findByIdToken(memberResult.getIdToken()); + if (existedMember.isPresent()) { + throw new MemberConflictException(); + } + + return existedMember.orElseGet(() -> memberRepository.save(memberResult, initialLevel)); } @Override - public Member login(SnsProvider snsProvider, String token) { - String accessToken; - switch (snsProvider) { - case KAKAO: - accessToken = token; - break; - default: - accessToken = oauthRepository.getOauthAccessToken(snsProvider, token); - break; - } + public Member login(SnsProvider snsProvider, String accessToken) { + Member memberResult = oauthRepository.getOauthLoginUserInfo(snsProvider, accessToken); - Member existedMember = memberRepository.findByIdToken(memberResult.getIdToken()); + Member existedMember = + memberRepository + .findByIdToken(memberResult.getIdToken()) + .orElseThrow(MemberNotFoundException::new); // 회원 탈퇴 유저일 경우 재가입 if (existedMember.getDeletedAt() != null) {