From 26c3e33e3f9ef123220d6acd04c468d38ab8de0a Mon Sep 17 00:00:00 2001 From: kjungw1025 Date: Mon, 21 Oct 2024 18:27:46 +0900 Subject: [PATCH] =?UTF-8?q?refactor:=20apple=20oauth=20v1=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EA=B5=AC=EC=A1=B0=20=EB=A6=AC=ED=8E=99=ED=86=A0?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../apple/service/AppleOAuth2V1Service.java | 176 +++++++----------- 1 file changed, 70 insertions(+), 106 deletions(-) diff --git a/src/main/java/com/wl2c/elswhereuserservice/domain/oauth/apple/service/AppleOAuth2V1Service.java b/src/main/java/com/wl2c/elswhereuserservice/domain/oauth/apple/service/AppleOAuth2V1Service.java index 47ce098..779ea95 100644 --- a/src/main/java/com/wl2c/elswhereuserservice/domain/oauth/apple/service/AppleOAuth2V1Service.java +++ b/src/main/java/com/wl2c/elswhereuserservice/domain/oauth/apple/service/AppleOAuth2V1Service.java @@ -1,8 +1,8 @@ package com.wl2c.elswhereuserservice.domain.oauth.apple.service; import com.fasterxml.jackson.databind.ObjectMapper; -import com.wl2c.elswhereuserservice.domain.oauth.apple.exception.FailedToReceiveAppleOAuth2TokenException; import com.wl2c.elswhereuserservice.domain.oauth.apple.jwt.AppleJwtTokenProvider; +import com.wl2c.elswhereuserservice.domain.oauth.google.exception.FailedToReceiveGoogleOAuth2TokenException; import com.wl2c.elswhereuserservice.domain.user.model.SocialType; import com.wl2c.elswhereuserservice.domain.user.model.UserStatus; import com.wl2c.elswhereuserservice.domain.user.model.entity.User; @@ -13,6 +13,7 @@ import com.wl2c.elswhereuserservice.global.auth.jwt.JwtDecoder; import com.wl2c.elswhereuserservice.global.auth.jwt.JwtProvider; import com.wl2c.elswhereuserservice.global.auth.role.UserRole; +import com.wl2c.elswhereuserservice.global.error.exception.FailedOAuthCallbackProcessingException; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -23,10 +24,10 @@ import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.client.RestTemplate; -import org.springframework.web.util.UriComponentsBuilder; -import java.util.HashMap; +import java.io.IOException; import java.util.Map; +import java.util.Objects; import java.util.Optional; @Service @@ -58,146 +59,109 @@ public class AppleOAuth2V1Service { @Value("${oauth2.apple.revoke-token-uri}") private String appleRevokeTokenUri; + private String fullName = ""; + private final UserRepository userRepository; private final JwtProvider jwtProvider; private final UserService userService; private final UserInfoService userInfoService; - private final AppleJwtTokenProvider tokenProvider; + private final AppleJwtTokenProvider appleJwtTokenProvider; private final ObjectMapper objectMapper = new ObjectMapper(); private final RestTemplate restTemplate = new RestTemplate(); - @Transactional - public ResponseEntity handleAppleOAuthCallback(Map params, HttpServletResponse response) throws Exception { - UriComponentsBuilder builder = UriComponentsBuilder.fromUriString("elswhere://"); + @Transactional(readOnly = true) + public ResponseEntity handleAppleOAuthCallback(Map params, HttpServletResponse response) throws Exception { if (params.containsKey("error")) { - String redirectUrl = builder.queryParam("error", params.get("error")).build().toUriString(); - HttpHeaders headers = new HttpHeaders(); - headers.add("Location", redirectUrl); - return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); - } - // Apple OAuth 서비스로부터 액세스 토큰을 가져옴 - Map tokenResponse = getTokens(params, response); - if (tokenResponse.containsKey("error")) { - String error = tokenResponse.get("error"); - String redirectUrl = builder.queryParam("error", error).build().toUriString(); - HttpHeaders headers = new HttpHeaders(); - headers.add("Location", redirectUrl); - return new ResponseEntity<>(headers, HttpStatus.SEE_OTHER); + return ResponseEntity.status(HttpStatus.SEE_OTHER).header("Location", "elswhere://?error=access_denied").build(); } - String accessToken = tokenResponse.get("access_token"); - String refreshToken = tokenResponse.get("refresh_token"); - // 클라이언트 앱의 콜백 URL 스킴을 사용하여 리다이렉션 - String callbackUrl = builder - .queryParam("access_token", accessToken) - .queryParam("refresh_token", refreshToken) - .build().toUriString(); + try { + HttpEntity> request = createAppleAuthTokenRequest(params); + ResponseEntity tokenResponse = restTemplate.exchange(appleTokenUri, HttpMethod.POST, request, Map.class); + + if (tokenResponse.getStatusCode().is2xxSuccessful()) { + + Map userInfo = JwtDecoder.decode((String) tokenResponse.getBody().get("id_token")); + + Optional optionalUser = userRepository.findBySocialId(userInfo.get("sub").toString()); + User user; + if (optionalUser.isEmpty()) { + user = User.builder() + .socialId(userInfo.get("sub").toString()) + .socialType(SocialType.APPLE) + .email(userInfo.get("email").toString()) + .name(fullName) + .nickname(userService.createRandomNickname()) + .userStatus(UserStatus.ACTIVE) + .userRole(UserRole.USER) + .build(); + + userRepository.save(user); + } else { + user = optionalUser.get(); + } + + // 백엔드 서버에서 서비스를 위한 자체 토큰 발급 + AuthenticationToken token = jwtProvider.issue(user); + userInfoService.cacheUserInfo(user.getId(), user); - // 리다이렉션을 위한 헤더 설정 - HttpHeaders headers = new HttpHeaders(); - headers.add("Location", callbackUrl); + String redirectUrl = "elswhere://callback?access_token=" + token.getAccessToken() + "&refresh_token=" + token.getRefreshToken(); + return ResponseEntity.status(302).header("Location", redirectUrl).build(); - // 302 리다이렉션 응답 - return new ResponseEntity<>(headers, HttpStatus.FOUND); + } else { + response.sendRedirect("elswhere://?error=invalid_token"); + throw new FailedToReceiveGoogleOAuth2TokenException(); + } + } catch (Exception e) { + try { + response.sendRedirect("elswhere://?error=invalid_token"); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + throw new FailedOAuthCallbackProcessingException(e); + } } - @Transactional - public Map getTokens(Map params, HttpServletResponse httpServletResponse) throws Exception { + private HttpEntity> createAppleAuthTokenRequest(Map params) throws Exception { // 최초 인증 시에만 이름이 옴 String authorizationCode = params.get("code").toString(); String firstName; String lastName; - String fullName = ""; - try { - // 'userJson'을 JSON으로 변환 - Map userMap = objectMapper.readValue(params.get("user").toString(), Map.class); + // 'userJson'을 JSON으로 변환 + Map userMap; + if (Objects.nonNull(params.get("user"))) { + userMap = objectMapper.readValue(params.get("user").toString(), Map.class); // 'userMap'에서 사용자의 이름과 이메일 추출 if (!userMap.isEmpty()) { - firstName = (String) ((Map) userMap.get("name")).get("firstName"); - lastName = (String) ((Map) userMap.get("name")).get("lastName"); - fullName = (lastName + " " + firstName).trim(); + Map nameMap = (Map) userMap.get("name"); + if (Objects.nonNull(nameMap)) { + firstName = (String) nameMap.get("firstName"); + lastName = (String) nameMap.get("lastName"); + fullName = (lastName + " " + firstName).trim(); + } } - - } catch (Exception e) { - e.printStackTrace(); } - // Apple OAuth 클라이언트 ID, 팀 ID, 키 ID 및 비밀 키 - String clientId = appleClientId; - String teamId = appleTeamId; - String keyId = appleKeyId; - String privateKeyPath = appleKeyPath; - String tokenUri = appleTokenUri; - String redirectUri = appleRedirectUriV1; - // JWT 생성 - String clientSecret = tokenProvider.createJwtToken(clientId, teamId, keyId, privateKeyPath); + String clientSecret = appleJwtTokenProvider.createJwtToken(appleClientId, appleTeamId, appleKeyId, appleKeyPath); HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + // Apple에 토큰 요청 MultiValueMap body = new LinkedMultiValueMap<>(); - body.add("client_id", clientId); + body.add("client_id", appleClientId); body.add("client_secret", clientSecret); body.add("code", authorizationCode); // 클라이언트에서 받은 authorization_code body.add("grant_type", "authorization_code"); - body.add("redirect_uri", redirectUri); // 콜백 URL - - HttpEntity> request = new HttpEntity<>(body, headers); + body.add("redirect_uri", appleRedirectUriV1); // 콜백 URL - Map result = null; - Map userInfo = null; - try { - ResponseEntity response = restTemplate.exchange(tokenUri, HttpMethod.POST, request, Map.class); - if (response.getStatusCode().is2xxSuccessful()) { - // Apple 토큰 서버에 POST 요청 - Map tokenResponse = response.getBody(); - - Map decodedJWT = JwtDecoder.decode(tokenResponse.get("id_token")); - userInfo = decodedJWT; - try { - Optional optionalUser = userRepository.findBySocialId(userInfo.get("sub").toString()); - User user; - if (optionalUser.isEmpty()) { - user = User.builder() - .socialId(userInfo.get("sub").toString()) - .socialType(SocialType.APPLE) - .email(userInfo.get("email").toString()) - .name(fullName) - .nickname(userService.createRandomNickname()) - .userStatus(UserStatus.ACTIVE) - .userRole(UserRole.USER) - .build(); - userRepository.save(user); - } else { - user = optionalUser.get(); - } - // 백엔드 서버에서 서비스를 위한 자체 토큰 발급 - AuthenticationToken token = jwtProvider.issue(user); - userInfoService.cacheUserInfo(user.getId(), user); - - result = new HashMap<>(); - result.put("access_token", token.getAccessToken()); - result.put("refresh_token", token.getRefreshToken()); - - return result; - - } catch (Exception e) { - httpServletResponse.sendRedirect("elswhere://?error=invalid_token"); - throw new FailedToReceiveAppleOAuth2TokenException(); - } - } else { - httpServletResponse.sendRedirect("elswhere://?error=invalid_token"); - throw new FailedToReceiveAppleOAuth2TokenException(); - } - } catch (Exception e) { - return Map.of("error", e.getMessage()); - } + return new HttpEntity<>(body, headers); } public String getAuthorizationUri() { @@ -208,4 +172,4 @@ public String getAuthorizationUri() { + "&response_mode=form_post" + "&scope=name email"; } -} +} \ No newline at end of file