From 3e373be172761f6d494822d4c46ae3adf9e493de Mon Sep 17 00:00:00 2001 From: SJ70 Date: Sat, 29 Jun 2024 19:58:55 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Google=20OAuth=20=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EC=9D=B8/=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 1 + .../j9/bestmoments/auth/AuthController.java | 36 +++++++++ .../oauth/dto/request/OAuthUserInfoDto.java | 14 ++++ .../auth/oauth/service/GoogleAuthService.java | 77 +++++++++++++++++++ .../auth/oauth/service/OAuthService.java | 9 +++ .../bestmoments/member/MemberRepository.java | 12 +++ .../j9/bestmoments/member/MemberService.java | 34 ++++++++ 7 files changed, 183 insertions(+) create mode 100644 src/main/java/com/j9/bestmoments/auth/AuthController.java create mode 100644 src/main/java/com/j9/bestmoments/auth/oauth/dto/request/OAuthUserInfoDto.java create mode 100644 src/main/java/com/j9/bestmoments/auth/oauth/service/GoogleAuthService.java create mode 100644 src/main/java/com/j9/bestmoments/auth/oauth/service/OAuthService.java create mode 100644 src/main/java/com/j9/bestmoments/member/MemberRepository.java create mode 100644 src/main/java/com/j9/bestmoments/member/MemberService.java diff --git a/build.gradle b/build.gradle index 83125f2..0eefdfa 100644 --- a/build.gradle +++ b/build.gradle @@ -27,6 +27,7 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.h2database:h2' diff --git a/src/main/java/com/j9/bestmoments/auth/AuthController.java b/src/main/java/com/j9/bestmoments/auth/AuthController.java new file mode 100644 index 0000000..f18637c --- /dev/null +++ b/src/main/java/com/j9/bestmoments/auth/AuthController.java @@ -0,0 +1,36 @@ +package com.j9.bestmoments.auth; + +import com.j9.bestmoments.auth.oauth.dto.request.OAuthUserInfoDto; +import com.j9.bestmoments.auth.oauth.service.GoogleAuthService; +import com.j9.bestmoments.auth.oauth.service.OAuthService; +import io.swagger.v3.oas.annotations.Operation; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("auth") +@RequiredArgsConstructor +@Slf4j +public class AuthController { + + private final GoogleAuthService googleAuthService; + + @GetMapping("/login/{oAuthProvider}") + @Operation(summary = "OAuth 인증코드로 로그인/회원가입", description = "oAuthProvider: google") + public String login(@PathVariable String oAuthProvider, @RequestParam String code) { + OAuthService oAuthService = switch (oAuthProvider) { + case "google" -> googleAuthService; + default -> throw new OAuth2AuthenticationException("존재하지 않는 OAuth 인증 방식입니다."); + }; + OAuthUserInfoDto oAuthUserInfo = oAuthService.getUserInfo(code); + log.info(oAuthUserInfo.toString()); + return "토큰 발급"; + } + +} diff --git a/src/main/java/com/j9/bestmoments/auth/oauth/dto/request/OAuthUserInfoDto.java b/src/main/java/com/j9/bestmoments/auth/oauth/dto/request/OAuthUserInfoDto.java new file mode 100644 index 0000000..3406105 --- /dev/null +++ b/src/main/java/com/j9/bestmoments/auth/oauth/dto/request/OAuthUserInfoDto.java @@ -0,0 +1,14 @@ +package com.j9.bestmoments.auth.oauth.dto.request; + +import lombok.Builder; + +@Builder +public record OAuthUserInfoDto( + String provider, + String id, + String name, + String email, + String profileImageUrl +) { + +} diff --git a/src/main/java/com/j9/bestmoments/auth/oauth/service/GoogleAuthService.java b/src/main/java/com/j9/bestmoments/auth/oauth/service/GoogleAuthService.java new file mode 100644 index 0000000..3fa22e2 --- /dev/null +++ b/src/main/java/com/j9/bestmoments/auth/oauth/service/GoogleAuthService.java @@ -0,0 +1,77 @@ +package com.j9.bestmoments.auth.oauth.service; + +import com.fasterxml.jackson.databind.JsonNode; +import com.j9.bestmoments.auth.oauth.dto.request.OAuthUserInfoDto; +import java.util.Collections; +import java.util.Map; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.client.RestTemplate; + +@Service +public class GoogleAuthService implements OAuthService { + + @Value("${spring.security.oauth2.client.registration.google.client-id}") + private String clientId; + + @Value("${spring.security.oauth2.client.registration.google.client-secret}") + private String clientSecret; + + @Value("${spring.security.oauth2.client.registration.google.redirect-uri}") + private String redirectUri; + + @Value("${spring.security.oauth2.client.registration.google.token-uri}") + private String tokenUri; + + @Value("${spring.security.oauth2.client.registration.google.userinfo-uri}") + private String userinfoUrl; + + @Override + public OAuthUserInfoDto getUserInfo(String code) { + String accessToken = getAccessToken(code); + Map attributes = getUserInfoAttributes(accessToken); + return OAuthUserInfoDto.builder() + .provider("google") + .id(attributes.get("id").toString()) + .name(attributes.get("name").toString()) + .email(attributes.get("email").toString()) + .profileImageUrl(attributes.get("picture").toString()) + .build(); + } + + private String getAccessToken(String code) { + MultiValueMap params = new LinkedMultiValueMap<>(); + params.put("code", Collections.singletonList(code)); + params.put("client_id", Collections.singletonList(clientId)); + params.put("client_secret", Collections.singletonList(clientSecret)); + params.put("redirect_uri", Collections.singletonList(redirectUri)); + params.put("grant_type", Collections.singletonList("authorization_code")); + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); + + HttpEntity entity = new HttpEntity(params, headers); + + RestTemplate restTemplate = new RestTemplate(); + ResponseEntity responseNode = restTemplate.exchange(tokenUri, HttpMethod.POST, entity, JsonNode.class); + JsonNode accessTokenNode = responseNode.getBody(); + + return accessTokenNode.get("access_token").asText(); + } + + private Map getUserInfoAttributes(String accessToken) { + HttpHeaders headers = new HttpHeaders(); + headers.setBearerAuth(accessToken); + HttpEntity entity = new HttpEntity<>(headers); + RestTemplate restTemplate = new RestTemplate(); + return restTemplate.exchange(userinfoUrl, HttpMethod.GET, entity, Map.class).getBody(); + } + +} diff --git a/src/main/java/com/j9/bestmoments/auth/oauth/service/OAuthService.java b/src/main/java/com/j9/bestmoments/auth/oauth/service/OAuthService.java new file mode 100644 index 0000000..f038d3b --- /dev/null +++ b/src/main/java/com/j9/bestmoments/auth/oauth/service/OAuthService.java @@ -0,0 +1,9 @@ +package com.j9.bestmoments.auth.oauth.service; + +import com.j9.bestmoments.auth.oauth.dto.request.OAuthUserInfoDto; + +public interface OAuthService { + + OAuthUserInfoDto getUserInfo(String code); + +} diff --git a/src/main/java/com/j9/bestmoments/member/MemberRepository.java b/src/main/java/com/j9/bestmoments/member/MemberRepository.java new file mode 100644 index 0000000..33f9f82 --- /dev/null +++ b/src/main/java/com/j9/bestmoments/member/MemberRepository.java @@ -0,0 +1,12 @@ +package com.j9.bestmoments.member; + +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface MemberRepository extends JpaRepository { + + Optional findByOauthProviderAndOauthId(String oauthProvider, String oauthId); + +} diff --git a/src/main/java/com/j9/bestmoments/member/MemberService.java b/src/main/java/com/j9/bestmoments/member/MemberService.java new file mode 100644 index 0000000..1d94dfa --- /dev/null +++ b/src/main/java/com/j9/bestmoments/member/MemberService.java @@ -0,0 +1,34 @@ +package com.j9.bestmoments.member; + +import com.j9.bestmoments.auth.oauth.dto.request.OAuthUserInfoDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class MemberService { + + private final MemberRepository memberRepository; + + @Transactional + public Member findOrSaveByOAuthInfo(OAuthUserInfoDto oAuth2UserInfo) { + Member member = memberRepository.findByOauthProviderAndOauthId(oAuth2UserInfo.provider(), oAuth2UserInfo.id()) + .orElse(this.create(oAuth2UserInfo)); + return memberRepository.save(member); + } + + private Member create(OAuthUserInfoDto oAuthUserInfoDto) { + Member member = Member.builder() + .oauthId(oAuthUserInfoDto.id()) + .oauthProvider(oAuthUserInfoDto.provider()) + .name(oAuthUserInfoDto.name()) + .email(oAuthUserInfoDto.email()) + .profileImageUrl(oAuthUserInfoDto.profileImageUrl()) + .role(MemberRole.USER) + .build(); + + return memberRepository.save(member); + } + +}