-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from TeamUStory/feat/google
Feat: OAuth 구글 소셜로그인
- Loading branch information
Showing
8 changed files
with
305 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
43 changes: 43 additions & 0 deletions
43
src/main/java/com/elice/ustory/global/oauth/google/GoogleController.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
package com.elice.ustory.global.oauth.google; | ||
|
||
import com.elice.ustory.domain.user.dto.LoginResponse; | ||
import com.elice.ustory.domain.user.service.UserService; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.stereotype.Controller; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RequestMethod; | ||
import org.springframework.web.bind.annotation.RequestParam; | ||
|
||
import java.util.HashMap; | ||
|
||
@Controller | ||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class GoogleController { | ||
private final GoogleOauth googleOauth; | ||
private final GoogleService googleService; | ||
private final UserService userService; | ||
|
||
@RequestMapping(value = "/login/oauth2/code/google", method = {RequestMethod.GET, RequestMethod.POST}) | ||
public ResponseEntity<LoginResponse> googleLogin(@RequestParam(name = "code") String code, | ||
HttpServletResponse response) { | ||
String accessToken = googleOauth.requestGoogleAccessToken(code); | ||
HashMap<String, String> accountProfile = googleOauth.requestGoogleAccountProfile(accessToken); | ||
|
||
String email = accountProfile.get("email"); | ||
String name = accountProfile.get("name"); | ||
|
||
if(!userService.checkExistByEmail(email)) { | ||
googleService.googleSignUp(accountProfile); | ||
} | ||
//TODO: 이미 구글 이메일로 기본 회원가입을 했는데, 소셜로그인을 시도할 경우? -> "이미 가입된 이메일입니다. 다른 로그인 방식을 시도해보세요." | ||
|
||
LoginResponse loginResponse = googleService.googleLogin(email, response, accessToken); | ||
|
||
log.info("[googleLogin] 구글 닉네임: {}", name); | ||
return ResponseEntity.ok().body(loginResponse); | ||
} | ||
} |
84 changes: 84 additions & 0 deletions
84
src/main/java/com/elice/ustory/global/oauth/google/GoogleOauth.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package com.elice.ustory.global.oauth.google; | ||
|
||
import com.elice.ustory.global.exception.model.NotFoundException; | ||
import com.google.gson.JsonElement; | ||
import com.google.gson.JsonObject; | ||
import com.google.gson.JsonParser; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.http.*; | ||
import org.springframework.stereotype.Component; | ||
import org.springframework.util.LinkedMultiValueMap; | ||
import org.springframework.util.MultiValueMap; | ||
import org.springframework.web.client.RestTemplate; | ||
|
||
import java.net.URLDecoder; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.HashMap; | ||
import java.util.Optional; | ||
|
||
@Component | ||
public class GoogleOauth { | ||
final String AUTHORIZATION_CODE = "authorization_code"; | ||
final String ACCESS_TOKEN = "access_token"; | ||
final String BEARER_TOKEN_PREFIX = "Bearer "; | ||
final String NOT_FOUND_ACCESS_TOKEN = "구글 액세스 토큰을 찾을 수 없습니다."; | ||
|
||
@Value("${google.clientId}") | ||
private String clientId; | ||
|
||
@Value("${google.clientSecret}") | ||
private String clientSecret; | ||
|
||
@Value("${google.redirectUri}") | ||
private String redirectUri; | ||
|
||
private String grantType = AUTHORIZATION_CODE; | ||
|
||
@Value("${google.accessTokenUri}") | ||
private String accessTokenUri; | ||
|
||
@Value("${google.accountProfileUri}") | ||
private String accountProfileUri; | ||
|
||
public String requestGoogleAccessToken(final String code) { | ||
RestTemplate restTemplate = new RestTemplate(); //TODO: 스프링 빈으로 관리 | ||
final String decodedCode = URLDecoder.decode(code, StandardCharsets.UTF_8); | ||
final HttpHeaders headers = new HttpHeaders(); | ||
headers.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE); | ||
|
||
MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); | ||
params.add("client_id", clientId); | ||
params.add("client_secret", clientSecret); | ||
params.add("code", decodedCode); | ||
params.add("grant_type", grantType); | ||
params.add("redirect_uri", redirectUri); | ||
|
||
final HttpEntity<MultiValueMap<String, String>> tokenRequestEntity = new HttpEntity<>(params, headers); | ||
final String responseBody = restTemplate.exchange(accessTokenUri, HttpMethod.POST, tokenRequestEntity, String.class).getBody(); | ||
JsonObject responseJson = JsonParser.parseString(responseBody).getAsJsonObject(); | ||
|
||
return Optional.ofNullable(responseJson.get(ACCESS_TOKEN)) | ||
.map(JsonElement::getAsString) | ||
.orElseThrow(() -> new NotFoundException(NOT_FOUND_ACCESS_TOKEN)); | ||
} | ||
|
||
public HashMap<String, String> requestGoogleAccountProfile(String accessToken) { | ||
HashMap<String, String> accountProfile = new HashMap<>(); | ||
|
||
RestTemplate restTemplate = new RestTemplate(); | ||
HttpHeaders headers = new HttpHeaders(); | ||
|
||
headers.add(HttpHeaders.AUTHORIZATION, BEARER_TOKEN_PREFIX + accessToken); | ||
final HttpEntity<MultiValueMap<String, String>> requestHeaderEntity = new HttpEntity<>(headers); | ||
final String responseBody = restTemplate.exchange(accountProfileUri, HttpMethod.GET, requestHeaderEntity, String.class).getBody(); | ||
JsonObject responseJson = JsonParser.parseString(responseBody).getAsJsonObject(); | ||
|
||
String name = responseJson.get("name").getAsString(); | ||
String email = responseJson.get("email").getAsString(); | ||
|
||
accountProfile.put("name", name); | ||
accountProfile.put("email", email); | ||
|
||
return accountProfile; | ||
} | ||
} |
105 changes: 105 additions & 0 deletions
105
src/main/java/com/elice/ustory/global/oauth/google/GoogleService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package com.elice.ustory.global.oauth.google; | ||
|
||
import com.elice.ustory.domain.diary.entity.Color; | ||
import com.elice.ustory.domain.diary.entity.Diary; | ||
import com.elice.ustory.domain.diary.entity.DiaryCategory; | ||
import com.elice.ustory.domain.diary.repository.DiaryRepository; | ||
import com.elice.ustory.domain.diaryUser.entity.DiaryUser; | ||
import com.elice.ustory.domain.diaryUser.entity.DiaryUserId; | ||
import com.elice.ustory.domain.diaryUser.repository.DiaryUserRepository; | ||
import com.elice.ustory.domain.user.dto.LoginResponse; | ||
import com.elice.ustory.domain.user.entity.Users; | ||
import com.elice.ustory.domain.user.repository.UserRepository; | ||
import com.elice.ustory.global.exception.model.NotFoundException; | ||
import com.elice.ustory.global.jwt.JwtTokenProvider; | ||
import com.elice.ustory.global.redis.google.GoogleTokenService; | ||
import com.elice.ustory.global.redis.refresh.RefreshTokenService; | ||
import com.elice.ustory.global.util.NicknameGenerator; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.security.crypto.password.PasswordEncoder; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.util.HashMap; | ||
import java.util.UUID; | ||
|
||
@Service | ||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class GoogleService { | ||
private final String EMAIL_LITERAL = "email"; | ||
private final String NAME_LITERAL = "name"; | ||
private final String AUTHORIZATION_LITERAL = "Authorization"; | ||
private final String NOT_FOUND_USER_MESSAGE = "해당 유저를 찾을 수 없습니다."; | ||
|
||
private final int REFRESH_TOKEN_TTL = 60 * 60 * 24 * 7; | ||
|
||
private final UserRepository userRepository; | ||
private final DiaryRepository diaryRepository; | ||
private final DiaryUserRepository diaryUserRepository; | ||
|
||
private final GoogleTokenService googleTokenService; | ||
private final RefreshTokenService refreshTokenService; | ||
|
||
private final PasswordEncoder passwordEncoder; | ||
private final NicknameGenerator nicknameGenerator; | ||
private final JwtTokenProvider jwtTokenProvider; | ||
|
||
public void googleSignUp(HashMap<String, String> accountProfile) { | ||
String email = accountProfile.get(EMAIL_LITERAL); | ||
String name = accountProfile.get(NAME_LITERAL); | ||
|
||
String randomPassword = String.valueOf(UUID.randomUUID()).substring(0, 8); | ||
String encodedPassword = passwordEncoder.encode(randomPassword); | ||
String formattedName = nicknameGenerator.formatNickname(name); | ||
|
||
Users builtUser = Users.addUserBuilder() | ||
.email(email) | ||
.loginType(Users.LoginType.GOOGLE) | ||
.name(formattedName) | ||
.nickname(formattedName) | ||
.password(encodedPassword) | ||
.profileImgUrl("") | ||
.profileDescription("자기소개") | ||
.build(); | ||
|
||
userRepository.save(builtUser); | ||
|
||
Diary userDiary = new Diary( | ||
String.format("%s의 다이어리", builtUser.getNickname()), | ||
"기본 DiaryImgUrl", | ||
DiaryCategory.INDIVIDUAL, | ||
String.format("%s의 개인 다이어리", builtUser.getNickname()), | ||
Color.RED | ||
); | ||
|
||
diaryRepository.save(userDiary); | ||
diaryUserRepository.save(new DiaryUser(new DiaryUserId(userDiary, builtUser))); | ||
} | ||
|
||
public LoginResponse googleLogin(String googleEmail, HttpServletResponse response, String googleToken) { | ||
|
||
Users loginUser = userRepository.findByEmail(googleEmail) | ||
.orElseThrow(() -> new NotFoundException(NOT_FOUND_USER_MESSAGE)); | ||
|
||
String accessToken = jwtTokenProvider.createAccessTokenSocial(loginUser.getId(), googleToken, loginUser.getLoginType()); | ||
String refreshToken = jwtTokenProvider.createRefreshToken(); | ||
|
||
LoginResponse loginResponse = LoginResponse.builder() | ||
.accessToken(accessToken) | ||
.refreshToken(refreshToken) | ||
.build(); | ||
|
||
log.info("[getLoginResult] LogInResponse 객체에 값 주입"); | ||
response.addHeader(AUTHORIZATION_LITERAL, accessToken); | ||
|
||
refreshTokenService.saveTokenInfo(loginUser.getId(), refreshToken, accessToken, REFRESH_TOKEN_TTL); | ||
googleTokenService.saveGoogleTokenInfo(loginUser.getId(), googleToken, accessToken); | ||
|
||
log.info("[logIn] 정상적으로 로그인되었습니다. id : {}, token : {}", loginUser.getId(), loginResponse.getAccessToken()); | ||
return loginResponse; | ||
} | ||
|
||
public void googleLogout(String accessToken) { googleTokenService.removeGoogleTokenInfo(accessToken); } | ||
} |
22 changes: 22 additions & 0 deletions
22
src/main/java/com/elice/ustory/global/redis/google/GoogleToken.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.elice.ustory.global.redis.google; | ||
|
||
import jakarta.persistence.Id; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import org.springframework.data.redis.core.RedisHash; | ||
import org.springframework.data.redis.core.index.Indexed; | ||
|
||
@RedisHash(value = "googleToken", timeToLive = 60 * 60 * 24 * 7) | ||
@AllArgsConstructor | ||
@NoArgsConstructor | ||
public class GoogleToken { | ||
@Id | ||
private String id; | ||
|
||
@Getter | ||
private String googleToken; | ||
|
||
@Indexed | ||
private String accessToken; | ||
} |
9 changes: 9 additions & 0 deletions
9
src/main/java/com/elice/ustory/global/redis/google/GoogleTokenRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.elice.ustory.global.redis.google; | ||
|
||
import org.springframework.data.repository.CrudRepository; | ||
|
||
import java.util.Optional; | ||
|
||
public interface GoogleTokenRepository extends CrudRepository<GoogleToken, String> { | ||
Optional<GoogleToken> findByAccessToken(String accessToken); | ||
} |
25 changes: 25 additions & 0 deletions
25
src/main/java/com/elice/ustory/global/redis/google/GoogleTokenService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.elice.ustory.global.redis.google; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
|
||
import java.util.Optional; | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class GoogleTokenService { | ||
private final GoogleTokenRepository googleTokenRepository; | ||
|
||
public void saveGoogleTokenInfo(Long userId, String googleToken, String accessToken) { | ||
googleTokenRepository.save(new GoogleToken(String.valueOf(userId), googleToken, accessToken)); | ||
} | ||
|
||
public void removeGoogleTokenInfo(String accessToken) { | ||
googleTokenRepository.findByAccessToken(accessToken) | ||
.ifPresent(googleTokenRepository::delete); | ||
} | ||
|
||
public Optional<GoogleToken> getByAccessToken(String accessToken) { | ||
return googleTokenRepository.findByAccessToken(accessToken); | ||
} | ||
} |