Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 애플/구글/카카오 로그인 통합 및 탈퇴 로직 구현 #304

Merged
merged 3 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,10 @@
import com.shallwe.domain.auth.domain.OAuth2Token;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.Optional;

public interface OAuth2TokenRepository extends JpaRepository<OAuth2Token, Long> {

Optional<OAuth2Token> findByProviderId(String providerId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.shallwe.domain.auth.dto.request;

import lombok.Data;

@Data
public class AppleTokenRevokeReq {

private String client_id;
private String client_secret;
private String token;

public static AppleTokenRevokeReq of(String clientId, String clientSecret, String token) {
AppleTokenRevokeReq request = new AppleTokenRevokeReq();
request.client_id = clientId;
request.client_secret = clientSecret;
request.token = token;
return request;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.shallwe.domain.auth.exception;

public class InvalidOAuth2RefreshTokenException extends RuntimeException {

public InvalidOAuth2RefreshTokenException() {
super("유효하지 않은 oauth2 refresh token입니다.");
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
public interface UserService {

UserDetailRes getCurrentUser(UserPrincipal userPrincipal);
DeleteUserRes inactiveCurrentUser(UserPrincipal userPrincipal, PostComplainReq postComplainReq);
void inactiveCurrentUser(UserPrincipal userPrincipal, PostComplainReq postComplainReq);
void signUpCurrentUser(UserPrincipal userPrincipal, SignUpUserReq signUpUserReq);
List<SendGiftDetailRes> findSendGiftsByUser(UserPrincipal userPrincipal);
List<ReceiveGiftDetailRes> findReceiveGiftsByUser(UserPrincipal userPrincipal);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,37 @@
package com.shallwe.domain.user.application;

import com.shallwe.domain.auth.domain.OAuth2Token;
import com.shallwe.domain.auth.domain.RefreshToken;
import com.shallwe.domain.auth.domain.repository.OAuth2TokenRepository;
import com.shallwe.domain.auth.domain.repository.RefreshTokenRepository;
import com.shallwe.domain.auth.exception.InvalidOAuth2RefreshTokenException;
import com.shallwe.domain.common.Status;
import com.shallwe.domain.reservation.domain.repository.ReservationRepository;
import com.shallwe.domain.user.domain.Complain;
import com.shallwe.domain.user.domain.Provider;
import com.shallwe.domain.user.domain.User;
import com.shallwe.domain.user.domain.repository.ComplainRepository;
import com.shallwe.domain.user.domain.repository.UserRepository;

import com.shallwe.domain.user.dto.*;
import com.shallwe.domain.user.exception.InvalidUserException;
import com.shallwe.domain.user.exception.InvalidTokenException;
import com.shallwe.global.config.security.AuthConfig;
import com.shallwe.global.config.security.token.UserPrincipal;
import com.shallwe.global.utils.AppleJwtUtils;
import org.springframework.http.MediaType;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.stereotype.Service;

import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClient;

import java.util.List;
import java.util.Map;
import java.util.Optional;

@RequiredArgsConstructor
@Service
Expand All @@ -29,6 +42,9 @@ public class UserServiceImpl implements UserService {
private final RefreshTokenRepository refreshTokenRepository;
private final ReservationRepository reservationRepository;
private final ComplainRepository complainRepository;
private final OAuth2TokenRepository oAuth2TokenRepository;
private final AppleJwtUtils appleJwtUtils;
private final AuthConfig authConfig;

@Override
public UserDetailRes getCurrentUser(final UserPrincipal userPrincipal) {
Expand All @@ -37,22 +53,63 @@ public UserDetailRes getCurrentUser(final UserPrincipal userPrincipal) {

@Override
@Transactional
public DeleteUserRes inactiveCurrentUser(final UserPrincipal userPrincipal, final PostComplainReq postComplainReq) {
public void inactiveCurrentUser(final UserPrincipal userPrincipal, final PostComplainReq postComplainReq) {
User user = userRepository.findById(userPrincipal.getId())
.orElseThrow(InvalidUserException::new);

Complain complain = Complain.builder()
.content(postComplainReq.getComplain())
.build();

if(user.getProvider().equals(Provider.APPLE)) {
OAuth2Token oAuth2RefreshToken = oAuth2TokenRepository.findByProviderId(user.getProviderId())
.orElseThrow(InvalidOAuth2RefreshTokenException::new);

appleJwtUtils.revokeToken(oAuth2RefreshToken.getRefreshToken());
}

if (user.getProvider().equals(Provider.GOOGLE)) {
OAuth2Token oAuth2RefreshToken = oAuth2TokenRepository.findByProviderId(user.getProviderId())
.orElseThrow(InvalidOAuth2RefreshTokenException::new);

RestClient restClient = RestClient.builder()
.baseUrl("https://oauth2.googleapis.com")
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.build();

MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("token", oAuth2RefreshToken.getRefreshToken());

restClient.post()
.uri("/revoke")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(body)
.retrieve()
.toBodilessEntity();
}

if (user.getProvider().equals(Provider.KAKAO)) {
RestClient restClient = RestClient.builder()
.baseUrl("https://kapi.kakao.com")
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.build();

Map<String, String> body = Map.of("target_id_type", "user_id", "target_id", user.getProviderId());

restClient.post()
.uri("/v1/user/unlink")
.header("Authorization", "KakaoAK " + authConfig.getAuth().getKakaoAdminKey())
.body(body)
.retrieve()
.toBodilessEntity();
}

RefreshToken refreshToken = refreshTokenRepository.findByProviderId(user.getEmail())
.orElseThrow(InvalidTokenException::new);

user.updateStatus(Status.DELETE);
refreshTokenRepository.delete(refreshToken);
complainRepository.save(complain);

return DeleteUserRes.toDto();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,12 @@ public ResponseCustom<UserDetailRes> getCurrentUser(
@ApiResponse(responseCode = "400", description = "유저 탈퇴 실패", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}),
})
@PostMapping("/inactive")
public ResponseCustom<DeleteUserRes> inactiveCurrentUser(
public ResponseEntity<Void> inactiveCurrentUser(
@Parameter(description = "AccessToken 을 입력해주세요.", required = true) @CurrentUser UserPrincipal userPrincipal,
@Parameter(description = "PostComplainReq를 확인 해 주세요.", required = true) @RequestBody PostComplainReq postComplainReq
) {
return ResponseCustom.OK(userServiceImpl.inactiveCurrentUser(userPrincipal, postComplainReq));
userServiceImpl.inactiveCurrentUser(userPrincipal, postComplainReq);
return ResponseEntity.noContent().build();
}

@Operation(summary = "유저 세부정보 입력", description = "이름, 마켓팅 정보 동의와 나이, 성별 정보를 입력받습니다.")
Expand Down
22 changes: 12 additions & 10 deletions src/main/java/com/shallwe/global/config/WebConfig.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,27 @@
package com.shallwe.global.config;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@RequiredArgsConstructor
@Configuration
public class WebConfig implements WebMvcConfigurer {

private final long MAX_AGE_SECS = 3600;

@Value("${app.cors.allowed-origins}")
private String[] allowedOrigins;

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") // url 패턴
.allowedOriginPatterns("*")
// todo: server url 생성 시 변경 필요
.allowedOrigins("https://api.shallwes.com/")
.allowedMethods(HttpMethod.GET.name(), HttpMethod.POST.name(), HttpMethod.PATCH.name(), HttpMethod.DELETE.name(), HttpMethod.OPTIONS.name(),
HttpMethod.HEAD.name(), HttpMethod.TRACE.name(), HttpMethod.OPTIONS.name()) // 허용 method
.allowedHeaders("Authorization", "Content-Type")// 허용 header
.allowCredentials(true);


.allowedOrigins(allowedOrigins)
.allowedMethods("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS")
.allowedHeaders("*")// 허용 header
.allowCredentials(true)
.maxAge(MAX_AGE_SECS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public static class Auth {
private String tokenSecret;
private long accessTokenExpirationMsec;
private long refreshTokenExpirationMsec;
private String kakaoAdminKey;
}

@Data
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/com/shallwe/global/utils/AppleJwtUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.shallwe.domain.auth.dto.request.AppleTokenReq;
import com.shallwe.domain.auth.dto.request.AppleTokenRevokeReq;
import com.shallwe.global.config.security.AuthConfig;
import com.shallwe.domain.auth.dto.response.ApplePublicKeyRes;
import io.jsonwebtoken.*;
Expand Down Expand Up @@ -108,6 +109,31 @@ public String getAppleToken(String code) {
return response.getRefresh_token();
}

public void revokeToken(String refreshToken) {
RestClient restClient = RestClient.builder()
.baseUrl("https://appleid.apple.com/auth")
.requestFactory(new HttpComponentsClientHttpRequestFactory())
.build();

AppleTokenRevokeReq appleTokenRevokeReq = AppleTokenRevokeReq.of(
authConfig.getAppleAuth().getClientId(),
makeClientSecret(),
refreshToken
);

MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
map.add("client_id", appleTokenRevokeReq.getClient_id());
map.add("client_secret", appleTokenRevokeReq.getClient_secret());
map.add("token", appleTokenRevokeReq.getToken());

restClient.post()
.uri("/revoke")
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.body(map)
.retrieve()
.toBodilessEntity();
}

public String makeClientSecret() {
AppleAuth appleAuth = authConfig.getAppleAuth();
Date expirationDate = Date.from(LocalDateTime.now().plusDays(30).atZone(ZoneId.systemDefault()).toInstant());
Expand Down
Loading