오늘은 전체적으로 큰 작업들은 정리가 되어가기 시작하고 살짝 붕 뜬 상태로 하루가 시작된 느낌이었다..
자잘한 조각 기능들을 구현하거나 아니면 테스트를 하면서 새롭게 발견되는 버그들을 수정하는 등 의 작업이 주된 업무였다.
그렇게 오전을 보내고 오후에도 어느정도 시간을 사용하고 난 다음, 남은 작업이 뭐가 더 있을까 하고 보던 중이었는데...
이제 게임의 메인 로직같은 경우에는 어느정도 끝난 상황이기에 내가 맡게된 작업은 구글 로그인 이었다.
보통 OAuth2 를 이용해서 구하는데 나같은 경우에는 이 의존성을 이용하지 않고 HttpEntity 와 RestTemplate 를 이용해서 직접적으로 http 요청을 구글에 보내서 가져온 인가 코드를 이용해서 구현하는 방법이다.
이는 항해측에서 카카오 로그인을 구현할 때 지급해준 코드를 그대로 이용한 방법이다.
HttpEntity
응답자체의 독립된 값이나 표현형태로, 사용자의 HttpRequest 에 대한 응답 데이터를 포함하는 클래스이다.
따라서 HttpStatus, HttpHeaders, HttpBody 를 포함한다.
즉, 여기서는 HttpHeaders 라는 객체를 생성해서 Header 의 ContentType 를 설정해서 요청을 보낸다.
Spring Framework 에서 제공하는 클래스인 HttpEntity<T> 를 상속받으며, RestTemplate(서버와 서버간 통신을 쉽게 해준다) 및 @Controller 메서드에서 이용되고 있다.
RestTemplate
Spring 에서 지원하는 객체로 간편하게 Rest 방식 API 를 호출할 수 있는 Spring 내장 클래스 이다.
Spring 3.0 부터 지원되었고, Json, xml 모두 응답을 받을 수 있다. 방식으로는 비동기 방식을 지원한다.
Controller
// 구글 로그인
@GetMapping("/google/callback")
public ResponseEntity<MsgResponseDto> googleLogin(@RequestParam String code, HttpServletResponse response) throws JsonProcessingException {
return ResponseEntity.ok(googleService.googleLogin(code, response));
}
@Service
package com.project.trysketch.service;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.project.trysketch.entity.History;
import com.project.trysketch.global.dto.MsgResponseDto;
import com.project.trysketch.global.exception.CustomException;
import com.project.trysketch.global.exception.StatusMsgCode;
import com.project.trysketch.global.jwt.JwtUtil;
import com.project.trysketch.dto.request.OAuthRequestDto;
import com.project.trysketch.entity.User;
import com.project.trysketch.repository.HistoryRepository;
import com.project.trysketch.repository.UserRepository;
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.ResponseEntity;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;
import javax.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import java.util.UUID;
@Slf4j
@Service
@RequiredArgsConstructor
public class GoogleService {
private final PasswordEncoder passwordEncoder;
private final UserRepository userRepository;
private final HistoryRepository historyRepository;
private final JwtUtil jwtUtil;
private final UserService userService;
private final HistoryService historyService;
@Value("${google.oauth2.client.id}")
private String clientId;
@Value("${google.oauth2.secret}")
private String clientSecret;
@Value("${google.oauth2.client.request.uri}")
private String requestUri;
@Value("${google.oauth2.client.grant.type}")
private String grantType;
@Value("${google.oauth2.client.redirect.uri}")
private String redirectUri;
@Value("${google.oauth2.client.token.uri}")
private String tokenUri;
public MsgResponseDto googleLogin(String code, HttpServletResponse response) throws JsonProcessingException {
String randomNickname = userService.RandomNick().getMessage();
// 1. "인가 코드"로 "액세스 토큰" 요청
String accessToken = getToken(code);
// 2. 토큰으로 구글 API 호출 : "액세스 토큰"으로 "구글 사용자 정보" 가져오기
OAuthRequestDto googleUserInfo = getGoogleUserInfo(accessToken, randomNickname);
// 3. 필요시에 회원가입
User googleUser = registerGoogleUserIfNeeded(googleUserInfo);
History history = googleUser.getHistory().updateVisits(1L);
historyRepository.save(history);
historyService.getTrophyOfVisit(googleUser);
// 4. JWT 토큰 반환
String createToken = jwtUtil.createToken(googleUser.getEmail(), googleUser.getNickname());
response.addHeader(JwtUtil.AUTHORIZATION_HEADER, createToken);
return new MsgResponseDto(StatusMsgCode.LOG_IN);
}
// "인가 코드" 로 "액세스 토큰" 요청
private String getToken(String code) throws JsonProcessingException {
// 1. HTTP Header 생성
HttpHeaders headers = new HttpHeaders();
headers.add("Content-type", "application/x-www-form-urlencoded;charset=utf-8");
// 2. HTTP Body 생성
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
body.add("grant_type", grantType);
body.add("client_id", clientId);
body.add("client_secret", clientSecret);
body.add("redirect_uri", redirectUri);
body.add("code", code);
// 3. HTTP 요청 보내기
HttpEntity<MultiValueMap<String, String>> googleTokenRequest = new HttpEntity<>(body, headers);
RestTemplate rt = new RestTemplate();
ResponseEntity<String> response = rt.exchange(
tokenUri,
HttpMethod.POST,
googleTokenRequest,
String.class
);
// 4. HTTP 응답 (JSON) -> 액세스 토큰 파싱
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
return jsonNode.get("access_token").asText();
}
// 토큰으로 구글 API 호출 : "액세스 토큰"으로 "구글 사용자 정보" 가져오기
private OAuthRequestDto getGoogleUserInfo(String accessToken, String randomNickname) throws JsonProcessingException {
// 1. HTTP Header 생성
HttpHeaders headers = new HttpHeaders();
// 2. HTTP 요청 보내기
HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(headers);
headers.add("Authorization", "Bearer " + accessToken);
// 3. HTTP 요청 보내기
RestTemplate rt = new RestTemplate();
ResponseEntity<String> response = rt.exchange(
requestUri,
HttpMethod.GET,
request,
String.class
);
// 4. HTTP 응답 (JSON) -> 액세스 토큰 파싱
String responseBody = response.getBody();
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
// 5. 필요한 값만 추출해서 리턴
Long id = jsonNode.get("exp").asLong();
String email = jsonNode.get("email").asText();
return new OAuthRequestDto(id, randomNickname, email);
}
// 필요시에 회원가입
@Transactional
public User registerGoogleUserIfNeeded(OAuthRequestDto googleUserInfo) {
// 1. DB 에 중복된 Google Id 가 있는지 확인
Long googleId = googleUserInfo.getId();
User googleUser = userRepository.findByGoogleId(googleId).orElse(null);
if (googleUser == null) {
// 2. 구글 사용자 email 동일한 email 가진 회원이 있는지 확인
String googleEmail = googleUserInfo.getEmail();
// 3. 유저정보에 동일한 이메일을 소유한 유저가 있는지 확인
User emailCheck = userRepository.findByEmail(googleEmail).orElse(null);
// 4. null 이 아닌 즉, 유저가 존재할 경우 시작
if (emailCheck != null) {
// 5. 기존의 유저 정보를 재활용(새롭게 받아온 유저에 기존 유저 정보를 덮어 씌운다)
googleUser = emailCheck;
// 6. 새롭게 받아온 ID 를 기존 계정의 ID 로 변경(기존 유저에서 ID 값만 변경)
googleUser = googleUser.googleIdUpdate(googleId);
googleUser = userRepository.save(googleUser);
} else {
// history 생성부
History newHistory = historyService.createHistory();
// 7. 신규 회원가입
String password = UUID.randomUUID().toString(); // 난수 비밀번호 생성
String encodedPassword = passwordEncoder.encode(password); // 비밀번호 디코딩
// 8. 새로운 계정 생성
googleUser = User.builder()
.password(encodedPassword)
.googleId(googleId)
.nickname(googleUserInfo.getNickname())
.email(googleUserInfo.getEmail())
.imgUrl(userService.getRandomThumbImg().getMessage())
.history(newHistory)
.build();
User newUser = userRepository.save(googleUser);
newHistory.updateUser(newUser);
}
}
return googleUser;
}
}
properties
# Google
google.oauth2.client.id=클라이언트ID
google.oauth2.secret=클라이언트 보안 비밀번호
google.oauth2.scope=https://www.googleapis.com/auth/userinfo profile https://www.googleapis.com/auth/userinfo.email
google.oauth2.client.grant.type=authorization_code
google.oauth2.client.redirect.uri=https://trys-ketch.com/login/google
google.oauth2.client.token.uri=https://oauth2.googleapis.com/token
google.oauth2.client.request.uri=https://oauth2.googleapis.com/tokeninfo
아래의 링크를 타고 들어가서 해당 글 대로 따라가서 발급받은 "클라이언트 ID" 와 "클라이언트 보안 비밀번호" 를 위의 프로퍼티에 넣어주면 된다.
Spring Boot 게시판 OAuth 2.0 구글 로그인 구현
Spring Boot에 Spring Security와 OAuth2.0을 사용해 소셜 로그인을 구현해보도록 하자. 1. 구글 OAuth 서비스 등록 필자는 이미 만들어놓은 프로젝트가 있어 예시 프로젝트를 하나 생성해보도록 하겠다. 1-1.
dev-coco.tistory.com
Google 클라우드 플랫폼
로그인 Google 클라우드 플랫폼으로 이동
accounts.google.com
https://oauth2.googleapis.com/tokeninfo
https://oauth2.googleapis.com/tokeninfo
https://oauth2.googleapis.com/tokeninfo
https://oauth2.googleapis.com/tokeninfo
https://oauth2.googleapis.com/tokeninfo
https://oauth2.googleapis.com/tokeninfo
https://oauth2.googleapis.com/tokeninfo
위의 저 주소를...
저 주소 하나 때문에 하루 종일 걸려서 겨우 해결할 수 있었다....
ㅡㅡ...
도대체 왜 구글 공식문서에서 조차 저 주소를 찾을수가 없는지 나원 참 에휴...
같이 이부분을 찾아주셨던 조원분 덕분에 겨우겨우 해결할 수 있었다...
'일기' 카테고리의 다른 글
2023-01-25 (0) | 2023.01.25 |
---|---|
2023-01-24 (0) | 2023.01.24 |
2023-01-21 (0) | 2023.01.21 |
2023-01-20 (0) | 2023.01.20 |
2023-01-19 (0) | 2023.01.19 |