-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Naver 로그인 작업 완료 PR(ver.2) #10
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
e5184c5
Feat: NaverOauth 클래스 생성
wambatcodeeee 0d57692
Feat: NaverOauth 내부 로직 작성
wambatcodeeee ae01946
Feat: NaverOauth getUserInfo 메서드 작성
wambatcodeeee 26e294f
Refactor: generateRandomPostFix 클래스화
wambatcodeeee 5e3c546
Fix: 소셜 로그인 증설에 따른 createAccessTokenKakao -> createAccessTokenSocial 수정
wambatcodeeee a7fc065
Feat: Redis 패키지 내 NaverToken 엔티티 파일 생성
wambatcodeeee 353df81
Feat: Redis 패키지 내 NaverToken repository 파일 생성
wambatcodeeee 3ab1782
Feat: Redis 패키지 내 NaverToken service 파일 생성
wambatcodeeee 6140582
Feat: NaverService 작성 완료
wambatcodeeee da06f00
Fix: 불필요 import문 제거
wambatcodeeee 9732178
Feat: NaverController 작성 완료
wambatcodeeee 185e7ee
Fix: 오타 수정 및 유저정보 받을 시 인코딩 처리
wambatcodeeee 0f76b07
Feat: Naver 사용자 정보 중 이메일 추출 코드 추가
wambatcodeeee 755b78d
Fix: Naver 사용자 정보(이메일) 추가로 인한 코드 수정
wambatcodeeee a8fe284
Fix: 클래스 파일 내 오타 수정
wambatcodeeee 6d26914
Refactor: 소셜 로그인 비밀번호 인코딩
wambatcodeeee 0ea4ef1
Fix: 로컬 개발 완료로 인한 Model 메소드 제거(Thymeleaf 관련)
wambatcodeeee eb4fa8b
Fix: 클래스 파일 내 오타 수정
wambatcodeeee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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
75 changes: 75 additions & 0 deletions
75
src/main/java/com/elice/ustory/global/oauth/naver/NaverController.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,75 @@ | ||
package com.elice.ustory.global.oauth.naver; | ||
|
||
import com.elice.ustory.domain.user.dto.LoginResponse; | ||
import com.elice.ustory.domain.user.dto.LogoutResponse; | ||
import com.elice.ustory.domain.user.service.UserService; | ||
import com.elice.ustory.global.exception.dto.ErrorResponse; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.media.Content; | ||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
import io.swagger.v3.oas.annotations.tags.Tag; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
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.ui.Model; | ||
import org.springframework.web.bind.annotation.GetMapping; | ||
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; | ||
|
||
@Tag(name = "NAVER", description = "NAVER OAUTH API") | ||
@Controller | ||
@Slf4j | ||
@RequestMapping | ||
@RequiredArgsConstructor | ||
public class NaverController { | ||
private final NaverOauth naverOauth; | ||
private final NaverService naverService; | ||
private final UserService userService; | ||
|
||
@Operation(summary = "NAVER LOGIN API", description = "네이버 로그인") | ||
@ApiResponses({ | ||
@ApiResponse(responseCode = "200", description = "Ok", content = @Content(mediaType = "application/json", schema = @Schema(implementation = LoginResponse.class))), | ||
@ApiResponse(responseCode = "404", description = "Not Found", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))), | ||
@ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) | ||
}) | ||
|
||
@RequestMapping(value = "/login/oauth2/code/naver", method = {RequestMethod.GET, RequestMethod.POST}) | ||
public ResponseEntity<LoginResponse> naverLogin(@RequestParam(name = "code") String code, | ||
@RequestParam(name = "state") String state, | ||
HttpServletResponse response) { | ||
String naverAccessToken = naverOauth.getNaverToken(code, state); | ||
HashMap<String, Object> userInfo = naverOauth.getUserInfoFromNaverToken(naverAccessToken); | ||
|
||
String nickname = (String) userInfo.get("nickname"); | ||
String naverEmail = (String) userInfo.get("email"); | ||
|
||
if(!userService.checkExistByEmail(naverEmail)){ | ||
naverService.naverSignUp(nickname, naverEmail); | ||
} | ||
|
||
LoginResponse loginResponse = naverService.naverLogin(naverEmail, response, naverAccessToken); | ||
|
||
log.info("[naverLogin] 네이버 닉네임: {}", nickname); | ||
return ResponseEntity.ok().body(loginResponse); | ||
} | ||
wambatcodeeee marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
@Operation(summary = "NAVER LOGOUT API", description = "네이버 로그아웃") | ||
@ApiResponses({ | ||
@ApiResponse(responseCode = "200", description = "Ok", content = @Content(mediaType = "application/json", schema = @Schema(implementation = LogoutResponse.class))), | ||
@ApiResponse(responseCode = "401", description = "Unauthorized", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))), | ||
@ApiResponse(responseCode = "500", description = "Internal Server Error", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) | ||
}) | ||
@RequestMapping(value = "/auth/logout/naver", method = {RequestMethod.GET, RequestMethod.POST}) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오와 이렇게 하면 GET POST 동시에 되는구나아 근데 두가지 방향으로 요청이 와요? |
||
public ResponseEntity<LogoutResponse> naverLogout(HttpServletRequest request) { | ||
LogoutResponse logoutResponse = naverService.naverLogout(request); | ||
return ResponseEntity.ok().body(logoutResponse); | ||
} | ||
} |
89 changes: 89 additions & 0 deletions
89
src/main/java/com/elice/ustory/global/oauth/naver/NaverOauth.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,89 @@ | ||
package com.elice.ustory.global.oauth.naver; | ||
|
||
import com.google.gson.JsonElement; | ||
import com.google.gson.JsonObject; | ||
import com.google.gson.JsonParser; | ||
import lombok.Getter; | ||
import lombok.extern.slf4j.Slf4j; | ||
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.stereotype.Component; | ||
import org.springframework.util.LinkedMultiValueMap; | ||
import org.springframework.util.MultiValueMap; | ||
import org.springframework.web.client.RestTemplate; | ||
|
||
import java.nio.charset.StandardCharsets; | ||
import java.util.HashMap; | ||
import java.util.Objects; | ||
|
||
@Slf4j | ||
@Component | ||
@Getter | ||
public class NaverOauth { | ||
@Value("${naver.clientId}") | ||
private String naverClientId; | ||
|
||
@Value("${naver.secret}") | ||
private String naverSecretKey; | ||
|
||
@Value("${naver.loginRedirectUri}") | ||
private String naverLoginRedirectUri; | ||
|
||
@Value("${naver.logoutRedirectUri}") | ||
private String naverLogoutRedirectUri; | ||
|
||
@Value("${naver.tokenUri}") | ||
private String naverTokenUri; | ||
|
||
@Value("${naver.userInfo}") | ||
private String userInfoUri; | ||
|
||
public String getNaverToken(String code, String state){ | ||
RestTemplate restTemplate = new RestTemplate(); | ||
HttpHeaders headers = new HttpHeaders(); | ||
|
||
MultiValueMap<String, String> params = new LinkedMultiValueMap<>(); | ||
params.add("grant_type", "authorization_code"); | ||
params.add("client_id", naverClientId); | ||
params.add("client_secret", naverSecretKey); | ||
params.add("code", code); | ||
params.add("state", state); | ||
|
||
HttpEntity<MultiValueMap<String, String>> naverTokenRequest = new HttpEntity<>(params, headers); | ||
ResponseEntity<String> response = restTemplate.exchange(naverTokenUri, HttpMethod.POST, naverTokenRequest, String.class); | ||
|
||
String responseBody = response.getBody(); | ||
JsonObject asJsonObject = null; | ||
if(responseBody != null) asJsonObject = JsonParser.parseString(responseBody).getAsJsonObject(); | ||
return asJsonObject.get("access_token").getAsString(); | ||
} | ||
|
||
public HashMap<String, Object> getUserInfoFromNaverToken(String accessToken){ | ||
HashMap<String, Object> userInfo = new HashMap<>(); | ||
|
||
RestTemplate restTemplate = new RestTemplate(); | ||
HttpHeaders headers = new HttpHeaders(); | ||
headers.add("Authorization", "Bearer " + accessToken); | ||
|
||
HttpEntity<MultiValueMap<String, String>> naverUserInfoRequest = new HttpEntity<>(headers); | ||
|
||
ResponseEntity<String> response = restTemplate.exchange(userInfoUri, HttpMethod.POST, naverUserInfoRequest, String.class); | ||
log.info("response = {}", response); | ||
String responseBody = response.getBody(); | ||
if(responseBody != null){ | ||
responseBody = new String(responseBody.getBytes(StandardCharsets.ISO_8859_1), StandardCharsets.UTF_8); | ||
} | ||
JsonElement element = JsonParser.parseString(Objects.requireNonNull(responseBody)); | ||
|
||
String nickname = element.getAsJsonObject().get("response").getAsJsonObject().get("nickname").getAsString(); | ||
String email = element.getAsJsonObject().get("response").getAsJsonObject().get("email").getAsString(); | ||
|
||
userInfo.put("nickname", nickname); | ||
userInfo.put("email", email); | ||
|
||
return userInfo; | ||
} | ||
} |
102 changes: 102 additions & 0 deletions
102
src/main/java/com/elice/ustory/global/oauth/naver/NaverService.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,102 @@ | ||
package com.elice.ustory.global.oauth.naver; | ||
|
||
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.dto.LogoutResponse; | ||
import com.elice.ustory.domain.user.entity.Users; | ||
import com.elice.ustory.domain.user.repository.UserRepository; | ||
import com.elice.ustory.domain.user.service.UserService; | ||
import com.elice.ustory.global.exception.model.NotFoundException; | ||
import com.elice.ustory.global.jwt.JwtTokenProvider; | ||
import com.elice.ustory.global.jwt.JwtUtil; | ||
import com.elice.ustory.global.redis.naver.NaverTokenService; | ||
import com.elice.ustory.global.redis.refresh.RefreshTokenService; | ||
import com.elice.ustory.global.util.RandomGenerator; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
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.UUID; | ||
|
||
@Service | ||
@Slf4j | ||
@RequiredArgsConstructor | ||
public class NaverService { | ||
private final UserRepository userRepository; | ||
private final DiaryRepository diaryRepository; | ||
private final DiaryUserRepository diaryUserRepository; | ||
private final UserService userService; | ||
private final RefreshTokenService refreshTokenService; | ||
private final NaverTokenService naverTokenService; | ||
private final JwtTokenProvider jwtTokenProvider; | ||
private final JwtUtil jwtUtil; | ||
private final RandomGenerator randomGenerator; | ||
private final PasswordEncoder passwordEncoder; | ||
|
||
public void naverSignUp(String naverNickname, String naverEmail){ | ||
String randomPassword = String.valueOf(UUID.randomUUID()).substring(0,8); | ||
String encodedPassword = passwordEncoder.encode(randomPassword); | ||
String generatedNickname = naverNickname + "#" + randomGenerator.generateRandomPostfix(); | ||
|
||
Users builtUser = Users.addUserBuilder() | ||
.email(naverEmail) | ||
.loginType(Users.LoginType.NAVER) | ||
.name(naverNickname) | ||
.nickname(generatedNickname) | ||
.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 naverLogin(String naverEmail, HttpServletResponse response, String naverToken){ | ||
Users loginUser = userRepository.findByEmail(naverEmail) | ||
.orElseThrow(() -> new NotFoundException("해당 유저를 찾을 수 없습니다.")); | ||
|
||
String accessToken = jwtTokenProvider.createAccessTokenSocial(loginUser.getId(), naverToken, loginUser.getLoginType()); | ||
String refreshToken = jwtTokenProvider.createRefreshToken(); | ||
|
||
LoginResponse loginResponse = LoginResponse.builder() | ||
.accessToken(accessToken) | ||
.refreshToken(refreshToken) | ||
.build(); | ||
|
||
log.info("[getLogInResult] LogInResponse 객체에 값 주입"); | ||
response.addHeader("Authorization", accessToken); | ||
|
||
refreshTokenService.saveTokenInfo(loginUser.getId(), refreshToken, accessToken, 60 * 60 * 24 * 7); | ||
naverTokenService.saveNaverTokenInfo(loginUser.getId(), naverToken, accessToken); | ||
|
||
log.info("[logIn] 정상적으로 로그인되었습니다. id : {}, token : {}", loginUser.getId(), loginResponse.getAccessToken()); | ||
return loginResponse; | ||
} | ||
|
||
public LogoutResponse naverLogout(HttpServletRequest request) { | ||
String accessToken = jwtUtil.getTokenFromRequest(request); | ||
naverTokenService.removeNaverTokenInfo(accessToken); | ||
userService.logout(request); | ||
|
||
return LogoutResponse.builder().success(true).build(); | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
엇 아 이 로직 살아있었군요? 카카오에서 자동으로 해주는 줄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
어차피 소셜 로그인은 저희 어스토리 로그인창에 아이디 비번을 쳐서 들어갈 필요도 없을 뿐더러, 그냥 그냥 UUID인채로 냅두면 극악의 확률로 뚫릴 수 있습니다. 그래서 디코드 과정은 없이 인코딩먼 해둔겁니다(2중 보안 느낌으로)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@yungic 우리 로직에서 소셜로그인은 비밀번호를 입력하는 과정이 따로 없어서, DB에 형식적으로 랜덤 생성한 비밀번호를 넣고 있어요. 그런데 나름대로 랜덤값이라고 해도 비밀번호는 안전한 게 좋으니까 한 번 더 인코딩 거친 듯한..?
소셜로그인 시에도 회원가입 과정을 추가해서 비밀번호를 입력받는 방법도 있긴 한데 소셜로그인 유저한테 아이디 비밀번호가 따로 있는 게 더 헷갈릴 거 같고 번거로워질 거 같아서 이쪽 방법으로 왔슴당
정확한 답변은 누오가 해줄 거 같은디 초벌 한 번 해봤어유
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그러니까 인코딩만 필요한 상황이라 랜덤 변수를 생성해주는거고 (보안상) 그걸 다시 인코딩 돌린거라는거지? 맞게 이해?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
그런 셈이죠