Skip to content

Commit

Permalink
Merge pull request #304 from ShallWeProject/develop
Browse files Browse the repository at this point in the history
feat: 애플/구글/카카오 로그인 통합 및 탈퇴 로직 구현
  • Loading branch information
sejineer authored Mar 31, 2024
2 parents cb4d5a8 + 2aafbe6 commit f3f955d
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 16 deletions.
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

0 comments on commit f3f955d

Please sign in to comment.