diff --git a/src/main/java/life/mosu/mosuserver/application/auth/AuthService.java b/src/main/java/life/mosu/mosuserver/application/auth/AuthService.java index c911dfba..881dcad3 100644 --- a/src/main/java/life/mosu/mosuserver/application/auth/AuthService.java +++ b/src/main/java/life/mosu/mosuserver/application/auth/AuthService.java @@ -1,10 +1,12 @@ package life.mosu.mosuserver.application.auth; import jakarta.servlet.http.HttpServletRequest; +import life.mosu.mosuserver.domain.profile.ProfileJpaRepository; import life.mosu.mosuserver.domain.user.UserJpaEntity; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; import life.mosu.mosuserver.presentation.auth.dto.LoginRequest; +import life.mosu.mosuserver.presentation.auth.dto.LoginResponse; import life.mosu.mosuserver.presentation.auth.dto.Token; import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; @@ -16,21 +18,25 @@ @Service @RequiredArgsConstructor public class AuthService { + private final AuthenticationManagerBuilder authenticationManagerBuilder; private final AuthTokenManager authTokenManager; + private final ProfileJpaRepository profileJpaRepository; @Transactional - public Token login(final LoginRequest request) { + public LoginResponse login(final LoginRequest request) { try { final UsernamePasswordAuthenticationToken authenticationToken = - new UsernamePasswordAuthenticationToken(request.id(), request.password()); + new UsernamePasswordAuthenticationToken(request.id(), request.password()); final Authentication authentication = authenticationManagerBuilder - .getObject() - .authenticate(authenticationToken); + .getObject() + .authenticate(authenticationToken); final PrincipalDetails principalDetails = (PrincipalDetails) authentication.getPrincipal(); final UserJpaEntity user = principalDetails.user(); - return authTokenManager.generateAuthToken(user); + Boolean isProfileRegistered = profileJpaRepository.existsByUserId(user.getId()); + + return LoginResponse.of(authTokenManager.generateAuthToken(user), isProfileRegistered); } catch (final Exception e) { throw new CustomRuntimeException(ErrorCode.INCORRECT_ID_OR_PASSWORD); } diff --git a/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUser.java b/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUser.java index 4bd41753..43727d92 100644 --- a/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUser.java +++ b/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUser.java @@ -5,26 +5,31 @@ import java.util.Map; import life.mosu.mosuserver.domain.user.UserJpaEntity; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.oauth2.core.user.OAuth2User; -public class OAuthUser implements OAuth2User, UserDetails { +@Slf4j +public class OAuthUser implements OAuth2User { @Getter private final UserJpaEntity user; private final Map attributes; private final String attributeKey; + @Getter + Boolean isProfileRegistered; public OAuthUser( final UserJpaEntity user, final Map attributes, - final String attributeKey + final String attributeKey, + final Boolean isProfileRegistered ) { this.user = user; this.attributes = attributes; this.attributeKey = attributeKey; + this.isProfileRegistered = isProfileRegistered; } @Override @@ -44,34 +49,4 @@ public Collection getAuthorities() { new SimpleGrantedAuthority(role) ); } - - @Override - public String getPassword() { - return null; - } - - @Override - public String getUsername() { - return user.getName(); - } - - @Override - public boolean isAccountNonExpired() { - return UserDetails.super.isAccountNonExpired(); - } - - @Override - public boolean isAccountNonLocked() { - return UserDetails.super.isAccountNonLocked(); - } - - @Override - public boolean isCredentialsNonExpired() { - return UserDetails.super.isCredentialsNonExpired(); - } - - @Override - public boolean isEnabled() { - return UserDetails.super.isEnabled(); - } } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java b/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java index d8225795..49b09ccd 100644 --- a/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java +++ b/src/main/java/life/mosu/mosuserver/application/oauth/OAuthUserService.java @@ -3,6 +3,7 @@ import java.time.LocalDate; import java.util.Map; import life.mosu.mosuserver.domain.profile.Gender; +import life.mosu.mosuserver.domain.profile.ProfileJpaRepository; import life.mosu.mosuserver.domain.user.UserJpaEntity; import life.mosu.mosuserver.domain.user.UserJpaRepository; import life.mosu.mosuserver.domain.user.UserRole; @@ -19,6 +20,7 @@ public class OAuthUserService extends DefaultOAuth2UserService { private final UserJpaRepository userRepository; + private final ProfileJpaRepository profileRepository; @Override public OAuth2User loadUser(final OAuth2UserRequest userRequest) @@ -26,7 +28,6 @@ public OAuth2User loadUser(final OAuth2UserRequest userRequest) final OAuth2User user = super.loadUser(userRequest); final Map oAuth2UserAttributes = user.getAttributes(); - System.out.println("OAuth2User Attributes: " + oAuth2UserAttributes.toString()); final String registrationId = userRequest.getClientRegistration().getRegistrationId(); final String userNameAttributeName = userRequest.getClientRegistration() .getProviderDetails() @@ -38,11 +39,13 @@ public OAuth2User loadUser(final OAuth2UserRequest userRequest) final UserJpaEntity oAuthUser = updateOrWrite(userInfo); - return new OAuthUser(oAuthUser, oAuth2UserAttributes, userNameAttributeName); + Boolean isProfileRegistered = profileRepository.existsByUserId(oAuthUser.getId()); + + return new OAuthUser(oAuthUser, oAuth2UserAttributes, userNameAttributeName, + isProfileRegistered); } private UserJpaEntity updateOrWrite(final OAuthUserInfo info) { - return userRepository.findByLoginId(info.email()) .map(existingUser -> { existingUser.updateOAuthUser(info.gender(), info.name(), diff --git a/src/main/java/life/mosu/mosuserver/domain/profile/ProfileJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/profile/ProfileJpaRepository.java index 3a7754b7..9b44caab 100644 --- a/src/main/java/life/mosu/mosuserver/domain/profile/ProfileJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/profile/ProfileJpaRepository.java @@ -8,5 +8,4 @@ public interface ProfileJpaRepository extends JpaRepository findByUserId(Long userId); - } diff --git a/src/main/java/life/mosu/mosuserver/global/handler/OAuth2LoginSuccessHandler.java b/src/main/java/life/mosu/mosuserver/global/handler/OAuth2LoginSuccessHandler.java index 4c88aded..f165c78b 100644 --- a/src/main/java/life/mosu/mosuserver/global/handler/OAuth2LoginSuccessHandler.java +++ b/src/main/java/life/mosu/mosuserver/global/handler/OAuth2LoginSuccessHandler.java @@ -1,5 +1,6 @@ package life.mosu.mosuserver.global.handler; +import jakarta.servlet.http.Cookie; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; @@ -7,11 +8,9 @@ import life.mosu.mosuserver.application.oauth.OAuthUser; 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.web.authentication.AuthenticationSuccessHandler; import org.springframework.stereotype.Component; -import org.springframework.web.util.UriComponentsBuilder; @Slf4j @Component @@ -19,8 +18,6 @@ public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { private final AccessTokenService accessTokenService; - @Value("${target.url}") - private String targetUrl; @Override public void onAuthenticationSuccess( @@ -29,16 +26,20 @@ public void onAuthenticationSuccess( final Authentication authentication ) throws IOException { final OAuthUser oAuthUser = (OAuthUser) authentication.getPrincipal(); - final String accessToken = accessTokenService.generateAccessToken(oAuthUser.getUser()); - - final String redirectUrlWithToken = UriComponentsBuilder.fromUriString(targetUrl) - .queryParam("token", accessToken) - .build() - .toUriString(); - - log.info("로그인 성공. 리다이렉트 URL: {}", redirectUrlWithToken); - - response.sendRedirect(redirectUrlWithToken); + String jsonResponse = + "{\"isProfileRegistered\": \"" + oAuthUser.getIsProfileRegistered() + "\"}"; + + Cookie cookie = new Cookie("accessToken", accessToken); + cookie.setHttpOnly(true); + cookie.setSecure(true); + cookie.setPath("/"); + cookie.setMaxAge(3600); + response.addCookie(cookie); + + response.setStatus(HttpServletResponse.SC_OK); + response.setContentType("application/json"); + response.setCharacterEncoding("UTF-8"); + response.getWriter().write(jsonResponse); } } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/global/util/EncodeUtil.java b/src/main/java/life/mosu/mosuserver/global/util/EncodeUtil.java index 59c8963b..0ee00aea 100644 --- a/src/main/java/life/mosu/mosuserver/global/util/EncodeUtil.java +++ b/src/main/java/life/mosu/mosuserver/global/util/EncodeUtil.java @@ -3,6 +3,11 @@ import org.springframework.security.crypto.password.PasswordEncoder; public class EncodeUtil { + + private EncodeUtil() { + throw new UnsupportedOperationException(); + } + public static String passwordEncode(final PasswordEncoder encoder, final String password) { return encoder.encode(password); } diff --git a/src/main/java/life/mosu/mosuserver/presentation/auth/AuthController.java b/src/main/java/life/mosu/mosuserver/presentation/auth/AuthController.java index 83fe3a8b..b5abf5e7 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/auth/AuthController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/auth/AuthController.java @@ -6,6 +6,7 @@ import life.mosu.mosuserver.application.auth.AuthTokenManager; import life.mosu.mosuserver.global.util.ApiResponseWrapper; import life.mosu.mosuserver.presentation.auth.dto.LoginRequest; +import life.mosu.mosuserver.presentation.auth.dto.LoginResponse; import life.mosu.mosuserver.presentation.auth.dto.Token; import lombok.RequiredArgsConstructor; import org.springframework.http.HttpHeaders; @@ -32,11 +33,12 @@ public class AuthController implements AuthControllerDocs { * @return 로그인 한 회원의 Access Token과 Refresh Token */ @PostMapping("/login") - public ResponseEntity> login( + public ResponseEntity> login( @RequestBody @Valid final LoginRequest request) { - final Token token = authService.login(request); + final LoginResponse response = authService.login(request); - final ResponseCookie cookie = ResponseCookie.from("accessToken", token.accessToken()) + final ResponseCookie cookie = ResponseCookie.from("accessToken", + response.token().accessToken()) .httpOnly(true) .secure(true) .path("/") @@ -45,7 +47,7 @@ public ResponseEntity> login( return ResponseEntity.status(HttpStatus.CREATED) .header(HttpHeaders.SET_COOKIE, cookie.toString()) - .body(ApiResponseWrapper.success(HttpStatus.CREATED, token)); + .body(ApiResponseWrapper.success(HttpStatus.CREATED, response)); } /** diff --git a/src/main/java/life/mosu/mosuserver/presentation/auth/AuthControllerDocs.java b/src/main/java/life/mosu/mosuserver/presentation/auth/AuthControllerDocs.java index 112722d5..6f5addd0 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/auth/AuthControllerDocs.java +++ b/src/main/java/life/mosu/mosuserver/presentation/auth/AuthControllerDocs.java @@ -6,6 +6,7 @@ import jakarta.validation.Valid; import life.mosu.mosuserver.global.util.ApiResponseWrapper; import life.mosu.mosuserver.presentation.auth.dto.LoginRequest; +import life.mosu.mosuserver.presentation.auth.dto.LoginResponse; import life.mosu.mosuserver.presentation.auth.dto.Token; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestBody; @@ -13,8 +14,8 @@ @Tag(description = "인증 API", name = "Auth API") public interface AuthControllerDocs { - @Operation(description = "로그인 API 지금은 쿠키와 response 둘다 반환하는데 곧 쿠키로만 작동하게 할 것 입니다.", summary = "사용자가 로그인합니다.") - public ResponseEntity> login( + @Operation(description = "로그인 API 지금은 쿠키와 response 둘다 반환하는데 곧 쿠키로만 작동하게 할 것 입니다. <프론트하고 변경하려고 Response 이렇게 만들었는데 나중에 같이 맞춥시다!>", summary = "사용자가 로그인합니다.") + public ResponseEntity> login( @RequestBody @Valid final LoginRequest request); @Operation(description = "수정될 예정 입니다.") diff --git a/src/main/java/life/mosu/mosuserver/presentation/auth/dto/LoginResponse.java b/src/main/java/life/mosu/mosuserver/presentation/auth/dto/LoginResponse.java new file mode 100644 index 00000000..46086bcc --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/auth/dto/LoginResponse.java @@ -0,0 +1,11 @@ +package life.mosu.mosuserver.presentation.auth.dto; + +public record LoginResponse( + Token token, + Boolean isProfileRegistered +) { + + public static LoginResponse of(final Token token, final Boolean isProfileRegistered) { + return new LoginResponse(token, isProfileRegistered); + } +} diff --git a/src/main/java/life/mosu/mosuserver/presentation/oauth/AccessTokenFilter.java b/src/main/java/life/mosu/mosuserver/presentation/oauth/AccessTokenFilter.java index 721c944f..e7ea1295 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/oauth/AccessTokenFilter.java +++ b/src/main/java/life/mosu/mosuserver/presentation/oauth/AccessTokenFilter.java @@ -37,7 +37,8 @@ // return; // } // -// if (request.getRequestURI().startsWith("/api/v1/oauth2")) { +// if (request.getRequestURI().startsWith("/api/v1/oauth2") || request.getRequestURI() +// .startsWith("/api/v1/oauth")) { // filterChain.doFilter(request, response); // return; // } diff --git a/src/main/java/life/mosu/mosuserver/presentation/oauth/OAuthController.java b/src/main/java/life/mosu/mosuserver/presentation/oauth/OAuthController.java index 937dc88c..83bd7868 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/oauth/OAuthController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/oauth/OAuthController.java @@ -1,14 +1,15 @@ package life.mosu.mosuserver.presentation.oauth; +import static life.mosu.mosuserver.global.resolver.AuthorizationRequestRedirectResolver.REDIRECT_PARAM_KEY; + import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.view.RedirectView; import org.springframework.web.util.UriComponentsBuilder; @Slf4j @@ -17,30 +18,16 @@ @RequestMapping("/oauth") public class OAuthController { - @GetMapping("/authorize-code") - public ResponseEntity handleOAuthCodeFromFrontend( - @RequestParam String code, - @RequestParam(required = false) String state + @GetMapping("/login/{registrationId}") + public RedirectView login( + @PathVariable String registrationId, + @RequestParam(REDIRECT_PARAM_KEY) String redirect ) { - UriComponentsBuilder uriBuilder = UriComponentsBuilder - .fromPath("/api/v1/oauth2/callback/kakao") - .queryParam("code", code); - - if (state != null && !state.isEmpty()) { - uriBuilder.queryParam("state", state); - } - - String redirectUrl = UriComponentsBuilder.newInstance() - .scheme("http") - .host("localhost") - .port(8080) - .path(uriBuilder.toUriString()) - .build() + final String url = UriComponentsBuilder + .fromPath("/api/v1/oauth2/authorization/{registrationId}") + .queryParam(REDIRECT_PARAM_KEY, redirect) + .buildAndExpand(registrationId) .toUriString(); - - log.info("클라이언트를 Spring Security OAuth2 콜백 URL로 리다이렉트합니다: {}", redirectUrl); - HttpHeaders headers = new HttpHeaders(); - headers.add(HttpHeaders.LOCATION, redirectUrl); - return new ResponseEntity<>(headers, HttpStatus.FOUND); + return new RedirectView(url); } } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/profile/ProfileController.java b/src/main/java/life/mosu/mosuserver/presentation/profile/ProfileController.java index af762ffe..8a96c05a 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/profile/ProfileController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/profile/ProfileController.java @@ -11,7 +11,6 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; -import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -38,7 +37,7 @@ public ResponseEntity> create( } @PutMapping - @PreAuthorize("isAuthenticated()") +// @PreAuthorize("isAuthenticated()") public ResponseEntity> update( @RequestParam Long userId, @Valid @RequestBody EditProfileRequest request