-
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
[feat] oauth2 로그인 기능 추가 (구글,네이버,카카오) #37
Changes from all commits
dbb8435
f2ab12a
c36ac1a
5b0ac0f
32807f8
561d6ab
5090451
1c03778
470100d
8a0f386
b04d8d1
68bac69
63fb62c
d5ef2ac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,7 @@ | ||
package com.haejwo.tripcometrue.domain.member.controller; | ||
|
||
import com.haejwo.tripcometrue.domain.member.dto.request.SignUpRequestDto; | ||
import com.haejwo.tripcometrue.domain.member.dto.response.LoginResponseDto; | ||
import com.haejwo.tripcometrue.domain.member.dto.response.SignUpResponseDto; | ||
import com.haejwo.tripcometrue.domain.member.dto.response.TestUserResponseDto; | ||
import com.haejwo.tripcometrue.domain.member.entity.Member; | ||
|
@@ -28,8 +29,8 @@ public class MemberController { | |
@PostMapping("/signup") | ||
public ResponseEntity<ResponseDTO<SignUpResponseDto>> signup( | ||
@Valid @RequestBody SignUpRequestDto signUpRequestDto) { | ||
SignUpResponseDto signupResponseDto = memberService.signup(signUpRequestDto); | ||
ResponseDTO<SignUpResponseDto> response = ResponseDTO.okWithData(signupResponseDto); | ||
SignUpResponseDto signUpResponseDto = memberService.signup(signUpRequestDto); | ||
ResponseDTO<SignUpResponseDto> response = ResponseDTO.okWithData(signUpResponseDto); | ||
return ResponseEntity | ||
.status(response.getCode()) | ||
.body(response); | ||
|
@@ -57,4 +58,15 @@ public ResponseEntity<ResponseDTO<Void>> checkDuplicateEmail( | |
.status(response.getCode()) | ||
.body(response); | ||
} | ||
|
||
@GetMapping("/oauth2/info") | ||
public ResponseEntity<ResponseDTO<LoginResponseDto>> oauth2Test( | ||
@RequestParam String token, @RequestParam String email, @RequestParam String name) { | ||
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. 가독성을 위해 한 칸 내린 매개변수들은 한 번의 탭을 추가하는 건 어떨까요? 바로 밑 코드들과 같은 라인이라 구분이 되면 좋을듯 합니다.
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. ctrl alt l로 google code java convetion에 맞춰 적용할 경우 자동으로 다시 수정되는 부분입니다. |
||
LoginResponseDto loginResponseDto = new LoginResponseDto(email, name, token); | ||
|
||
ResponseDTO<LoginResponseDto> response = ResponseDTO.okWithData(loginResponseDto); | ||
return ResponseEntity | ||
.status(response.getCode()) | ||
.body(response); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package com.haejwo.tripcometrue.global.springsecurity; | ||
|
||
import java.util.Map; | ||
|
||
/** | ||
* @author liyusang1 | ||
* @implNote OAuth2 구글 로그인 후 받아온 값에서 사용자 정보를 저장하기 위한 클래스 | ||
*/ | ||
|
||
public class GoogleUserInfo implements OAuth2UserInfo { | ||
|
||
private Map<String, Object> attributes; | ||
|
||
public GoogleUserInfo(Map<String, Object> attributes) { | ||
this.attributes = attributes; | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return (String) attributes.get("name"); | ||
} | ||
|
||
@Override | ||
public String getPhoneNumber() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public String getProfileImage() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public String getEmail() { | ||
return (String) attributes.get("email") + "GoogleOAuth2"; | ||
} | ||
|
||
@Override | ||
public String getProvider() { | ||
return "google"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.haejwo.tripcometrue.global.springsecurity; | ||
|
||
import java.util.Map; | ||
|
||
/** | ||
* @author liyusang1 | ||
* @implNote OAuth2 카카오 로그인 후 받아온 값에서 사용자 정보를 저장하기 위한 클래스 | ||
*/ | ||
|
||
public class KakaoUserInfo implements OAuth2UserInfo { | ||
|
||
private Map<String, Object> attributes; | ||
|
||
public KakaoUserInfo(Map<String, Object> attributes) { | ||
this.attributes = attributes; | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
// Kakao의 닉네임은 properties 안에 있습니다. | ||
Map<String, Object> properties = (Map<String, Object>) attributes.get("properties"); | ||
return (String) properties.get("nickname"); | ||
} | ||
|
||
@Override | ||
public String getPhoneNumber() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public String getEmail() { | ||
// Kakao의 이메일은 kakao_account 안에 있습니다. | ||
Map<String, Object> kakaoAccount = (Map<String, Object>) attributes.get("kakao_account"); | ||
return (String) kakaoAccount.get("email") + "KaKaoOAuth2"; | ||
} | ||
|
||
@Override | ||
public String getProvider() { | ||
return "kakao"; | ||
} | ||
|
||
@Override | ||
public String getProfileImage() { | ||
// Kakao의 프로필 이미지는 properties 안에 있습니다. | ||
Map<String, Object> properties = (Map<String, Object>) attributes.get("properties"); | ||
return (String) properties.get("profile_image"); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
package com.haejwo.tripcometrue.global.springsecurity; | ||
|
||
import java.util.Map; | ||
|
||
/** | ||
* @author liyusang1 | ||
* @implNote OAuth2 네이버 로그인 후 받아온 값에서 사용자 정보를 저장하기 위한 클래스 | ||
*/ | ||
public class NaverUserInfo implements OAuth2UserInfo { | ||
|
||
private Map<String, Object> attributes; | ||
|
||
public NaverUserInfo(Map<String, Object> attributes) { | ||
this.attributes = attributes; | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return (String) attributes.get("name"); | ||
} | ||
|
||
@Override | ||
public String getPhoneNumber() { | ||
return (String) attributes.get("mobile"); | ||
} | ||
|
||
@Override | ||
public String getProfileImage() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public String getEmail() { | ||
return (String) attributes.get("email") + "NaverOAuth2"; | ||
} | ||
|
||
@Override | ||
public String getProvider() { | ||
return "naver"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package com.haejwo.tripcometrue.global.springsecurity; | ||
|
||
import com.haejwo.tripcometrue.global.jwt.JwtProvider; | ||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import java.io.IOException; | ||
import java.net.URLEncoder; | ||
import java.nio.charset.StandardCharsets; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; | ||
import org.springframework.stereotype.Component; | ||
|
||
/** | ||
* @author liyusang1 | ||
* @implNote 해당 클래스는 SimpleUrlAuthenticationSuccessHandler를 상속받은 OAuth 로그인 성공 후 로직을 처리 하는 클래스 | ||
* 로그인 성공 후 리디렉트 하게 설정 했습니다. | ||
* 프론트 배포사이트 -> http://localhost:5173 | ||
* 스프링 코드 내로 리디렉트 설정 하고 싶은 경우 | ||
* String redirectUrl = "/user/oauth-success?token="+token; | ||
*/ | ||
@Component | ||
@RequiredArgsConstructor | ||
public class OAuth2LoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { | ||
|
||
private final JwtProvider jwtProvider; | ||
|
||
@Override | ||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, | ||
Authentication authentication) throws IOException, ServletException { | ||
|
||
PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); | ||
String token = jwtProvider.createToken(principalDetails.getMember()); | ||
String email = principalDetails.getEmail(); | ||
String name = principalDetails.getUsername(); | ||
|
||
//한국어 인코딩 설정 | ||
String encodedName = URLEncoder.encode(name, StandardCharsets.UTF_8.toString()); | ||
|
||
String redirectUrl = "http://localhost:8080/v1/member/oauth2/info?token=" + token | ||
+ "&email=" + email + "&name=" + encodedName; | ||
getRedirectStrategy().sendRedirect(request, response, redirectUrl); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.haejwo.tripcometrue.global.springsecurity; | ||
|
||
/** | ||
* @author liyusang1 | ||
* @implNote OAuth2.0 제공자들 마다 응답 해주는 속성 세부 값이 달라서 생성한 공통 interface | ||
*/ | ||
public interface OAuth2UserInfo { | ||
String getProvider(); | ||
String getEmail(); | ||
String getName(); | ||
String getPhoneNumber(); | ||
String getProfileImage(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
package com.haejwo.tripcometrue.global.springsecurity; | ||
|
||
import com.haejwo.tripcometrue.domain.member.entity.Member; | ||
import com.haejwo.tripcometrue.domain.member.repository.MemberRepository; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; | ||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; | ||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; | ||
import org.springframework.security.oauth2.core.user.OAuth2User; | ||
import org.springframework.stereotype.Service; | ||
|
||
/** | ||
* @author liyusang1 | ||
* @implNote OAuth2 client라이브러리에서 redirect된 경로의 로그인 성공 후 후처리를 하는 클래스, 로그인 성공 시 accesstoken과 사용자 정보를 | ||
* 같이 지급받게 되며, 발급받은 accesstoken 및 사용자 정보를 아래와 같이 코드로 확인할 수 있다. | ||
* System.out.println("getClientRegistration : " + userRequest.getClientRegistration ()); | ||
* System.out.println("getAccessToken: " + userRequest.getAccessToken()); | ||
* System.out.println("getAttributes: " + super.loadUser(userRequest).getAttributes()) | ||
*/ | ||
|
||
@Service | ||
@RequiredArgsConstructor | ||
public class PrincipalOauth2UserService extends DefaultOAuth2UserService { | ||
|
||
private final MemberRepository memberRepository; | ||
|
||
@Override | ||
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException { | ||
|
||
OAuth2User oauth2User = super.loadUser(userRequest); | ||
OAuth2UserInfo oauth2Userinfo = null; | ||
String provider = userRequest.getClientRegistration() | ||
.getRegistrationId(); //google kakao facebook... | ||
|
||
if (provider.equals("google")) { | ||
oauth2Userinfo = new GoogleUserInfo(oauth2User.getAttributes()); | ||
} else if (provider.equals("naver")) { | ||
oauth2Userinfo = new NaverUserInfo((Map) oauth2User.getAttributes().get("response")); | ||
} else if (provider.equals("kakao")) { | ||
oauth2Userinfo = new KakaoUserInfo(oauth2User.getAttributes()); | ||
} | ||
|
||
Optional<Member> user = memberRepository.findByMemberBaseEmailAndProvider( | ||
oauth2Userinfo.getEmail(), oauth2Userinfo.getProvider()); | ||
|
||
//이미 소셜로그인을 한적이 있는지 없는지 | ||
if (user.isEmpty()) { | ||
Member newUser = Member.builder() | ||
.email(oauth2Userinfo.getEmail()) | ||
.nickname(oauth2Userinfo.getName()) | ||
.password("OAuth2") //Oauth2로 로그인을 해서 패스워드는 의미없음. | ||
.authority("ROLE_USER") | ||
.provider(provider) | ||
.build(); | ||
if (provider.equals("kakao")) { | ||
newUser.updateProfileImage(oauth2Userinfo.getProfileImage()); | ||
} | ||
|
||
memberRepository.save(newUser); | ||
return new PrincipalDetails(newUser, oauth2User.getAttributes()); | ||
} else { | ||
return new PrincipalDetails(user.get(), oauth2User.getAttributes()); | ||
} | ||
} | ||
} |
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.
예전 커밋이지만 지금 발견해서 말씀드립니다.
32번째 줄을 보면 메소드명은 signup인데, Dto명에선 signUp으로 되어 있어요.
일반적으로 사용하는 signUp으로 통일하면 어떨까요?
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.
넵 이부분 반영하겠습니다 실수한거같네요