diff --git a/build.gradle b/build.gradle index 4e7b841d..ca530af6 100644 --- a/build.gradle +++ b/build.gradle @@ -44,6 +44,9 @@ dependencies { // swagger setting implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' + // webClient setting + implementation 'org.springframework.boot:spring-boot-starter-webflux' + // s3 setting implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE' diff --git a/src/main/java/com/umc/networkingService/config/security/jwt/JwtTokenProvider.java b/src/main/java/com/umc/networkingService/config/security/jwt/JwtTokenProvider.java index 1a2f9bd4..ccb24f89 100644 --- a/src/main/java/com/umc/networkingService/config/security/jwt/JwtTokenProvider.java +++ b/src/main/java/com/umc/networkingService/config/security/jwt/JwtTokenProvider.java @@ -1,8 +1,6 @@ package com.umc.networkingService.config.security.jwt; import com.umc.networkingService.config.security.auth.PrincipalDetails; -import com.umc.networkingService.domain.member.dto.MemberResponseDto; -import io.jsonwebtoken.*; import com.umc.networkingService.config.security.auth.PrincipalDetailsService; import com.umc.networkingService.global.common.exception.ErrorCode; import com.umc.networkingService.global.common.exception.RestApiException; @@ -40,7 +38,7 @@ public class JwtTokenProvider { private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String REFRESH_HEADER = "refreshToken"; - private static final long TOKEN_VALID_TIME = 1000 * 60L * 60L; // 유효기간 1시간 + private static final long TOKEN_VALID_TIME = 1000 * 60L * 60L * 24L; // 유효기간 1일 private static final long REF_TOKEN_VALID_TIME = 1000 * 60L * 60L * 24L * 14L; // 유효기간 14일 @PostConstruct @@ -49,7 +47,7 @@ protected void init() { refreshSecretKey = Base64.getEncoder().encodeToString(refreshSecretKey.getBytes()); } - public String generateAccessToken(Claims claims, UUID memberId) { + public String generateAccessToken(Claims claims) { Date now = new Date(); Date accessTokenExpirationTime = new Date(now.getTime() + TOKEN_VALID_TIME); @@ -61,7 +59,7 @@ public String generateAccessToken(Claims claims, UUID memberId) { .compact(); } - public String generateRefreshToken(Claims claims, UUID memberId) { + public String generateRefreshToken(Claims claims) { Date now = new Date(); Date refreshTokenExpirationTime = new Date(now.getTime() + REF_TOKEN_VALID_TIME); @@ -73,15 +71,15 @@ public String generateRefreshToken(Claims claims, UUID memberId) { .compact(); } - public MemberResponseDto.TokenInfo generateToken(UUID memberId) { + public TokenInfo generateToken(UUID memberId) { Claims claims = Jwts.claims(); claims.put("memberId", memberId); - String accessToken = generateAccessToken(claims, memberId); - String refreshToken = generateRefreshToken(claims, memberId); + String accessToken = generateAccessToken(claims); + String refreshToken = generateRefreshToken(claims); - return new MemberResponseDto.TokenInfo(accessToken, refreshToken); + return new TokenInfo(accessToken, refreshToken); } public Authentication getAuthentication(String token) { diff --git a/src/main/java/com/umc/networkingService/config/security/jwt/TokenInfo.java b/src/main/java/com/umc/networkingService/config/security/jwt/TokenInfo.java new file mode 100644 index 00000000..73aa5516 --- /dev/null +++ b/src/main/java/com/umc/networkingService/config/security/jwt/TokenInfo.java @@ -0,0 +1,15 @@ +package com.umc.networkingService.config.security.jwt; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + + +@Getter +@Builder +@AllArgsConstructor +public class TokenInfo { + + private String accessToken; + private String refreshToken; +} diff --git a/src/main/java/com/umc/networkingService/domain/member/client/AppleMemberClient.java b/src/main/java/com/umc/networkingService/domain/member/client/AppleMemberClient.java new file mode 100644 index 00000000..049e4b54 --- /dev/null +++ b/src/main/java/com/umc/networkingService/domain/member/client/AppleMemberClient.java @@ -0,0 +1,30 @@ +package com.umc.networkingService.domain.member.client; + +import com.umc.networkingService.domain.member.dto.client.AppleResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +public class AppleMemberClient { + private WebClient webClient; + + public AppleMemberClient(WebClient.Builder webclientBuilder){ + this.webClient = webclientBuilder + .baseUrl("https://appleid.apple.com/auth/keys") + .build(); + } + + public String getappleClientID(final String accessToken){ + AppleResponse response = webClient.get() + .header("Authorization", "Bearer " + accessToken) + .retrieve() + .bodyToMono(AppleResponse.class) + .block(); + + //TODO 정보 받기 실패 예외 처리 + if(response == null) + return null; + + return response.getSub(); + } +} diff --git a/src/main/java/com/umc/networkingService/domain/member/client/GoogleMemberClient.java b/src/main/java/com/umc/networkingService/domain/member/client/GoogleMemberClient.java new file mode 100644 index 00000000..b200277d --- /dev/null +++ b/src/main/java/com/umc/networkingService/domain/member/client/GoogleMemberClient.java @@ -0,0 +1,31 @@ +package com.umc.networkingService.domain.member.client; + +import com.umc.networkingService.domain.member.dto.client.GoogleResponse; +import com.umc.networkingService.global.common.exception.ErrorCode; +import com.umc.networkingService.global.common.exception.RestApiException; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +public class GoogleMemberClient { + private final WebClient webClient; + + public GoogleMemberClient(WebClient.Builder webClientBuilder){ + this.webClient = webClientBuilder + .baseUrl("https://www.googleapis.com/oauth2/v3/userinfo") + .build(); + } + + public String getgoogleClientID(final String accessToken) { + GoogleResponse response = webClient.get() + .header("Authorization", "Bearer " + accessToken) + .retrieve() + .bodyToMono(GoogleResponse.class) + .block(); + + if(response == null) + throw new RestApiException(ErrorCode._INTERNAL_SERVER_ERROR); + + return response.getSub(); + } +} diff --git a/src/main/java/com/umc/networkingService/domain/member/client/KakaoMemberClient.java b/src/main/java/com/umc/networkingService/domain/member/client/KakaoMemberClient.java new file mode 100644 index 00000000..2ab8ec26 --- /dev/null +++ b/src/main/java/com/umc/networkingService/domain/member/client/KakaoMemberClient.java @@ -0,0 +1,33 @@ +package com.umc.networkingService.domain.member.client; + +import com.umc.networkingService.domain.member.dto.client.KakaoResponse; +import com.umc.networkingService.global.common.exception.ErrorCode; +import com.umc.networkingService.global.common.exception.RestApiException; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +public class KakaoMemberClient { + + private final WebClient webClient; + + + public KakaoMemberClient(WebClient.Builder webClientBuilder ) { + this.webClient = webClientBuilder + .baseUrl("https://kapi.kakao.com/v2/user/me") + .build(); + } + + public String getkakaoClientID(final String accessToken) { + KakaoResponse response = webClient.get() + .header("Authorization", "Bearer " + accessToken) + .retrieve() + .bodyToMono(KakaoResponse.class) + .block(); + + if(response == null) + throw new RestApiException(ErrorCode._INTERNAL_SERVER_ERROR); + + return response.getId(); + } +} diff --git a/src/main/java/com/umc/networkingService/domain/member/client/NaverMemberClient.java b/src/main/java/com/umc/networkingService/domain/member/client/NaverMemberClient.java new file mode 100644 index 00000000..7af2759b --- /dev/null +++ b/src/main/java/com/umc/networkingService/domain/member/client/NaverMemberClient.java @@ -0,0 +1,32 @@ +package com.umc.networkingService.domain.member.client; + +import com.umc.networkingService.domain.member.dto.client.NaverResponse; +import com.umc.networkingService.global.common.exception.ErrorCode; +import com.umc.networkingService.global.common.exception.RestApiException; +import org.springframework.stereotype.Component; +import org.springframework.web.reactive.function.client.WebClient; + +@Component +public class NaverMemberClient { + + private final WebClient webClient; + + public NaverMemberClient(WebClient.Builder webClientBuilder){ + this.webClient=webClientBuilder + .baseUrl("https://openapi.naver.com/v1/nid/me") + .build(); + } + + public String getnaverClientID(final String accessToken){ + NaverResponse response=webClient.get() + .header("Authorization","Bearer "+ accessToken) + .retrieve() + .bodyToMono(NaverResponse.class) + .block(); + + if(response == null) + throw new RestApiException(ErrorCode._INTERNAL_SERVER_ERROR); + + return response.getResponse().getId(); + } +} diff --git a/src/main/java/com/umc/networkingService/domain/member/controller/MemberController.java b/src/main/java/com/umc/networkingService/domain/member/controller/MemberController.java index ca1c4f75..5e691ec1 100644 --- a/src/main/java/com/umc/networkingService/domain/member/controller/MemberController.java +++ b/src/main/java/com/umc/networkingService/domain/member/controller/MemberController.java @@ -1,25 +1,23 @@ package com.umc.networkingService.domain.member.controller; + import com.umc.networkingService.config.security.auth.CurrentMember; -import com.umc.networkingService.config.security.jwt.JwtTokenProvider; import com.umc.networkingService.domain.member.dto.request.MemberSignUpRequest; +import com.umc.networkingService.domain.member.dto.response.MemberLoginResponse; import com.umc.networkingService.domain.member.dto.response.MemberSignUpResponse; import com.umc.networkingService.domain.member.entity.Member; import com.umc.networkingService.domain.member.entity.SocialType; -import com.umc.networkingService.domain.member.repository.MemberRepository; import com.umc.networkingService.domain.member.service.MemberService; import com.umc.networkingService.global.common.base.BaseResponse; -import com.umc.networkingService.global.common.enums.Role; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @Tag(name = "멤버 API", description = "멤버 관련 API") @RestController @@ -28,6 +26,20 @@ public class MemberController { private final MemberService memberService; + + @Operation(summary = "소셜 로그인", description = "네이버, 카카오, 구글, 애플 로그인") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "로그인 성공"), + @ApiResponse(responseCode = "COMMON500", description = "소셜 서버와의 통신 에러" , content = + @Content(schema = @Schema(implementation = BaseResponse.class))) + }) + @PostMapping("/login") + public BaseResponse socialLogin(@RequestParam(value = "accessToken") String accessToken, + @RequestParam(value = "socialType") SocialType socialType) { + return BaseResponse.onSuccess(memberService.socialLogin(accessToken, socialType)); + + } + @Operation(summary = "회원가입 API", description = "최초 멤버 정보를 등록하는 API입니다.") @ApiResponses( value = { @ApiResponse(responseCode = "COMMON200", description = "성공"), diff --git a/src/main/java/com/umc/networkingService/domain/member/dto/MemberResponseDto.java b/src/main/java/com/umc/networkingService/domain/member/dto/MemberResponseDto.java deleted file mode 100644 index f4c437e0..00000000 --- a/src/main/java/com/umc/networkingService/domain/member/dto/MemberResponseDto.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.umc.networkingService.domain.member.dto; - -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; - -public class MemberResponseDto { - - @Getter - @Builder - @AllArgsConstructor - public static class TokenInfo { - - private String accessToken; - private String refreshToken; - } -} diff --git a/src/main/java/com/umc/networkingService/domain/member/dto/client/AppleResponse.java b/src/main/java/com/umc/networkingService/domain/member/dto/client/AppleResponse.java new file mode 100644 index 00000000..23ae5416 --- /dev/null +++ b/src/main/java/com/umc/networkingService/domain/member/dto/client/AppleResponse.java @@ -0,0 +1,8 @@ +package com.umc.networkingService.domain.member.dto.client; + +import lombok.Getter; + +@Getter +public class AppleResponse { + private String sub; +} diff --git a/src/main/java/com/umc/networkingService/domain/member/dto/client/GoogleResponse.java b/src/main/java/com/umc/networkingService/domain/member/dto/client/GoogleResponse.java new file mode 100644 index 00000000..b0ccd2d8 --- /dev/null +++ b/src/main/java/com/umc/networkingService/domain/member/dto/client/GoogleResponse.java @@ -0,0 +1,8 @@ +package com.umc.networkingService.domain.member.dto.client; + +import lombok.Getter; + +@Getter +public class GoogleResponse { + private String sub; +} diff --git a/src/main/java/com/umc/networkingService/domain/member/dto/client/KakaoResponse.java b/src/main/java/com/umc/networkingService/domain/member/dto/client/KakaoResponse.java new file mode 100644 index 00000000..b1ae2e5e --- /dev/null +++ b/src/main/java/com/umc/networkingService/domain/member/dto/client/KakaoResponse.java @@ -0,0 +1,8 @@ +package com.umc.networkingService.domain.member.dto.client; + +import lombok.Getter; + +@Getter +public class KakaoResponse { + private String id; +} diff --git a/src/main/java/com/umc/networkingService/domain/member/dto/client/NaverResponse.java b/src/main/java/com/umc/networkingService/domain/member/dto/client/NaverResponse.java new file mode 100644 index 00000000..b5b5ed04 --- /dev/null +++ b/src/main/java/com/umc/networkingService/domain/member/dto/client/NaverResponse.java @@ -0,0 +1,13 @@ +package com.umc.networkingService.domain.member.dto.client; + +import lombok.Getter; + +@Getter +public class NaverResponse { + private Response response; + + @Getter + public static class Response { + private String id; + } +} diff --git a/src/main/java/com/umc/networkingService/domain/member/dto/response/MemberLoginResponse.java b/src/main/java/com/umc/networkingService/domain/member/dto/response/MemberLoginResponse.java new file mode 100644 index 00000000..2ec72b38 --- /dev/null +++ b/src/main/java/com/umc/networkingService/domain/member/dto/response/MemberLoginResponse.java @@ -0,0 +1,15 @@ +package com.umc.networkingService.domain.member.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +import java.util.UUID; + +@Getter +@Builder +public class MemberLoginResponse { + private UUID memberId; + private String accessToken; + private String refreshToken; +} diff --git a/src/main/java/com/umc/networkingService/domain/member/entity/Member.java b/src/main/java/com/umc/networkingService/domain/member/entity/Member.java index fafac682..fbbf3e47 100644 --- a/src/main/java/com/umc/networkingService/domain/member/entity/Member.java +++ b/src/main/java/com/umc/networkingService/domain/member/entity/Member.java @@ -21,8 +21,8 @@ @Getter @Entity @Builder -@AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor @SQLRestriction("deleted_at is null") @DynamicInsert public class Member extends BaseEntity { @@ -31,6 +31,7 @@ public class Member extends BaseEntity { @Column(name = "member_id") private UUID id; + @Column(nullable = false) private String clientId; @ManyToOne(fetch = FetchType.LAZY) @@ -52,6 +53,7 @@ public class Member extends BaseEntity { private String statusMessage; + @Enumerated(EnumType.STRING) @Column(nullable = false) private SocialType socialType; diff --git a/src/main/java/com/umc/networkingService/domain/member/entity/RefreshToken.java b/src/main/java/com/umc/networkingService/domain/member/entity/RefreshToken.java index ce3bdc2f..9658522b 100644 --- a/src/main/java/com/umc/networkingService/domain/member/entity/RefreshToken.java +++ b/src/main/java/com/umc/networkingService/domain/member/entity/RefreshToken.java @@ -15,7 +15,7 @@ @AllArgsConstructor @Getter @Builder -@RedisHash(value = "jwtToken", timeToLive = 60*60*24*30) // 30일 +@RedisHash(value = "jwtToken", timeToLive = 60*60*24*14) // 14일 public class RefreshToken { //redis에 저장할 객체 @Id diff --git a/src/main/java/com/umc/networkingService/domain/member/entity/SocialType.java b/src/main/java/com/umc/networkingService/domain/member/entity/SocialType.java index 5d557a72..70d68e91 100644 --- a/src/main/java/com/umc/networkingService/domain/member/entity/SocialType.java +++ b/src/main/java/com/umc/networkingService/domain/member/entity/SocialType.java @@ -8,6 +8,7 @@ public enum SocialType { KAKAO("카카오"), GOOGLE("구글"), + NAVER("네이버"), APPLE("애플"); private final String toKorean; diff --git a/src/main/java/com/umc/networkingService/domain/member/mapper/MemberMapper.java b/src/main/java/com/umc/networkingService/domain/member/mapper/MemberMapper.java index 1e12cd88..65c9008e 100644 --- a/src/main/java/com/umc/networkingService/domain/member/mapper/MemberMapper.java +++ b/src/main/java/com/umc/networkingService/domain/member/mapper/MemberMapper.java @@ -1,13 +1,31 @@ package com.umc.networkingService.domain.member.mapper; +import com.umc.networkingService.config.security.jwt.TokenInfo; +import com.umc.networkingService.domain.member.dto.response.MemberLoginResponse; import com.umc.networkingService.domain.member.entity.Member; +import com.umc.networkingService.domain.member.entity.SocialType; +import com.umc.networkingService.global.common.enums.Role; +import org.springframework.stereotype.Component;; import com.umc.networkingService.domain.member.entity.MemberPosition; import com.umc.networkingService.domain.member.entity.PositionType; -import org.springframework.stereotype.Component; @Component public class MemberMapper { + public Member toMember(final String clientId, SocialType socialType){ + return Member.builder() + .clientId(clientId) + .socialType(socialType) + .role(Role.MEMBER) + .build(); + } + public MemberLoginResponse toLoginMember(final Member member, TokenInfo tokenInfo) { + return MemberLoginResponse.builder() + .memberId(member.getId()) + .accessToken(tokenInfo.getAccessToken()) + .refreshToken(tokenInfo.getRefreshToken()) + .build(); + } public MemberPosition toMemberPosition(Member member, PositionType type, String position) { return MemberPosition.builder() .name(position) diff --git a/src/main/java/com/umc/networkingService/domain/member/repository/MemberRepository.java b/src/main/java/com/umc/networkingService/domain/member/repository/MemberRepository.java index 42b40145..a7963479 100644 --- a/src/main/java/com/umc/networkingService/domain/member/repository/MemberRepository.java +++ b/src/main/java/com/umc/networkingService/domain/member/repository/MemberRepository.java @@ -1,6 +1,7 @@ package com.umc.networkingService.domain.member.repository; import com.umc.networkingService.domain.member.entity.Member; +import com.umc.networkingService.domain.member.entity.SocialType; import java.util.Optional; import java.util.UUID; @@ -8,8 +9,9 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; -public interface MemberRepository extends JpaRepository { +public interface MemberRepository extends JpaRepository { + Optional findByClientIdAndSocialType(String clientId, SocialType socialType); @Query(value = "select m from Member m where m.id = :memberId and m.deletedAt is null") Optional findById(@Param("memberId") UUID memberId); diff --git a/src/main/java/com/umc/networkingService/domain/member/service/MemberService.java b/src/main/java/com/umc/networkingService/domain/member/service/MemberService.java index f95d7e3e..db09720d 100644 --- a/src/main/java/com/umc/networkingService/domain/member/service/MemberService.java +++ b/src/main/java/com/umc/networkingService/domain/member/service/MemberService.java @@ -1,9 +1,12 @@ package com.umc.networkingService.domain.member.service; import com.umc.networkingService.domain.member.dto.request.MemberSignUpRequest; +import com.umc.networkingService.domain.member.dto.response.MemberLoginResponse; import com.umc.networkingService.domain.member.dto.response.MemberSignUpResponse; import com.umc.networkingService.domain.member.entity.Member; +import com.umc.networkingService.domain.member.entity.SocialType; public interface MemberService { + public MemberLoginResponse socialLogin(final String accessToken, SocialType socialType); MemberSignUpResponse signUp(Member member, MemberSignUpRequest request); } diff --git a/src/main/java/com/umc/networkingService/domain/member/service/MemberServiceImpl.java b/src/main/java/com/umc/networkingService/domain/member/service/MemberServiceImpl.java index 93871ae9..398dc746 100644 --- a/src/main/java/com/umc/networkingService/domain/member/service/MemberServiceImpl.java +++ b/src/main/java/com/umc/networkingService/domain/member/service/MemberServiceImpl.java @@ -1,29 +1,136 @@ package com.umc.networkingService.domain.member.service; +import com.umc.networkingService.config.security.jwt.JwtTokenProvider; +import com.umc.networkingService.domain.member.client.AppleMemberClient; +import com.umc.networkingService.domain.member.client.KakaoMemberClient; +import com.umc.networkingService.domain.member.client.NaverMemberClient; +import com.umc.networkingService.domain.member.client.GoogleMemberClient; +import com.umc.networkingService.domain.member.dto.response.MemberLoginResponse; +import com.umc.networkingService.config.security.jwt.TokenInfo; +import com.umc.networkingService.domain.member.entity.Member; +import com.umc.networkingService.domain.member.entity.SocialType; +import com.umc.networkingService.domain.member.mapper.MemberMapper; +import com.umc.networkingService.domain.member.repository.MemberRepository; +import com.umc.networkingService.global.common.enums.Role; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; import com.umc.networkingService.domain.branch.service.BranchUniversityService; import com.umc.networkingService.domain.member.dto.request.MemberSignUpRequest; import com.umc.networkingService.domain.member.dto.response.MemberSignUpResponse; -import com.umc.networkingService.domain.member.entity.Member; import com.umc.networkingService.domain.member.entity.MemberPosition; import com.umc.networkingService.domain.member.entity.PositionType; -import com.umc.networkingService.domain.member.mapper.MemberMapper; import com.umc.networkingService.domain.member.repository.MemberPositionRepository; -import com.umc.networkingService.domain.member.repository.MemberRepository; import com.umc.networkingService.domain.university.entity.University; import com.umc.networkingService.domain.university.service.UniversityService; -import com.umc.networkingService.global.common.enums.Role; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; + @Service @RequiredArgsConstructor -public class MemberServiceImpl implements MemberService { +public class MemberServiceImpl implements MemberService{ - private final MemberRepository memberRepository; + private final KakaoMemberClient kakaoMemberClient; + private final GoogleMemberClient googleMemberClient; + private final NaverMemberClient naverMemberClient; + private final AppleMemberClient appleMemberClient; private final MemberMapper memberMapper; + private final MemberRepository memberRepository; + private final JwtTokenProvider jwtTokenProvider; + private final RefreshTokenService refreshTokenService; + + @Override + public MemberLoginResponse socialLogin(String accessToken, SocialType socialType){ + // 로그인 구분 + if(socialType.equals(SocialType.KAKAO)) + return loginByKakao(accessToken); + + if(socialType.equals(SocialType.GOOGLE)) + return loginByGoogle(accessToken); + + if(socialType.equals(SocialType.NAVER)) + return loginByNaver(accessToken); + + if(socialType.equals(SocialType.APPLE)) + return loginByApple(accessToken); + + return null; + } + + private MemberLoginResponse loginByApple(final String accessToken){ + // apple 서버와 통신해서 유저 고유값(clientId) 받기 + String clientId = appleMemberClient.getappleClientID(accessToken); + //존재 여부 파악 + Optional getMember = memberRepository.findByClientIdAndSocialType(clientId, SocialType.APPLE); + + //1. 없으면 : Member 객체 생성하고 DB 저장 + if(getMember.isEmpty()){ + return saveNewMember(clientId, SocialType.APPLE); + } + // 2. 있으면 : 새로운 토큰 반환 + return getNewToken(getMember.get()); + } + + private MemberLoginResponse loginByKakao(final String accessToken){ + // kakao 서버와 통신해서 유저 고유값(clientId) 받기 + String clientId = kakaoMemberClient.getkakaoClientID(accessToken); + // 존재 여부 파악 + Optional getMember = memberRepository.findByClientIdAndSocialType(clientId, SocialType.KAKAO); + + // 1. 없으면 : Member 객체 생성하고 DB 저장 + if(getMember.isEmpty()) { + return saveNewMember(clientId, SocialType.KAKAO); + } + // 2. 있으면 : 새로운 토큰 반환 + return getNewToken(getMember.get()); + } + + private MemberLoginResponse loginByNaver(final String accessToken){ + // naver 서버와 통신해서 유저 고유값(clientId) 받기 + String clientId = naverMemberClient.getnaverClientID(accessToken); + // 존재 여부 파악 + Optional getMember = memberRepository.findByClientIdAndSocialType(clientId,SocialType.NAVER); + + // 1. 없으면 (처음 로그인 하는 경우) + if(getMember.isEmpty()) { + return saveNewMember(clientId,SocialType.NAVER); + } + // 2. 있으면 (이미 로그인 했던 적이 있는 경우) + return getNewToken(getMember.get()); + } + + private MemberLoginResponse loginByGoogle(final String accessToken){ + // google 서버와 통신해서 유저 고유값(clientId) 받기 + String clientId = googleMemberClient.getgoogleClientID(accessToken); + // 존재 여부 파악 + Optional getMember = memberRepository.findByClientIdAndSocialType(clientId, SocialType.GOOGLE); + + // 1. 없으면 : Member 객체 생성하고 DB 저장 + if(getMember.isEmpty()){ + return saveNewMember(clientId, SocialType.GOOGLE); + } + // 2. 있으면 : 새로운 토큰 반환 + return getNewToken(getMember.get()); + } + + private MemberLoginResponse saveNewMember(String clientId, SocialType socialType) { + Member member = memberMapper.toMember(clientId, socialType); + Member newMember = memberRepository.save(member); + + return getNewToken(newMember); + } + + private MemberLoginResponse getNewToken(Member member) { + // jwt 토큰 생성 + TokenInfo tokenInfo = jwtTokenProvider.generateToken(member.getId()); + // refreshToken 디비에 저장 + refreshTokenService.saveTokenInfo(tokenInfo.getRefreshToken(), member.getId()); + + return memberMapper.toLoginMember(member, tokenInfo); + } + private final MemberPositionRepository memberPositionRepository; private final UniversityService universityService; diff --git a/src/test/java/com/umc/networkingService/domain/member/controller/MemberControllerTest.java b/src/test/java/com/umc/networkingService/domain/member/controller/MemberControllerTest.java index 29c36bb5..246047b6 100644 --- a/src/test/java/com/umc/networkingService/domain/member/controller/MemberControllerTest.java +++ b/src/test/java/com/umc/networkingService/domain/member/controller/MemberControllerTest.java @@ -2,6 +2,7 @@ import com.umc.networkingService.config.security.jwt.JwtTokenProvider; import com.umc.networkingService.domain.member.dto.request.MemberSignUpRequest; +import com.umc.networkingService.domain.member.dto.response.MemberLoginResponse; import com.umc.networkingService.domain.member.dto.response.MemberSignUpResponse; import com.umc.networkingService.domain.member.entity.Member; import com.umc.networkingService.domain.member.entity.SocialType; @@ -31,8 +32,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import static org.mockito.Mockito.*; - - @DisplayName("Member 컨트롤러의") @SpringBootTest @AutoConfigureMockMvc @@ -100,4 +99,32 @@ public void signUpTest() throws Exception { } + + @Test + @DisplayName("소셜 로그인 테스트") + public void loginTest() throws Exception { + // given + String accessToken = "ya29.a0AfB_byD6"; + + MemberLoginResponse response = MemberLoginResponse.builder() + .memberId(UUID.randomUUID()) + .accessToken("서버에서 발급받은 accessToken") + .refreshToken("서버에서 발급받은 refreshToken") + .build(); + + // when + when(memberService.socialLogin(accessToken, SocialType.KAKAO)).thenReturn(response); + + // then + this.mockMvc.perform(post("/members/login") + .contentType(MediaType.APPLICATION_JSON) + .param("accessToken", accessToken) + .param("socialType", "KAKAO")) + .andDo(print()) // 응답 출력 + .andExpect(status().isOk()) + .andExpect(jsonPath("$.code").value("COMMON200")) + .andExpect(jsonPath("$.message").value("요청에 성공하였습니다.")) + .andExpect(jsonPath("$.result.memberId").value(response.getMemberId().toString())) + .andExpect(jsonPath("$.result.accessToken").value(response.getAccessToken())); + } } \ No newline at end of file diff --git a/src/test/java/com/umc/networkingService/domain/member/service/MemberServiceIntegrationTest.java b/src/test/java/com/umc/networkingService/domain/member/service/MemberServiceIntegrationTest.java index 623ad3a9..24fb0360 100644 --- a/src/test/java/com/umc/networkingService/domain/member/service/MemberServiceIntegrationTest.java +++ b/src/test/java/com/umc/networkingService/domain/member/service/MemberServiceIntegrationTest.java @@ -1,15 +1,20 @@ package com.umc.networkingService.domain.member.service; +import com.umc.networkingService.config.security.jwt.JwtTokenProvider; import com.umc.networkingService.domain.branch.entity.Branch; import com.umc.networkingService.domain.branch.entity.BranchUniversity; import com.umc.networkingService.domain.branch.repository.BranchRepository; import com.umc.networkingService.domain.branch.repository.BranchUniversityRepository; +import com.umc.networkingService.domain.member.client.GoogleMemberClient; +import com.umc.networkingService.domain.member.client.KakaoMemberClient; +import com.umc.networkingService.domain.member.client.NaverMemberClient; import com.umc.networkingService.domain.member.dto.request.MemberSignUpRequest; +import com.umc.networkingService.domain.member.dto.response.MemberLoginResponse; import com.umc.networkingService.domain.member.entity.Member; +import com.umc.networkingService.domain.member.entity.RefreshToken; import com.umc.networkingService.domain.member.entity.SocialType; import com.umc.networkingService.domain.member.repository.MemberRepository; -import com.umc.networkingService.domain.member.service.MemberService; import com.umc.networkingService.domain.university.entity.University; import com.umc.networkingService.domain.university.repository.UniversityRepository; import com.umc.networkingService.global.common.enums.Part; @@ -20,13 +25,14 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.transaction.annotation.Transactional; - import java.util.List; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; @DisplayName("Member 서비스의 ") @SpringBootTest @@ -39,9 +45,17 @@ public class MemberServiceIntegrationTest { @Autowired private BranchRepository branchRepository; @Autowired private BranchUniversityRepository branchUniversityRepository; + @Autowired private JwtTokenProvider jwtTokenProvider; + @Autowired private RefreshTokenService refreshTokenService; + + @MockBean private KakaoMemberClient kakaoMemberClient; + @MockBean private GoogleMemberClient googleMemberClient; + @MockBean private NaverMemberClient naverMemberClient; + private Member member; private University university; private Branch branch; + private BranchUniversity branchUniversity; @BeforeEach @@ -120,4 +134,88 @@ public void signUpTest() { assertEquals(1, savedMember.getPart().size()); assertEquals(3, savedMember.getSemester().size()); } -} + + @Test + @DisplayName("카카오 로그인 테스트") + @Transactional + public void kakaoLoginTest() { + // given + String accessToken = "ya29.a0AfB_byD6"; + String clientId = "abcdefg"; + + given(kakaoMemberClient.getkakaoClientID(any())).willReturn(clientId); + + //when + MemberLoginResponse response = memberService.socialLogin(accessToken, SocialType.KAKAO); + + //then + // 멤버 저장 상태 테스트 + Optional optionalMember = memberRepository.findById(response.getMemberId()); + assertTrue(optionalMember.isPresent()); + Member savedMember = optionalMember.get(); + + assertEquals(clientId, savedMember.getClientId()); + assertEquals(Role.MEMBER, savedMember.getRole()); + assertEquals(SocialType.KAKAO, savedMember.getSocialType()); + + // 리프레시 토큰 저장 상태 테스트 + RefreshToken refreshToken = refreshTokenService.findByMemberId(savedMember.getId()); + assertEquals(response.getRefreshToken(), refreshToken.getRefreshToken()); + } + + @Test + @DisplayName("구글 로그인 테스트") + @Transactional + public void googleLoginTest() { + // given + String accessToken = "ya29.a0AfB_byD6"; + String sub = "abcdefg"; + + given(googleMemberClient.getgoogleClientID(any())).willReturn(sub); + + //when + MemberLoginResponse response = memberService.socialLogin(accessToken, SocialType.GOOGLE); + + //then + // 멤버 저장 상태 테스트 + Optional optionalMember = memberRepository.findById(response.getMemberId()); + assertTrue(optionalMember.isPresent()); + Member savedMember = optionalMember.get(); + + assertEquals(sub, savedMember.getClientId()); + assertEquals(Role.MEMBER, savedMember.getRole()); + assertEquals(SocialType.GOOGLE, savedMember.getSocialType()); + + // 리프레시 토큰 저장 상태 테스트 + RefreshToken refreshToken = refreshTokenService.findByMemberId(savedMember.getId()); + assertEquals(response.getRefreshToken(), refreshToken.getRefreshToken()); + } + + @Test + @DisplayName("네이버 로그인 테스트") + @Transactional + public void naverLoginTest() { + // given + String accessToken = "ya29.a0AfB_byD6"; + String clientId = "abcdefg"; + + given(naverMemberClient.getnaverClientID(any())).willReturn(clientId); + + //when + MemberLoginResponse response = memberService.socialLogin(accessToken, SocialType.NAVER); + + //then + // 멤버 저장 상태 테스트 + Optional optionalMember = memberRepository.findById(response.getMemberId()); + assertTrue(optionalMember.isPresent()); + Member savedMember = optionalMember.get(); + + assertEquals(clientId, savedMember.getClientId()); + assertEquals(Role.MEMBER, savedMember.getRole()); + assertEquals(SocialType.NAVER, savedMember.getSocialType()); + + // 리프레시 토큰 저장 상태 테스트 + RefreshToken refreshToken = refreshTokenService.findByMemberId(savedMember.getId()); + assertEquals(response.getRefreshToken(), refreshToken.getRefreshToken()); + } +} \ No newline at end of file