Skip to content

Commit

Permalink
feat: Google OAuth 로그인/회원가입 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
SJ70 committed Jun 29, 2024
1 parent 53302d0 commit 3e373be
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 0 deletions.
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
36 changes: 36 additions & 0 deletions src/main/java/com/j9/bestmoments/auth/AuthController.java
Original file line number Diff line number Diff line change
@@ -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 "토큰 발급";
}

}
Original file line number Diff line number Diff line change
@@ -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
) {

}
Original file line number Diff line number Diff line change
@@ -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<String, String> 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<JsonNode> 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<String> entity = new HttpEntity<>(headers);
RestTemplate restTemplate = new RestTemplate();
return restTemplate.exchange(userinfoUrl, HttpMethod.GET, entity, Map.class).getBody();
}

}
Original file line number Diff line number Diff line change
@@ -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);

}
12 changes: 12 additions & 0 deletions src/main/java/com/j9/bestmoments/member/MemberRepository.java
Original file line number Diff line number Diff line change
@@ -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<Member, String> {

Optional<Member> findByOauthProviderAndOauthId(String oauthProvider, String oauthId);

}
34 changes: 34 additions & 0 deletions src/main/java/com/j9/bestmoments/member/MemberService.java
Original file line number Diff line number Diff line change
@@ -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);
}

}

0 comments on commit 3e373be

Please sign in to comment.