From 468d47735398b5b001f58d6ffd68e1472a4b807f Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Fri, 7 Feb 2025 16:04:12 +0900 Subject: [PATCH 01/11] =?UTF-8?q?refactor:=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=EC=97=90=20password=20=EC=BB=AC=EB=9F=BC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../siteuser/domain/SiteUser.java | 25 +++++++++++++++++++ .../db/migration/V5__add_password_column.sql | 2 ++ 2 files changed, 27 insertions(+) create mode 100644 src/main/resources/db/migration/V5__add_password_column.sql diff --git a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java index 2c2a5d8be..d6624c522 100644 --- a/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/SiteUser.java @@ -82,6 +82,9 @@ public class SiteUser { @Setter private LocalDate quitedAt; + @Column(nullable = true) + private String password; + @OneToMany(mappedBy = "siteUser", cascade = CascadeType.ALL, orphanRemoval = true) private List postList = new ArrayList<>(); @@ -133,4 +136,26 @@ public SiteUser( this.gender = gender; this.authType = authType; } + + // todo: 가입 방법에 따라서 정해진 인자만 받고, 그렇지 않을 경우 예외 발생하도록 수정 필요 + public SiteUser( + String email, + String nickname, + String profileImageUrl, + String birth, + PreparationStatus preparationStage, + Role role, + Gender gender, + AuthType authType, + String password) { + this.email = email; + this.nickname = nickname; + this.profileImageUrl = profileImageUrl; + this.birth = birth; + this.preparationStage = preparationStage; + this.role = role; + this.gender = gender; + this.authType = authType; + this.password = password; + } } diff --git a/src/main/resources/db/migration/V5__add_password_column.sql b/src/main/resources/db/migration/V5__add_password_column.sql new file mode 100644 index 000000000..948e2a97d --- /dev/null +++ b/src/main/resources/db/migration/V5__add_password_column.sql @@ -0,0 +1,2 @@ +ALTER TABLE site_user +ADD COLUMN password VARCHAR(255) NULL; From c8b3ae9e88f003535041b17ce5735b71631c7c48 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Fri, 7 Feb 2025 16:53:32 +0900 Subject: [PATCH 02/11] =?UTF-8?q?feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=95=94=ED=98=B8=ED=99=94=20=EB=B9=88=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config/security/SecurityConfiguration.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java index 6851b3e8c..98492568b 100644 --- a/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java +++ b/src/main/java/com/example/solidconnection/config/security/SecurityConfiguration.java @@ -10,6 +10,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.www.BasicAuthenticationFilter; import org.springframework.web.cors.CorsConfiguration; @@ -40,6 +42,11 @@ public CorsConfigurationSource corsConfigurationSource() { return source; } + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http From 9c16473c3e9456cede5be62f21611353ae69751d Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Fri, 7 Feb 2025 21:30:12 +0900 Subject: [PATCH 03/11] =?UTF-8?q?refactor:=20Oauth=20=EA=B0=80=EC=9E=85?= =?UTF-8?q?=EC=9A=A9=20=ED=86=A0=ED=81=B0=20=EC=A0=9C=EA=B3=B5=EC=9E=90=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/oauth/AppleOAuthService.java | 4 +- .../auth/service/oauth/KakaoOAuthService.java | 4 +- .../auth/service/oauth/OAuthService.java | 8 ++-- ...der.java => OAuthSignUpTokenProvider.java} | 12 +++--- .../custom/exception/ErrorCode.java | 6 +-- ...java => OAuthSignUpTokenProviderTest.java} | 40 +++++++++---------- .../solidconnection/e2e/SignUpTest.java | 14 +++---- 7 files changed, 44 insertions(+), 44 deletions(-) rename src/main/java/com/example/solidconnection/auth/service/oauth/{SignUpTokenProvider.java => OAuthSignUpTokenProvider.java} (88%) rename src/test/java/com/example/solidconnection/auth/service/oauth/{SignUpTokenProviderTest.java => OAuthSignUpTokenProviderTest.java} (79%) diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/AppleOAuthService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/AppleOAuthService.java index 2af82e07d..2605ad89f 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/AppleOAuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/AppleOAuthService.java @@ -12,9 +12,9 @@ public class AppleOAuthService extends OAuthService { private final AppleOAuthClient appleOAuthClient; - public AppleOAuthService(SignUpTokenProvider signUpTokenProvider, SiteUserRepository siteUserRepository, + public AppleOAuthService(OAuthSignUpTokenProvider OAuthSignUpTokenProvider, SiteUserRepository siteUserRepository, AppleOAuthClient appleOAuthClient, SignInService signInService) { - super(signUpTokenProvider, siteUserRepository, signInService); + super(OAuthSignUpTokenProvider, siteUserRepository, signInService); this.appleOAuthClient = appleOAuthClient; } diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/KakaoOAuthService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/KakaoOAuthService.java index 5dc6faea1..c2202ab2a 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/KakaoOAuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/KakaoOAuthService.java @@ -12,9 +12,9 @@ public class KakaoOAuthService extends OAuthService { private final KakaoOAuthClient kakaoOAuthClient; - public KakaoOAuthService(SignUpTokenProvider signUpTokenProvider, SiteUserRepository siteUserRepository, + public KakaoOAuthService(OAuthSignUpTokenProvider OAuthSignUpTokenProvider, SiteUserRepository siteUserRepository, KakaoOAuthClient kakaoOAuthClient, SignInService signInService) { - super(signUpTokenProvider, siteUserRepository, signInService); + super(OAuthSignUpTokenProvider, siteUserRepository, signInService); this.kakaoOAuthClient = kakaoOAuthClient; } diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java index 4f37db060..6e9bf7030 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthService.java @@ -22,12 +22,12 @@ * */ public abstract class OAuthService { - private final SignUpTokenProvider signUpTokenProvider; + private final OAuthSignUpTokenProvider OAuthSignUpTokenProvider; private final SignInService signInService; private final SiteUserRepository siteUserRepository; - protected OAuthService(SignUpTokenProvider signUpTokenProvider, SiteUserRepository siteUserRepository, SignInService signInService) { - this.signUpTokenProvider = signUpTokenProvider; + protected OAuthService(OAuthSignUpTokenProvider OAuthSignUpTokenProvider, SiteUserRepository siteUserRepository, SignInService signInService) { + this.OAuthSignUpTokenProvider = OAuthSignUpTokenProvider; this.siteUserRepository = siteUserRepository; this.signInService = signInService; } @@ -52,7 +52,7 @@ protected final OAuthSignInResponse getSignInResponse(SiteUser siteUser) { } protected final SignUpPrepareResponse getSignUpPrepareResponse(OAuthUserInfoDto userInfoDto) { - String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(userInfoDto.getEmail(), getAuthType()); + String signUpToken = OAuthSignUpTokenProvider.generateAndSaveSignUpToken(userInfoDto.getEmail(), getAuthType()); return SignUpPrepareResponse.of(userInfoDto, signUpToken); } diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/SignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java similarity index 88% rename from src/main/java/com/example/solidconnection/auth/service/oauth/SignUpTokenProvider.java rename to src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java index 5399dc1eb..c3a95dbe9 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/SignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProvider.java @@ -16,17 +16,17 @@ import java.util.Map; import java.util.Objects; -import static com.example.solidconnection.custom.exception.ErrorCode.OAUTH_SIGN_UP_TOKEN_INVALID; -import static com.example.solidconnection.custom.exception.ErrorCode.OAUTH_SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; +import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; import static com.example.solidconnection.util.JwtUtils.parseClaims; import static com.example.solidconnection.util.JwtUtils.parseSubject; @Component -public class SignUpTokenProvider extends TokenProvider { +public class OAuthSignUpTokenProvider extends TokenProvider { static final String AUTH_TYPE_CLAIM_KEY = "authType"; - public SignUpTokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate) { + public OAuthSignUpTokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate) { super(jwtProperties, redisTemplate); } @@ -58,14 +58,14 @@ private void validateFormatAndExpiration(String token) { String serializedAuthType = claims.get(AUTH_TYPE_CLAIM_KEY, String.class); AuthType.valueOf(serializedAuthType); } catch (Exception e) { - throw new CustomException(OAUTH_SIGN_UP_TOKEN_INVALID); + throw new CustomException(SIGN_UP_TOKEN_INVALID); } } private void validateIssuedByServer(String email) { String key = TokenType.SIGN_UP.addPrefix(email); if (redisTemplate.opsForValue().get(key) == null) { - throw new CustomException(OAUTH_SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER); + throw new CustomException(SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER); } } diff --git a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java index d3fdf136d..cd0bb6695 100644 --- a/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java +++ b/src/main/java/com/example/solidconnection/custom/exception/ErrorCode.java @@ -28,9 +28,9 @@ public enum ErrorCode { KAKAO_USER_INFO_FAIL(HttpStatus.BAD_REQUEST.value(), "카카오 사용자 정보 조회에 실패했습니다."), INVALID_SERVICE_PUBLISHED_KAKAO_TOKEN(HttpStatus.BAD_REQUEST.value(), "우리 서비스에서 발급한 카카오 토큰이 아닙니다"), - // oauth - OAUTH_SIGN_UP_TOKEN_INVALID(HttpStatus.BAD_REQUEST.value(), "유효하지 않은 회원가입 토큰입니다."), - OAUTH_SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER(HttpStatus.BAD_REQUEST.value(), "회원가입 토큰이 우리 서버에서 발급되지 않았습니다."), + // sign up token + SIGN_UP_TOKEN_INVALID(HttpStatus.BAD_REQUEST.value(), "유효하지 않은 회원가입 토큰입니다."), + SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER(HttpStatus.BAD_REQUEST.value(), "회원가입 토큰이 우리 서버에서 발급되지 않았습니다."), // data not found UNIVERSITY_INFO_FOR_APPLY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "존재하지 않는 대학교 지원 정보입니다."), diff --git a/src/test/java/com/example/solidconnection/auth/service/oauth/SignUpTokenProviderTest.java b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java similarity index 79% rename from src/test/java/com/example/solidconnection/auth/service/oauth/SignUpTokenProviderTest.java rename to src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java index d3a1efac1..12ab6f666 100644 --- a/src/test/java/com/example/solidconnection/auth/service/oauth/SignUpTokenProviderTest.java +++ b/src/test/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpTokenProviderTest.java @@ -20,19 +20,19 @@ import java.util.HashMap; import java.util.Map; -import static com.example.solidconnection.auth.service.oauth.SignUpTokenProvider.AUTH_TYPE_CLAIM_KEY; -import static com.example.solidconnection.custom.exception.ErrorCode.OAUTH_SIGN_UP_TOKEN_INVALID; -import static com.example.solidconnection.custom.exception.ErrorCode.OAUTH_SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; +import static com.example.solidconnection.auth.service.oauth.OAuthSignUpTokenProvider.AUTH_TYPE_CLAIM_KEY; +import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertAll; @TestContainerSpringBootTest -@DisplayName("회원가입 토큰 제공자 테스트") -class SignUpTokenProviderTest { +@DisplayName("OAuth 회원가입 토큰 제공자 테스트") +class OAuthSignUpTokenProviderTest { @Autowired - private SignUpTokenProvider signUpTokenProvider; + private OAuthSignUpTokenProvider OAuthSignUpTokenProvider; @Autowired private RedisTemplate redisTemplate; @@ -47,7 +47,7 @@ class SignUpTokenProviderTest { AuthType authType = AuthType.KAKAO; // when - String signUpToken = signUpTokenProvider.generateAndSaveSignUpToken(email, authType); + String signUpToken = OAuthSignUpTokenProvider.generateAndSaveSignUpToken(email, authType); // then Claims claims = JwtUtils.parseClaims(signUpToken, jwtProperties.secret()); @@ -73,7 +73,7 @@ class 주어진_회원가입_토큰을_검증한다 { redisTemplate.opsForValue().set(TokenType.SIGN_UP.addPrefix(email), validToken); // when & then - assertThatCode(() -> signUpTokenProvider.validateSignUpToken(validToken)).doesNotThrowAnyException(); + assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(validToken)).doesNotThrowAnyException(); } @Test @@ -82,9 +82,9 @@ class 주어진_회원가입_토큰을_검증한다 { String expiredToken = createExpiredToken(); // when & then - assertThatCode(() -> signUpTokenProvider.validateSignUpToken(expiredToken)) + assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(expiredToken)) .isInstanceOf(CustomException.class) - .hasMessageContaining(OAUTH_SIGN_UP_TOKEN_INVALID.getMessage()); + .hasMessageContaining(SIGN_UP_TOKEN_INVALID.getMessage()); } @Test @@ -93,9 +93,9 @@ class 주어진_회원가입_토큰을_검증한다 { String notJwt = "not jwt"; // when & then - assertThatCode(() -> signUpTokenProvider.validateSignUpToken(notJwt)) + assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(notJwt)) .isInstanceOf(CustomException.class) - .hasMessageContaining(OAUTH_SIGN_UP_TOKEN_INVALID.getMessage()); + .hasMessageContaining(SIGN_UP_TOKEN_INVALID.getMessage()); } @Test @@ -105,9 +105,9 @@ class 주어진_회원가입_토큰을_검증한다 { String wrongAuthType = createBaseJwtBuilder().addClaims(wrongClaim).compact(); // when & then - assertThatCode(() -> signUpTokenProvider.validateSignUpToken(wrongAuthType)) + assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(wrongAuthType)) .isInstanceOf(CustomException.class) - .hasMessageContaining(OAUTH_SIGN_UP_TOKEN_INVALID.getMessage()); + .hasMessageContaining(SIGN_UP_TOKEN_INVALID.getMessage()); } @Test @@ -117,9 +117,9 @@ class 주어진_회원가입_토큰을_검증한다 { String noSubject = createBaseJwtBuilder().addClaims(claim).compact(); // when & then - assertThatCode(() -> signUpTokenProvider.validateSignUpToken(noSubject)) + assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(noSubject)) .isInstanceOf(CustomException.class) - .hasMessageContaining(OAUTH_SIGN_UP_TOKEN_INVALID.getMessage()); + .hasMessageContaining(SIGN_UP_TOKEN_INVALID.getMessage()); } @Test @@ -129,9 +129,9 @@ class 주어진_회원가입_토큰을_검증한다 { String signUpToken = createBaseJwtBuilder().addClaims(validClaim).setSubject("email").compact(); // when & then - assertThatCode(() -> signUpTokenProvider.validateSignUpToken(signUpToken)) + assertThatCode(() -> OAuthSignUpTokenProvider.validateSignUpToken(signUpToken)) .isInstanceOf(CustomException.class) - .hasMessageContaining(OAUTH_SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER.getMessage()); + .hasMessageContaining(SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER.getMessage()); } } @@ -144,7 +144,7 @@ class 주어진_회원가입_토큰을_검증한다 { redisTemplate.opsForValue().set(TokenType.SIGN_UP.addPrefix(email), validToken); // when - String extractedEmail = signUpTokenProvider.parseEmail(validToken); + String extractedEmail = OAuthSignUpTokenProvider.parseEmail(validToken); // then assertThat(extractedEmail).isEqualTo(email); @@ -158,7 +158,7 @@ class 주어진_회원가입_토큰을_검증한다 { String validToken = createBaseJwtBuilder().setSubject("email").addClaims(claim).compact(); // when - AuthType extractedAuthType = signUpTokenProvider.parseAuthType(validToken); + AuthType extractedAuthType = OAuthSignUpTokenProvider.parseAuthType(validToken); // then assertThat(extractedAuthType).isEqualTo(authType); diff --git a/src/test/java/com/example/solidconnection/e2e/SignUpTest.java b/src/test/java/com/example/solidconnection/e2e/SignUpTest.java index 7d01f365c..6599c20cc 100644 --- a/src/test/java/com/example/solidconnection/e2e/SignUpTest.java +++ b/src/test/java/com/example/solidconnection/e2e/SignUpTest.java @@ -3,7 +3,7 @@ import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.service.AuthTokenProvider; -import com.example.solidconnection.auth.service.oauth.SignUpTokenProvider; +import com.example.solidconnection.auth.service.oauth.OAuthSignUpTokenProvider; import com.example.solidconnection.custom.response.ErrorResponse; import com.example.solidconnection.entity.Country; import com.example.solidconnection.entity.InterestedCountry; @@ -30,7 +30,7 @@ import static com.example.solidconnection.auth.domain.TokenType.REFRESH; import static com.example.solidconnection.custom.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; -import static com.example.solidconnection.custom.exception.ErrorCode.OAUTH_SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_EXISTED; import static com.example.solidconnection.e2e.DynamicFixture.createSiteUserByEmail; import static com.example.solidconnection.e2e.DynamicFixture.createSiteUserByNickName; @@ -59,7 +59,7 @@ class SignUpTest extends BaseEndToEndTest { AuthTokenProvider authTokenProvider; @Autowired - SignUpTokenProvider signUpTokenProvider; + OAuthSignUpTokenProvider OAuthSignUpTokenProvider; @Autowired RedisTemplate redisTemplate; @@ -74,7 +74,7 @@ class SignUpTest extends BaseEndToEndTest { // setup - 카카오 토큰 발급 String email = "email@email.com"; - String generatedKakaoToken = signUpTokenProvider.generateAndSaveSignUpToken(email, AuthType.KAKAO); + String generatedKakaoToken = OAuthSignUpTokenProvider.generateAndSaveSignUpToken(email, AuthType.KAKAO); // request - body 생성 및 요청 List interestedRegionNames = List.of("유럽"); @@ -126,7 +126,7 @@ class SignUpTest extends BaseEndToEndTest { // setup - 카카오 토큰 발급 String email = "email@email.com"; - String generatedKakaoToken = signUpTokenProvider.generateAndSaveSignUpToken(email, AuthType.KAKAO); + String generatedKakaoToken = OAuthSignUpTokenProvider.generateAndSaveSignUpToken(email, AuthType.KAKAO); // request - body 생성 및 요청 SignUpRequest signUpRequest = new SignUpRequest(generatedKakaoToken, null, null, @@ -151,7 +151,7 @@ class SignUpTest extends BaseEndToEndTest { siteUserRepository.save(alreadyExistUser); // setup - 카카오 토큰 발급 - String generatedKakaoToken = signUpTokenProvider.generateAndSaveSignUpToken(alreadyExistEmail, AuthType.KAKAO); + String generatedKakaoToken = OAuthSignUpTokenProvider.generateAndSaveSignUpToken(alreadyExistEmail, AuthType.KAKAO); // request - body 생성 및 요청 SignUpRequest signUpRequest = new SignUpRequest(generatedKakaoToken, null, null, @@ -181,6 +181,6 @@ class SignUpTest extends BaseEndToEndTest { .extract().as(ErrorResponse.class); assertThat(errorResponse.message()) - .contains(OAUTH_SIGN_UP_TOKEN_INVALID.getMessage()); + .contains(SIGN_UP_TOKEN_INVALID.getMessage()); } } From d70edf71e0ecaec29a5a4f06223007f59024b24d Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Fri, 7 Feb 2025 21:30:42 +0900 Subject: [PATCH 04/11] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EA=B0=80=EC=9E=85=EC=9A=A9=20=ED=86=A0=ED=81=B0=20=EC=A0=9C?= =?UTF-8?q?=EA=B3=B5=EC=9E=90=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/EmailSignUpTokenProvider.java | 84 +++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java new file mode 100644 index 000000000..73a376fe2 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java @@ -0,0 +1,84 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.config.security.JwtProperties; +import com.example.solidconnection.custom.exception.CustomException; +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Component; + +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; +import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER; +import static com.example.solidconnection.util.JwtUtils.parseClaims; +import static com.example.solidconnection.util.JwtUtils.parseSubject; + +@Component +public class EmailSignUpTokenProvider extends TokenProvider { + + static final String PASSWORD_CLAIM_KEY = "password"; + + private final PasswordEncoder passwordEncoder; + + public EmailSignUpTokenProvider(JwtProperties jwtProperties, RedisTemplate redisTemplate, + PasswordEncoder passwordEncoder) { + super(jwtProperties, redisTemplate); + this.passwordEncoder = passwordEncoder; + } + + public String generateAndSaveSignUpToken(String email, String password) { + String encodedPassword = passwordEncoder.encode(password); + Map passwordClaims = new HashMap<>(Map.of(PASSWORD_CLAIM_KEY, encodedPassword)); + Claims claims = Jwts.claims(passwordClaims).setSubject(email); + Date now = new Date(); + Date expiredDate = new Date(now.getTime() + TokenType.SIGN_UP.getExpireTime()); + + String signUpToken = Jwts.builder() + .setClaims(claims) + .setIssuedAt(now) + .setExpiration(expiredDate) + .signWith(SignatureAlgorithm.HS512, jwtProperties.secret()) + .compact(); + return saveToken(signUpToken, TokenType.SIGN_UP); + } + + public void validateSignUpToken(String token) { + validateFormatAndExpiration(token); + String email = parseEmail(token); + validateIssuedByServer(email); + } + + private void validateFormatAndExpiration(String token) { + try { + Claims claims = parseClaims(token, jwtProperties.secret()); + Objects.requireNonNull(claims.getSubject()); + String encodedPassword = claims.get(PASSWORD_CLAIM_KEY, String.class); + Objects.requireNonNull(encodedPassword); + } catch (Exception e) { + throw new CustomException(SIGN_UP_TOKEN_INVALID); + } + } + + private void validateIssuedByServer(String email) { + String key = TokenType.SIGN_UP.addPrefix(email); + if (redisTemplate.opsForValue().get(key) == null) { + throw new CustomException(SIGN_UP_TOKEN_NOT_ISSUED_BY_SERVER); + } + } + + public String parseEmail(String token) { + return parseSubject(token, jwtProperties.secret()); + } + + public String parseEncodedPassword(String token) { + Claims claims = parseClaims(token, jwtProperties.secret()); + return claims.get(PASSWORD_CLAIM_KEY, String.class); + } +} From 7d97e03e195d2a0f78c9ebd7e6b00fdea708cc77 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Fri, 7 Feb 2025 21:32:03 +0900 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EA=B5=AC=ED=98=84,=20?= =?UTF-8?q?=ED=9A=8C=EC=9B=90=EA=B0=80=EC=9E=85=20=EC=84=9C=EB=B9=84?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EC=83=81=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/dto/SignUpRequest.java | 16 +++- .../auth/service/EmailSignUpService.java | 48 ++++++++++ .../auth/service/SignUpService.java | 90 +++++++++++++++++++ .../service/oauth/OAuthSignUpService.java | 83 +++++------------ 4 files changed, 174 insertions(+), 63 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java create mode 100644 src/main/java/com/example/solidconnection/auth/service/SignUpService.java diff --git a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java index b28b467bd..43f8e6caf 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/SignUpRequest.java @@ -24,7 +24,7 @@ public record SignUpRequest( @JsonFormat(pattern = "yyyy-MM-dd") String birth) { - public SiteUser toSiteUser(String email, AuthType authType) { + public SiteUser toOAuthSiteUser(String email, AuthType authType) { return new SiteUser( email, this.nickname, @@ -36,4 +36,18 @@ public SiteUser toSiteUser(String email, AuthType authType) { authType ); } + + public SiteUser toEmailSiteUser(String email, String encodedPassword) { + return new SiteUser( + email, + this.nickname, + this.profileImageUrl, + this.birth, + this.preparationStatus, + Role.MENTEE, + this.gender, + AuthType.EMAIL, + encodedPassword + ); + } } diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java new file mode 100644 index 000000000..2678e3880 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java @@ -0,0 +1,48 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.dto.SignUpRequest; +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.repositories.CountryRepository; +import com.example.solidconnection.repositories.InterestedCountyRepository; +import com.example.solidconnection.repositories.InterestedRegionRepository; +import com.example.solidconnection.repositories.RegionRepository; +import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import org.springframework.stereotype.Service; + +import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_EXISTED; + +@Service +public class EmailSignUpService extends SignUpService { + + private final EmailSignUpTokenProvider emailSignUpTokenProvider; + + public EmailSignUpService(SignInService signInService, SiteUserRepository siteUserRepository, + RegionRepository regionRepository, InterestedRegionRepository interestedRegionRepository, + CountryRepository countryRepository, InterestedCountyRepository interestedCountyRepository, + EmailSignUpTokenProvider emailSignUpTokenProvider) { + super(signInService, siteUserRepository, regionRepository, interestedRegionRepository, countryRepository, interestedCountyRepository); + this.emailSignUpTokenProvider = emailSignUpTokenProvider; + } + + @Override + protected void validateSignUpToken(SignUpRequest signUpRequest) { + emailSignUpTokenProvider.validateSignUpToken(signUpRequest.signUpToken()); + } + + @Override + protected void validateUserNotDuplicated(SignUpRequest signUpRequest) { + String email = emailSignUpTokenProvider.parseEmail(signUpRequest.signUpToken()); + if (siteUserRepository.existsByEmailAndAuthType(email, AuthType.EMAIL)) { + throw new CustomException(USER_ALREADY_EXISTED); + } + } + + @Override + protected SiteUser createSiteUser(SignUpRequest signUpRequest) { + String email = emailSignUpTokenProvider.parseEmail(signUpRequest.signUpToken()); + String encodedPassword = emailSignUpTokenProvider.parseEncodedPassword(signUpRequest.signUpToken()); + return signUpRequest.toEmailSiteUser(email, encodedPassword); + } +} diff --git a/src/main/java/com/example/solidconnection/auth/service/SignUpService.java b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java new file mode 100644 index 000000000..319083658 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/SignUpService.java @@ -0,0 +1,90 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.dto.SignInResponse; +import com.example.solidconnection.auth.dto.SignUpRequest; +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.entity.InterestedCountry; +import com.example.solidconnection.entity.InterestedRegion; +import com.example.solidconnection.repositories.CountryRepository; +import com.example.solidconnection.repositories.InterestedCountyRepository; +import com.example.solidconnection.repositories.InterestedRegionRepository; +import com.example.solidconnection.repositories.RegionRepository; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static com.example.solidconnection.custom.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; + +/* + * 우리 서버에서 인증되었음을 확인하기 위한 signUpToken 을 검증한다. + * - 사용자 정보를 DB에 저장한다. + * - 관심 국가와 지역을 DB에 저장한다. + * - 관심 국가와 지역은 site_user_id를 참조하므로, 사용자 저장 후 저장한다. + * - 바로 로그인하도록 액세스 토큰과 리프레시 토큰을 발급한다. + * */ +public abstract class SignUpService { + + protected final SignInService signInService; + protected final SiteUserRepository siteUserRepository; + protected final RegionRepository regionRepository; + protected final InterestedRegionRepository interestedRegionRepository; + protected final CountryRepository countryRepository; + protected final InterestedCountyRepository interestedCountyRepository; + + protected SignUpService(SignInService signInService, SiteUserRepository siteUserRepository, + RegionRepository regionRepository, InterestedRegionRepository interestedRegionRepository, + CountryRepository countryRepository, InterestedCountyRepository interestedCountyRepository) { + this.signInService = signInService; + this.siteUserRepository = siteUserRepository; + this.regionRepository = regionRepository; + this.interestedRegionRepository = interestedRegionRepository; + this.countryRepository = countryRepository; + this.interestedCountyRepository = interestedCountyRepository; + } + + @Transactional + public SignInResponse signUp(SignUpRequest signUpRequest) { + // 검증 + validateSignUpToken(signUpRequest); + validateUserNotDuplicated(signUpRequest); + validateNicknameDuplicated(signUpRequest.nickname()); + + // 사용자 저장 + SiteUser siteUser = siteUserRepository.save(createSiteUser(signUpRequest)); + + // 관심 지역, 국가 저장 + saveInterestedRegion(signUpRequest, siteUser); + saveInterestedCountry(signUpRequest, siteUser); + + // 로그인 + return signInService.signIn(siteUser); + } + + private void validateNicknameDuplicated(String nickname) { + if (siteUserRepository.existsByNickname(nickname)) { + throw new CustomException(NICKNAME_ALREADY_EXISTED); + } + } + + private void saveInterestedRegion(SignUpRequest signUpRequest, SiteUser savedSiteUser) { + List interestedRegionNames = signUpRequest.interestedRegions(); + List interestedRegions = regionRepository.findByKoreanNames(interestedRegionNames).stream() + .map(region -> new InterestedRegion(savedSiteUser, region)) + .toList(); + interestedRegionRepository.saveAll(interestedRegions); + } + + private void saveInterestedCountry(SignUpRequest signUpRequest, SiteUser savedSiteUser) { + List interestedCountryNames = signUpRequest.interestedCountries(); + List interestedCountries = countryRepository.findByKoreanNames(interestedCountryNames).stream() + .map(country -> new InterestedCountry(savedSiteUser, country)) + .toList(); + interestedCountyRepository.saveAll(interestedCountries); + } + + protected abstract void validateSignUpToken(SignUpRequest signUpRequest); + protected abstract void validateUserNotDuplicated(SignUpRequest signUpRequest); + protected abstract SiteUser createSiteUser(SignUpRequest signUpRequest); +} diff --git a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java index 7b6d44d26..a46728bb2 100644 --- a/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/oauth/OAuthSignUpService.java @@ -1,11 +1,9 @@ package com.example.solidconnection.auth.service.oauth; -import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.service.SignInService; +import com.example.solidconnection.auth.service.SignUpService; import com.example.solidconnection.custom.exception.CustomException; -import com.example.solidconnection.entity.InterestedCountry; -import com.example.solidconnection.entity.InterestedRegion; import com.example.solidconnection.repositories.CountryRepository; import com.example.solidconnection.repositories.InterestedCountyRepository; import com.example.solidconnection.repositories.InterestedRegionRepository; @@ -13,80 +11,41 @@ import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import com.example.solidconnection.siteuser.repository.SiteUserRepository; -import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import java.util.List; - -import static com.example.solidconnection.custom.exception.ErrorCode.NICKNAME_ALREADY_EXISTED; import static com.example.solidconnection.custom.exception.ErrorCode.USER_ALREADY_EXISTED; -@RequiredArgsConstructor @Service -public class OAuthSignUpService { - - private final SignUpTokenProvider signUpTokenProvider; - private final SignInService signInService; - private final SiteUserRepository siteUserRepository; - private final RegionRepository regionRepository; - private final InterestedRegionRepository interestedRegionRepository; - private final CountryRepository countryRepository; - private final InterestedCountyRepository interestedCountyRepository; - - /* - * OAuth 인증 후 회원가입을 한다. - * - 우리 서버에서 OAuth 인증했음을 확인하기 위한 signUpToken 을 검증한다. - * - 사용자 정보를 DB에 저장한다. - * - 관심 국가와 지역을 DB에 저장한다. - * - 관심 국가와 지역은 site_user_id를 참조하므로, 사용자 저장 후 저장한다. - * - 바로 로그인하도록 액세스 토큰과 리프레시 토큰을 발급한다. - * */ - @Transactional - public SignInResponse signUp(SignUpRequest signUpRequest) { - // 검증 - signUpTokenProvider.validateSignUpToken(signUpRequest.signUpToken()); - validateNicknameDuplicated(signUpRequest.nickname()); - String email = signUpTokenProvider.parseEmail(signUpRequest.signUpToken()); - AuthType authType = signUpTokenProvider.parseAuthType(signUpRequest.signUpToken()); - validateUserNotDuplicated(email, authType); - - // 사용자 저장 - SiteUser siteUser = siteUserRepository.save(signUpRequest.toSiteUser(email, authType)); +public class OAuthSignUpService extends SignUpService { - // 관심 지역, 국가 저장 - saveInterestedRegion(signUpRequest, siteUser); - saveInterestedCountry(signUpRequest, siteUser); + private final OAuthSignUpTokenProvider oAuthSignUpTokenProvider; - // 로그인 - return signInService.signIn(siteUser); + OAuthSignUpService(SignInService signInService, SiteUserRepository siteUserRepository, + RegionRepository regionRepository, InterestedRegionRepository interestedRegionRepository, + CountryRepository countryRepository, InterestedCountyRepository interestedCountyRepository, + OAuthSignUpTokenProvider oAuthSignUpTokenProvider) { + super(signInService, siteUserRepository, regionRepository, interestedRegionRepository, countryRepository, interestedCountyRepository); + this.oAuthSignUpTokenProvider = oAuthSignUpTokenProvider; } - private void validateNicknameDuplicated(String nickname) { - if (siteUserRepository.existsByNickname(nickname)) { - throw new CustomException(NICKNAME_ALREADY_EXISTED); - } + @Override + protected void validateSignUpToken(SignUpRequest signUpRequest) { + oAuthSignUpTokenProvider.validateSignUpToken(signUpRequest.signUpToken()); } - private void validateUserNotDuplicated(String email, AuthType authType) { + @Override + protected void validateUserNotDuplicated(SignUpRequest signUpRequest) { + String email = oAuthSignUpTokenProvider.parseEmail(signUpRequest.signUpToken()); + AuthType authType = oAuthSignUpTokenProvider.parseAuthType(signUpRequest.signUpToken()); if (siteUserRepository.existsByEmailAndAuthType(email, authType)) { throw new CustomException(USER_ALREADY_EXISTED); } } - private void saveInterestedRegion(SignUpRequest signUpRequest, SiteUser savedSiteUser) { - List interestedRegionNames = signUpRequest.interestedRegions(); - List interestedRegions = regionRepository.findByKoreanNames(interestedRegionNames).stream() - .map(region -> new InterestedRegion(savedSiteUser, region)) - .toList(); - interestedRegionRepository.saveAll(interestedRegions); - } - - private void saveInterestedCountry(SignUpRequest signUpRequest, SiteUser savedSiteUser) { - List interestedCountryNames = signUpRequest.interestedCountries(); - List interestedCountries = countryRepository.findByKoreanNames(interestedCountryNames).stream() - .map(country -> new InterestedCountry(savedSiteUser, country)) - .toList(); - interestedCountyRepository.saveAll(interestedCountries); + @Override + protected SiteUser createSiteUser(SignUpRequest signUpRequest) { + String email = oAuthSignUpTokenProvider.parseEmail(signUpRequest.signUpToken()); + AuthType authType = oAuthSignUpTokenProvider.parseAuthType(signUpRequest.signUpToken()); + return signUpRequest.toOAuthSiteUser(email, authType); } } From db7b56a7ec945eb8baad5e408400a24a2b5cedea Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Fri, 7 Feb 2025 21:32:45 +0900 Subject: [PATCH 06/11] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/dto/EmailSignInRequest.java | 13 ++++++ .../auth/service/EmailSignInService.java | 43 +++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java create mode 100644 src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java diff --git a/src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java b/src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java new file mode 100644 index 000000000..306a8185a --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/dto/EmailSignInRequest.java @@ -0,0 +1,13 @@ +package com.example.solidconnection.auth.dto; + +import jakarta.validation.constraints.NotBlank; + +public record EmailSignInRequest( + + @NotBlank(message = "이메일을 입력해주세요.") + String email, + + @NotBlank(message = "비밀번호를 입력해주세요.") + String password +) { +} diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java new file mode 100644 index 000000000..bbbb4f85c --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignInService.java @@ -0,0 +1,43 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.dto.EmailSignInRequest; +import com.example.solidconnection.auth.dto.SignInResponse; +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +import static com.example.solidconnection.custom.exception.ErrorCode.USER_NOT_FOUND; + +/* + * 보안을 위해 이메일과 비밀번호 중 무엇이 틀렸는지 구체적으로 응답하지 않는다. + * */ +@Service +@RequiredArgsConstructor +public class EmailSignInService { + + private final SignInService signInService; + private final SiteUserRepository siteUserRepository; + private final PasswordEncoder passwordEncoder; + + public SignInResponse signIn(EmailSignInRequest signInRequest) { + Optional optionalSiteUser = siteUserRepository.findByEmailAndAuthType(signInRequest.email(), AuthType.EMAIL); + if (optionalSiteUser.isPresent()) { + SiteUser siteUser = optionalSiteUser.get(); + validatePassword(signInRequest.password(), siteUser.getPassword()); + return signInService.signIn(siteUser); + } + throw new CustomException(USER_NOT_FOUND, "이메일과 비밀번호를 확인해주세요."); + } + + private void validatePassword(String rawPassword, String encodedPassword) throws CustomException { + if (!passwordEncoder.matches(rawPassword, encodedPassword)) { + throw new CustomException(USER_NOT_FOUND, "이메일과 비밀번호를 확인해주세요."); + } + } +} From d5ddc90425a46120faa4479b2122f7637d3a5d61 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Fri, 7 Feb 2025 21:33:01 +0900 Subject: [PATCH 07/11] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8,=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EC=97=94=ED=8A=B8=ED=8F=AC=EC=9D=B8=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index aa3ce4f20..934f0bd69 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -1,11 +1,14 @@ package com.example.solidconnection.auth.controller; +import com.example.solidconnection.auth.dto.EmailSignInRequest; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.dto.oauth.OAuthCodeRequest; import com.example.solidconnection.auth.dto.oauth.OAuthResponse; import com.example.solidconnection.auth.service.AuthService; +import com.example.solidconnection.auth.service.EmailSignInService; +import com.example.solidconnection.auth.service.EmailSignUpService; import com.example.solidconnection.auth.service.oauth.AppleOAuthService; import com.example.solidconnection.auth.service.oauth.KakaoOAuthService; import com.example.solidconnection.auth.service.oauth.OAuthSignUpService; @@ -31,6 +34,8 @@ public class AuthController { private final OAuthSignUpService oAuthSignUpService; private final AppleOAuthService appleOAuthService; private final KakaoOAuthService kakaoOAuthService; + private final EmailSignInService emailSignInService; + private final EmailSignUpService emailSignUpService; @PostMapping("/apple") public ResponseEntity processAppleOAuth( @@ -48,6 +53,22 @@ public ResponseEntity processKakaoOAuth( return ResponseEntity.ok(oAuthResponse); } + @PostMapping("/email/sign-in") + public ResponseEntity signInWithEmail( + @Valid @RequestBody EmailSignInRequest signInRequest + ) { + SignInResponse signInResponse = emailSignInService.signIn(signInRequest); + return ResponseEntity.ok(signInResponse); + } + + @PostMapping("/email/sign-up") + public ResponseEntity signUpWithEmail( + @Valid @RequestBody SignUpRequest signUpRequest + ) { + SignInResponse signInResponse = emailSignUpService.signUp(signUpRequest); + return ResponseEntity.ok(signInResponse); + } + @PostMapping("/sign-up") public ResponseEntity signUp( @Valid @RequestBody SignUpRequest signUpRequest From 16659050df434f81bbbd39d54a9e636e6a4bca7c Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Fri, 7 Feb 2025 22:15:55 +0900 Subject: [PATCH 08/11] =?UTF-8?q?refactor:=20=EC=9E=98=EB=AA=BB=20?= =?UTF-8?q?=EC=9D=B4=ED=95=B4=ED=95=B4=EC=84=9C=20=EB=8B=A4=EB=A5=B4?= =?UTF-8?q?=EA=B2=8C=20=EA=B5=AC=ED=98=84=ED=95=9C=20api=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/controller/AuthController.java | 21 ++++++++++++--- .../auth/dto/EmailSignUpTokenRequest.java | 13 +++++++++ .../auth/dto/EmailSignUpTokenResponse.java | 6 +++++ .../service/CommonSignUpTokenProvider.java | 27 +++++++++++++++++++ .../service/EmailSignUpTokenProvider.java | 14 +++++++--- .../siteuser/domain/AuthType.java | 4 +++ 6 files changed, 78 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java create mode 100644 src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenResponse.java create mode 100644 src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index 934f0bd69..599c5550c 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -1,20 +1,25 @@ package com.example.solidconnection.auth.controller; import com.example.solidconnection.auth.dto.EmailSignInRequest; +import com.example.solidconnection.auth.dto.EmailSignUpTokenRequest; +import com.example.solidconnection.auth.dto.EmailSignUpTokenResponse; import com.example.solidconnection.auth.dto.ReissueResponse; import com.example.solidconnection.auth.dto.SignInResponse; import com.example.solidconnection.auth.dto.SignUpRequest; import com.example.solidconnection.auth.dto.oauth.OAuthCodeRequest; import com.example.solidconnection.auth.dto.oauth.OAuthResponse; import com.example.solidconnection.auth.service.AuthService; +import com.example.solidconnection.auth.service.CommonSignUpTokenProvider; import com.example.solidconnection.auth.service.EmailSignInService; import com.example.solidconnection.auth.service.EmailSignUpService; +import com.example.solidconnection.auth.service.EmailSignUpTokenProvider; import com.example.solidconnection.auth.service.oauth.AppleOAuthService; import com.example.solidconnection.auth.service.oauth.KakaoOAuthService; import com.example.solidconnection.auth.service.oauth.OAuthSignUpService; import com.example.solidconnection.custom.resolver.AuthorizedUser; import com.example.solidconnection.custom.resolver.ExpiredToken; import com.example.solidconnection.custom.security.authentication.ExpiredTokenAuthentication; +import com.example.solidconnection.siteuser.domain.AuthType; import com.example.solidconnection.siteuser.domain.SiteUser; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -36,6 +41,8 @@ public class AuthController { private final KakaoOAuthService kakaoOAuthService; private final EmailSignInService emailSignInService; private final EmailSignUpService emailSignUpService; + private final EmailSignUpTokenProvider emailSignUpTokenProvider; + private final CommonSignUpTokenProvider commonSignUpTokenProvider; @PostMapping("/apple") public ResponseEntity processAppleOAuth( @@ -61,18 +68,24 @@ public ResponseEntity signInWithEmail( return ResponseEntity.ok(signInResponse); } + /* 이메일 회원가입 시 signUpToken 을 발급받기 위한 api */ @PostMapping("/email/sign-up") - public ResponseEntity signUpWithEmail( - @Valid @RequestBody SignUpRequest signUpRequest + public ResponseEntity signUpWithEmail( + @Valid @RequestBody EmailSignUpTokenRequest signUpRequest ) { - SignInResponse signInResponse = emailSignUpService.signUp(signUpRequest); - return ResponseEntity.ok(signInResponse); + String signUpToken = emailSignUpTokenProvider.generateAndSaveSignUpToken(signUpRequest); + return ResponseEntity.ok(new EmailSignUpTokenResponse(signUpToken)); } @PostMapping("/sign-up") public ResponseEntity signUp( @Valid @RequestBody SignUpRequest signUpRequest ) { + AuthType authType = commonSignUpTokenProvider.parseAuthType(signUpRequest.signUpToken()); + if (AuthType.isEmail(authType)) { + SignInResponse signInResponse = emailSignUpService.signUp(signUpRequest); + return ResponseEntity.ok(signInResponse); + } SignInResponse signInResponse = oAuthSignUpService.signUp(signUpRequest); return ResponseEntity.ok(signInResponse); } diff --git a/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java new file mode 100644 index 000000000..c24cb15f3 --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java @@ -0,0 +1,13 @@ +package com.example.solidconnection.auth.dto; + +import jakarta.validation.constraints.NotBlank; + +public record EmailSignUpTokenRequest( + + @NotBlank(message = "이메일을 입력해주세요.") + String email, + + @NotBlank(message = "비밀번호를 입력해주세요.") + String password +) { +} diff --git a/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenResponse.java b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenResponse.java new file mode 100644 index 000000000..c8e983d0c --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenResponse.java @@ -0,0 +1,6 @@ +package com.example.solidconnection.auth.dto; + +public record EmailSignUpTokenResponse( + String signUpToken +) { +} diff --git a/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java new file mode 100644 index 000000000..3d0eda53b --- /dev/null +++ b/src/main/java/com/example/solidconnection/auth/service/CommonSignUpTokenProvider.java @@ -0,0 +1,27 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.config.security.JwtProperties; +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.util.JwtUtils; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +import static com.example.solidconnection.auth.service.EmailSignUpTokenProvider.AUTH_TYPE_CLAIM_KEY; +import static com.example.solidconnection.custom.exception.ErrorCode.SIGN_UP_TOKEN_INVALID; + +@Component +@RequiredArgsConstructor +public class CommonSignUpTokenProvider { + + private final JwtProperties jwtProperties; + + public AuthType parseAuthType(String signUpToken) { + try { + String authTypeStr = JwtUtils.parseClaims(signUpToken, jwtProperties.secret()).get(AUTH_TYPE_CLAIM_KEY, String.class); + return AuthType.valueOf(authTypeStr); + } catch (Exception e) { + throw new CustomException(SIGN_UP_TOKEN_INVALID); + } + } +} diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java index 73a376fe2..1c27a87bd 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpTokenProvider.java @@ -1,8 +1,10 @@ package com.example.solidconnection.auth.service; import com.example.solidconnection.auth.domain.TokenType; +import com.example.solidconnection.auth.dto.EmailSignUpTokenRequest; import com.example.solidconnection.config.security.JwtProperties; import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.siteuser.domain.AuthType; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; @@ -24,6 +26,7 @@ public class EmailSignUpTokenProvider extends TokenProvider { static final String PASSWORD_CLAIM_KEY = "password"; + static final String AUTH_TYPE_CLAIM_KEY = "authType"; private final PasswordEncoder passwordEncoder; @@ -33,10 +36,15 @@ public EmailSignUpTokenProvider(JwtProperties jwtProperties, RedisTemplate passwordClaims = new HashMap<>(Map.of(PASSWORD_CLAIM_KEY, encodedPassword)); - Claims claims = Jwts.claims(passwordClaims).setSubject(email); + Map emailSignUpClaims = new HashMap<>(Map.of( + PASSWORD_CLAIM_KEY, encodedPassword, + AUTH_TYPE_CLAIM_KEY, AuthType.EMAIL + )); + Claims claims = Jwts.claims(emailSignUpClaims).setSubject(email); Date now = new Date(); Date expiredDate = new Date(now.getTime() + TokenType.SIGN_UP.getExpireTime()); diff --git a/src/main/java/com/example/solidconnection/siteuser/domain/AuthType.java b/src/main/java/com/example/solidconnection/siteuser/domain/AuthType.java index d9d0b582c..d13462298 100644 --- a/src/main/java/com/example/solidconnection/siteuser/domain/AuthType.java +++ b/src/main/java/com/example/solidconnection/siteuser/domain/AuthType.java @@ -6,4 +6,8 @@ public enum AuthType { APPLE, EMAIL, ; + + public static boolean isEmail(AuthType authType) { + return EMAIL.equals(authType); + } } From f247820ab155b9494ecac024c485035275f7f2ca Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Fri, 7 Feb 2025 18:35:23 +0900 Subject: [PATCH 09/11] =?UTF-8?q?test:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth/service/EmailSignInServiceTest.java | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java diff --git a/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java new file mode 100644 index 000000000..e9663f5df --- /dev/null +++ b/src/test/java/com/example/solidconnection/auth/service/EmailSignInServiceTest.java @@ -0,0 +1,99 @@ +package com.example.solidconnection.auth.service; + +import com.example.solidconnection.auth.dto.EmailSignInRequest; +import com.example.solidconnection.auth.dto.SignInResponse; +import com.example.solidconnection.custom.exception.CustomException; +import com.example.solidconnection.custom.exception.ErrorCode; +import com.example.solidconnection.siteuser.domain.AuthType; +import com.example.solidconnection.siteuser.domain.SiteUser; +import com.example.solidconnection.siteuser.repository.SiteUserRepository; +import com.example.solidconnection.support.TestContainerSpringBootTest; +import com.example.solidconnection.type.Gender; +import com.example.solidconnection.type.PreparationStatus; +import com.example.solidconnection.type.Role; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.password.PasswordEncoder; + +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.junit.jupiter.api.Assertions.assertAll; + +@DisplayName("이메일 로그인 서비스 테스트") +@TestContainerSpringBootTest +class EmailSignInServiceTest { + + @Autowired + private EmailSignInService emailSignInService; + + @Autowired + private SiteUserRepository siteUserRepository; + + @Autowired + private PasswordEncoder passwordEncoder; + + @Test + void 로그인에_성공한다() { + // given + String email = "testEmail"; + String rawPassword = "testPassword"; + SiteUser siteUser = createSiteUser(email, rawPassword); + siteUserRepository.save(siteUser); + EmailSignInRequest signInRequest = new EmailSignInRequest(siteUser.getEmail(), rawPassword); + + // when + SignInResponse signInResponse = emailSignInService.signIn(signInRequest); + + // then + assertAll( + () -> Assertions.assertThat(signInResponse.accessToken()).isNotNull(), + () -> Assertions.assertThat(signInResponse.refreshToken()).isNotNull() + ); + } + + @Nested + class 로그인에_실패한다 { + + @Test + void 이메일과_일치하는_사용자가_없으면_예외_응답을_반환한다() { + // given + EmailSignInRequest signInRequest = new EmailSignInRequest("이메일", "비밀번호"); + + // when & then + assertThatCode(() -> emailSignInService.signIn(signInRequest)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.USER_NOT_FOUND.getMessage()); + } + + @Test + void 비밀번호가_일치하지_않으면_예외_응답을_반환한다() { + // given + String email = "testEmail"; + SiteUser siteUser = createSiteUser(email, "testPassword"); + siteUserRepository.save(siteUser); + EmailSignInRequest signInRequest = new EmailSignInRequest(email, "틀린비밀번호"); + + // when & then + assertThatCode(() -> emailSignInService.signIn(signInRequest)) + .isInstanceOf(CustomException.class) + .hasMessageContaining(ErrorCode.USER_NOT_FOUND.getMessage()); + } + } + + private SiteUser createSiteUser(String email, String rawPassword) { + String encodedPassword = passwordEncoder.encode(rawPassword); + return new SiteUser( + email, + "nickname", + "profileImageUrl", + "1999-01-01", + PreparationStatus.CONSIDERING, + Role.MENTEE, + Gender.MALE, + AuthType.EMAIL, + encodedPassword + ); + } +} From 36575a35cb52b4992cc9b2711fd7c563e6171c64 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sat, 8 Feb 2025 15:47:32 +0900 Subject: [PATCH 10/11] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=ED=98=95=EC=8B=9D=EC=9C=BC=EB=A1=9C=20=EA=B2=80=EC=A6=9D?= =?UTF-8?q?=ED=95=98=EB=85=B8=EB=A1=9D=20dto=20=EC=96=B4=EB=85=B8=ED=85=8C?= =?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/auth/dto/EmailSignUpTokenRequest.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java index c24cb15f3..92073b434 100644 --- a/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java +++ b/src/main/java/com/example/solidconnection/auth/dto/EmailSignUpTokenRequest.java @@ -1,10 +1,11 @@ package com.example.solidconnection.auth.dto; +import jakarta.validation.constraints.Email; import jakarta.validation.constraints.NotBlank; public record EmailSignUpTokenRequest( - @NotBlank(message = "이메일을 입력해주세요.") + @Email(message = "이메일을 입력해주세요.") String email, @NotBlank(message = "비밀번호를 입력해주세요.") From 8e800b9036107df2cc6f27cd0288af5ba1b087f2 Mon Sep 17 00:00:00 2001 From: nayonsoso Date: Sat, 8 Feb 2025 15:51:15 +0900 Subject: [PATCH 11/11] =?UTF-8?q?refactor:=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=EC=A4=91=EB=B3=B5=20=EA=B2=80=EC=A6=9D=EC=9D=B4=20=EC=95=9E?= =?UTF-8?q?=EC=97=90=20=EC=9C=84=EC=B9=98=ED=95=98=EB=8F=84=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../solidconnection/auth/controller/AuthController.java | 1 + .../solidconnection/auth/service/EmailSignUpService.java | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java index 599c5550c..80520942f 100644 --- a/src/main/java/com/example/solidconnection/auth/controller/AuthController.java +++ b/src/main/java/com/example/solidconnection/auth/controller/AuthController.java @@ -73,6 +73,7 @@ public ResponseEntity signInWithEmail( public ResponseEntity signUpWithEmail( @Valid @RequestBody EmailSignUpTokenRequest signUpRequest ) { + emailSignUpService.validateUniqueEmail(signUpRequest.email()); String signUpToken = emailSignUpTokenProvider.generateAndSaveSignUpToken(signUpRequest); return ResponseEntity.ok(new EmailSignUpTokenResponse(signUpToken)); } diff --git a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java index 2678e3880..37f6681ea 100644 --- a/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java +++ b/src/main/java/com/example/solidconnection/auth/service/EmailSignUpService.java @@ -26,6 +26,12 @@ public EmailSignUpService(SignInService signInService, SiteUserRepository siteUs this.emailSignUpTokenProvider = emailSignUpTokenProvider; } + public void validateUniqueEmail(String email) { + if (siteUserRepository.existsByEmailAndAuthType(email, AuthType.EMAIL)) { + throw new CustomException(USER_ALREADY_EXISTED); + } + } + @Override protected void validateSignUpToken(SignUpRequest signUpRequest) { emailSignUpTokenProvider.validateSignUpToken(signUpRequest.signUpToken());