-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
…ogin [feat] : 소셜 로그인 기능을 구현한다
- Loading branch information
Showing
27 changed files
with
990 additions
and
2 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package side.onetime.auth.constant; | ||
|
||
public enum Provider { | ||
GOOGLE_PROVIDER("google"), | ||
KAKAO_PROVIDER("kakao"), | ||
NAVER_PROVIDER("naver"); | ||
|
||
private final String provider; | ||
|
||
Provider(String provider) { | ||
this.provider = provider; | ||
} | ||
|
||
public String getProvider() { | ||
return provider; | ||
} | ||
} |
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,27 @@ | ||
package side.onetime.auth.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import side.onetime.auth.constant.Provider; | ||
|
||
import java.util.Map; | ||
|
||
@AllArgsConstructor | ||
public class GoogleUserInfo implements OAuth2UserInfo { | ||
|
||
private Map<String, Object> attributes; | ||
|
||
@Override | ||
public String getProviderId() { | ||
return (String) attributes.get("sub"); | ||
} | ||
|
||
@Override | ||
public String getProvider() { | ||
return Provider.GOOGLE_PROVIDER.getProvider(); | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return (String) attributes.get("name"); | ||
} | ||
} |
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,29 @@ | ||
package side.onetime.auth.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import side.onetime.auth.constant.Provider; | ||
|
||
import java.util.Map; | ||
|
||
@AllArgsConstructor | ||
public class KakaoUserInfo implements OAuth2UserInfo { | ||
|
||
private Map<String, Object> attributes; | ||
|
||
@Override | ||
public String getProviderId() { | ||
// Long 타입이기 때문에 toString으로 변환 | ||
return attributes.get("id").toString(); | ||
} | ||
|
||
@Override | ||
public String getProvider() { | ||
return Provider.KAKAO_PROVIDER.getProvider(); | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
// kakao_account라는 Map에서 추출 | ||
return (String) ((Map) attributes.get("properties")).get("nickname"); | ||
} | ||
} |
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,27 @@ | ||
package side.onetime.auth.dto; | ||
|
||
import lombok.AllArgsConstructor; | ||
import side.onetime.auth.constant.Provider; | ||
|
||
import java.util.Map; | ||
|
||
@AllArgsConstructor | ||
public class NaverUserInfo implements OAuth2UserInfo { | ||
|
||
private Map<String, Object> attributes; | ||
|
||
@Override | ||
public String getProviderId() { | ||
return (String) attributes.get("id"); | ||
} | ||
|
||
@Override | ||
public String getProvider() { | ||
return Provider.NAVER_PROVIDER.getProvider(); | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return (String) attributes.get("name"); | ||
} | ||
} |
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,7 @@ | ||
package side.onetime.auth.dto; | ||
|
||
public interface OAuth2UserInfo { | ||
String getProviderId(); | ||
String getProvider(); | ||
String getName(); | ||
} |
26 changes: 26 additions & 0 deletions
26
src/main/java/side/onetime/auth/handler/OAuthLoginFailureHandler.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,26 @@ | ||
package side.onetime.auth.handler; | ||
|
||
import jakarta.servlet.ServletException; | ||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.security.core.AuthenticationException; | ||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; | ||
import org.springframework.stereotype.Component; | ||
|
||
import java.io.IOException; | ||
|
||
@Slf4j | ||
@Component | ||
@RequiredArgsConstructor | ||
public class OAuthLoginFailureHandler extends SimpleUrlAuthenticationFailureHandler { | ||
|
||
@Override | ||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { | ||
log.error("LOGIN FAILED : {}", exception.getMessage()); | ||
super.onAuthenticationFailure(request, response, exception); | ||
|
||
// 추후 구현 | ||
} | ||
} |
120 changes: 120 additions & 0 deletions
120
src/main/java/side/onetime/auth/handler/OAuthLoginSuccessHandler.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,120 @@ | ||
package side.onetime.auth.handler; | ||
|
||
import jakarta.servlet.http.HttpServletRequest; | ||
import jakarta.servlet.http.HttpServletResponse; | ||
import lombok.RequiredArgsConstructor; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.security.core.Authentication; | ||
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; | ||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; | ||
import org.springframework.stereotype.Component; | ||
import side.onetime.auth.dto.GoogleUserInfo; | ||
import side.onetime.auth.dto.KakaoUserInfo; | ||
import side.onetime.auth.dto.NaverUserInfo; | ||
import side.onetime.auth.dto.OAuth2UserInfo; | ||
import side.onetime.domain.RefreshToken; | ||
import side.onetime.domain.User; | ||
import side.onetime.repository.RefreshTokenRepository; | ||
import side.onetime.repository.UserRepository; | ||
import side.onetime.util.JwtUtil; | ||
|
||
import java.io.IOException; | ||
import java.util.Map; | ||
|
||
@Slf4j | ||
@Component | ||
@RequiredArgsConstructor | ||
public class OAuthLoginSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { | ||
|
||
@Value("${jwt.redirect.access}") | ||
private String ACCESS_TOKEN_REDIRECT_URI; | ||
|
||
@Value("${jwt.redirect.register}") | ||
private String REGISTER_TOKEN_REDIRECT_URI; | ||
|
||
@Value("${jwt.access-token.expiration-time}") | ||
private long ACCESS_TOKEN_EXPIRATION_TIME; | ||
|
||
@Value("${jwt.refresh-token.expiration-time}") | ||
private long REFRESH_TOKEN_EXPIRATION_TIME; | ||
|
||
private final JwtUtil jwtUtil; | ||
private final UserRepository userRepository; | ||
private final RefreshTokenRepository refreshTokenRepository; | ||
|
||
@Override | ||
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { | ||
OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; | ||
String provider = token.getAuthorizedClientRegistrationId(); // provider 추출 | ||
|
||
OAuth2UserInfo oAuth2UserInfo = extractOAuth2UserInfo(token, provider); | ||
handleAuthentication(request, response, oAuth2UserInfo, provider); | ||
} | ||
|
||
// OAuth2UserInfo 추출 | ||
private OAuth2UserInfo extractOAuth2UserInfo(OAuth2AuthenticationToken token, String provider) { | ||
switch (provider) { | ||
case "google": | ||
log.info("구글 로그인 요청"); | ||
return new GoogleUserInfo(token.getPrincipal().getAttributes()); | ||
case "kakao": | ||
log.info("카카오 로그인 요청"); | ||
return new KakaoUserInfo(token.getPrincipal().getAttributes()); | ||
case "naver": | ||
log.info("네이버 로그인 요청"); | ||
return new NaverUserInfo((Map<String, Object>) token.getPrincipal().getAttributes().get("response")); | ||
default: | ||
throw new IllegalArgumentException("지원하지 않는 OAuth2 제공자입니다."); | ||
} | ||
} | ||
|
||
// 인증 처리 | ||
private void handleAuthentication(HttpServletRequest request, HttpServletResponse response, OAuth2UserInfo oAuth2UserInfo, String provider) throws IOException { | ||
String providerId = oAuth2UserInfo.getProviderId(); | ||
String name = oAuth2UserInfo.getName(); | ||
|
||
User existUser = userRepository.findByProviderId(providerId); | ||
|
||
if (existUser == null) { | ||
// 신규 유저 처리 | ||
handleNewUser(request, response, provider, providerId, name); | ||
} else { | ||
// 기존 유저 처리 | ||
handleExistingUser(request, response, existUser); | ||
} | ||
|
||
log.info("유저 이름 : {}", name); | ||
log.info("PROVIDER : {}", provider); | ||
log.info("PROVIDER_ID : {}", providerId); | ||
} | ||
|
||
// 신규 유저 처리 | ||
private void handleNewUser(HttpServletRequest request, HttpServletResponse response, String provider, String providerId, String name) throws IOException { | ||
log.info("신규 유저입니다."); | ||
String registerToken = jwtUtil.generateRegisterToken(provider, providerId, name, ACCESS_TOKEN_EXPIRATION_TIME); | ||
String redirectUri = String.format(REGISTER_TOKEN_REDIRECT_URI, registerToken); | ||
getRedirectStrategy().sendRedirect(request, response, redirectUri); | ||
} | ||
|
||
// 기존 유저 처리 | ||
private void handleExistingUser(HttpServletRequest request, HttpServletResponse response, User user) throws IOException { | ||
log.info("기존 유저입니다."); | ||
Long userId = user.getId(); | ||
|
||
// 액세스 & 리프레쉬 토큰 발급 및 저장 | ||
String accessToken = jwtUtil.generateAccessToken(userId, ACCESS_TOKEN_EXPIRATION_TIME); | ||
String refreshToken = jwtUtil.generateRefreshToken(userId, REFRESH_TOKEN_EXPIRATION_TIME); | ||
saveRefreshToken(userId, refreshToken); | ||
|
||
// 리다이렉트 처리 | ||
String redirectUri = String.format(ACCESS_TOKEN_REDIRECT_URI, accessToken, refreshToken); | ||
getRedirectStrategy().sendRedirect(request, response, redirectUri); | ||
} | ||
|
||
// Refresh Token 저장 | ||
private void saveRefreshToken(Long userId, String refreshToken) { | ||
RefreshToken newRefreshToken = new RefreshToken(userId, refreshToken); | ||
refreshTokenRepository.save(newRefreshToken); | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
src/main/java/side/onetime/controller/TokenController.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,28 @@ | ||
package side.onetime.controller; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import side.onetime.dto.TokenDto; | ||
import side.onetime.global.common.ApiResponse; | ||
import side.onetime.global.common.constant.SuccessStatus; | ||
import side.onetime.service.TokenService; | ||
|
||
@RestController | ||
@RequestMapping("/api/v1/tokens") | ||
@RequiredArgsConstructor | ||
public class TokenController { | ||
private final TokenService tokenService; | ||
|
||
// 액세스 토큰 재발행 API | ||
@PostMapping("/action-reissue") | ||
public ResponseEntity<ApiResponse<TokenDto.ReissueTokenResponse>> reissueToken( | ||
@RequestBody TokenDto.ReissueTokenRequest reissueAccessTokenRequest) { | ||
|
||
TokenDto.ReissueTokenResponse reissueTokenResponse = tokenService.reissueToken(reissueAccessTokenRequest); | ||
return ApiResponse.onSuccess(SuccessStatus._REISSUE_TOKENS, reissueTokenResponse); | ||
} | ||
} |
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,28 @@ | ||
package side.onetime.controller; | ||
|
||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.http.ResponseEntity; | ||
import org.springframework.web.bind.annotation.PostMapping; | ||
import org.springframework.web.bind.annotation.RequestBody; | ||
import org.springframework.web.bind.annotation.RequestMapping; | ||
import org.springframework.web.bind.annotation.RestController; | ||
import side.onetime.dto.UserDto; | ||
import side.onetime.global.common.ApiResponse; | ||
import side.onetime.global.common.constant.SuccessStatus; | ||
import side.onetime.service.UserService; | ||
|
||
@RestController | ||
@RequestMapping("/api/v1/users") | ||
@RequiredArgsConstructor | ||
public class UserController { | ||
private final UserService userService; | ||
|
||
// 유저 온보딩 API | ||
@PostMapping("/onboarding") | ||
public ResponseEntity<ApiResponse<UserDto.OnboardUserResponse>> onboardUser( | ||
@RequestBody UserDto.OnboardUserRequest onboardUserRequest) { | ||
|
||
UserDto.OnboardUserResponse onboardUserResponse = userService.onboardUser(onboardUserRequest); | ||
return ApiResponse.onSuccess(SuccessStatus._ONBOARD_USER, onboardUserResponse); | ||
} | ||
} |
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,16 @@ | ||
package side.onetime.domain; | ||
|
||
import jakarta.persistence.Id; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
public class RefreshToken { | ||
@Id | ||
private Long userId; | ||
private String refreshToken; | ||
|
||
public RefreshToken(Long userId, String refreshToken) { | ||
this.userId = userId; | ||
this.refreshToken = refreshToken; | ||
} | ||
} |
Oops, something went wrong.