diff --git a/backend/core/build.gradle b/backend/core/build.gradle index d65dfbe74..4163b4e3b 100644 --- a/backend/core/build.gradle +++ b/backend/core/build.gradle @@ -69,6 +69,9 @@ dependencies { annotationProcessor 'jakarta.annotation:jakarta.annotation-api' annotationProcessor 'jakarta.persistence:jakarta.persistence-api' + // nanoid + implementation 'com.aventrix.jnanoid:jnanoid:2.0.0' + //swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0' diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java index ce89ea1c5..74b699774 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApi.java @@ -23,65 +23,6 @@ import site.timecapsulearchive.core.global.error.ErrorResponse; public interface AuthApi { - - @Operation( - summary = "카카오 로그인 페이지", - description = """ - oauth2 kakao 인증 페이지 url을 반환한다. - """, - tags = {"oauth2"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "ok" - ) - }) - ResponseEntity getOAuth2KakaoUrl(HttpServletRequest request); - - - @Operation( - summary = "구글 로그인 페이지", - description = """ - oauth2 google 인증 페이지 url을 반환한다. - """, - tags = {"oauth2"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "ok" - ) - }) - ResponseEntity getOAuth2GoogleUrl(HttpServletRequest request); - - @Operation( - summary = "카카오 인증 성공시 임시 인증 토큰 발급", - description = "oauth2 kakao 인증 성공시 임시 인증 토큰을 발급한다. (oauth2 로그인 성공시 리다이렉트 엔드포인트로 문서화 목적) ", - tags = {"oauth2"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "ok" - ) - }) - ResponseEntity getTemporaryTokenByKakao(); - - - @Operation( - summary = "구글 인증 성공시 임시 인증 토큰 발급", - description = "oauth2 google 인증 성공시 임시 인증 토큰을 발급한다. (oauth2 로그인 성공시 리다이렉트 엔드포인트로 문서화 목적) ", - tags = {"oauth2"} - ) - @ApiResponses(value = { - @ApiResponse( - responseCode = "200", - description = "ok" - ) - }) - ResponseEntity getTemporaryTokenByGoogle(); - @Operation( summary = "다른 소셜 프로바이더의 앱으로 인증한 클라이언트 아이디로 회원가입", description = """ diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java index a88287550..f9efe3e89 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/api/AuthApiController.java @@ -1,11 +1,9 @@ package site.timecapsulearchive.core.domain.auth.api; -import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; -import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -19,7 +17,6 @@ import site.timecapsulearchive.core.domain.auth.data.request.TokenReIssueRequest; import site.timecapsulearchive.core.domain.auth.data.request.VerificationMessageSendRequest; import site.timecapsulearchive.core.domain.auth.data.request.VerificationNumberValidRequest; -import site.timecapsulearchive.core.domain.auth.data.response.OAuth2UriResponse; import site.timecapsulearchive.core.domain.auth.data.response.TemporaryTokenResponse; import site.timecapsulearchive.core.domain.auth.data.response.TokenResponse; import site.timecapsulearchive.core.domain.auth.data.response.VerificationMessageSendResponse; @@ -35,34 +32,6 @@ public class AuthApiController implements AuthApi { private final AuthManager authManager; - @GetMapping(value = "/login/url/kakao", produces = {"application/json"}) - @Override - public ResponseEntity getOAuth2KakaoUrl(final HttpServletRequest request) { - final String kakaoLoginUrl = authManager.getOAuth2KakaoUrl(request); - - return ResponseEntity.ok(OAuth2UriResponse.from(kakaoLoginUrl)); - } - - @GetMapping(value = "/login/url/google", produces = {"application/json"}) - @Override - public ResponseEntity getOAuth2GoogleUrl(final HttpServletRequest request) { - final String googleLoginUrl = authManager.getOauth2GoogleUrl(request); - - return ResponseEntity.ok(OAuth2UriResponse.from(googleLoginUrl)); - } - - @GetMapping(value = "/login/oauth2/code/kakao", produces = {"application/json"}) - @Override - public ResponseEntity getTemporaryTokenByKakao() { - throw new UnsupportedOperationException(); - } - - @GetMapping(value = "/login/oauth2/code/google", produces = {"application/json"}) - @Override - public ResponseEntity getTemporaryTokenByGoogle() { - throw new UnsupportedOperationException(); - } - @PostMapping( value = "/temporary-token/re-issue", produces = {"application/json"}, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java index 5f389aa8a..262ab3d93 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/auth/service/AuthManager.java @@ -1,6 +1,5 @@ package site.timecapsulearchive.core.domain.auth.service; -import jakarta.servlet.http.HttpServletRequest; import java.nio.charset.StandardCharsets; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; @@ -15,31 +14,10 @@ @RequiredArgsConstructor public class AuthManager { - private static final String KAKAO_AUTHORIZATION_ENDPOINT = "/auth/login/kakao"; - private static final String GOOGLE_AUTHORIZATION_ENDPOINT = "/auth/login/google"; - private final TokenManager tokenManager; private final MemberService memberService; private final MessageVerificationService messageVerificationService; - public String getOAuth2KakaoUrl(final HttpServletRequest request) { - final String baseUrl = request.getRequestURL().toString(); - - return baseUrl.replace( - request.getRequestURI(), - request.getContextPath() + KAKAO_AUTHORIZATION_ENDPOINT - ); - } - - public String getOauth2GoogleUrl(final HttpServletRequest request) { - final String baseUrl = request.getRequestURL().toString(); - - return baseUrl.replace( - request.getRequestURI(), - request.getContextPath() + GOOGLE_AUTHORIZATION_ENDPOINT - ); - } - public TemporaryTokenDto reIssueTemporaryToken(final String authId, final SocialType socialType) { final Long notVerifiedMemberId = memberService.findNotVerifiedMemberIdBy(authId, @@ -78,12 +56,12 @@ public TokenDto validVerificationMessage( final String certificationNumber, final String receiver ) { - final byte[] plain = receiver.getBytes(StandardCharsets.UTF_8); + final byte[] phoneBytes = receiver.getBytes(StandardCharsets.UTF_8); messageVerificationService.validVerificationMessage(memberId, - certificationNumber, plain); + certificationNumber, phoneBytes); - Long verifiedMemberId = memberService.updateVerifiedMember(memberId, plain); + Long verifiedMemberId = memberService.updateVerifiedMember(memberId, phoneBytes); return tokenManager.createNewToken(verifiedMemberId); } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/MemberDetailDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/MemberDetailDto.java index 2a4f62424..62e1c715d 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/MemberDetailDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/MemberDetailDto.java @@ -10,7 +10,7 @@ public record MemberDetailDto( String profileUrl, String tag, SocialType socialType, - String email, + ByteArrayWrapper email, ByteArrayWrapper phone, Long friendCount, Long groupCount, diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/SignUpRequestDto.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/SignUpRequestDto.java index bf7969ca9..cde15aeec 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/SignUpRequestDto.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/dto/SignUpRequestDto.java @@ -11,15 +11,16 @@ public record SignUpRequestDto( SocialType socialType ) { - public MemberTemporary toMemberTemporary(final String tag) { + public MemberTemporary toMemberTemporary(final String tag, final byte[] email, + final byte[] emailHash) { return MemberTemporary.builder() .authId(authId) .nickname(MakeRandomNickNameUtil.makeRandomNickName()) - .email(email) .profileUrl(profileUrl) .socialType(socialType) .tag(tag) + .email(email) + .emailHash(emailHash) .build(); } - } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/response/MemberDetailResponse.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/response/MemberDetailResponse.java index 5a01e1bd7..df0f109b0 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/response/MemberDetailResponse.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/data/response/MemberDetailResponse.java @@ -41,15 +41,15 @@ public record MemberDetailResponse( public static MemberDetailResponse createOf( final MemberDetailDto detailDto, - final Function phoneDecryption + final Function aesEncryptionManager ) { return new MemberDetailResponse( detailDto.nickname(), detailDto.profileUrl(), detailDto.tag(), detailDto.socialType(), - detailDto.email(), - phoneDecryption.apply(detailDto.phone().data()), + aesEncryptionManager.apply(detailDto.email().data()), + aesEncryptionManager.apply(detailDto.phone().data()), detailDto.friendCount(), detailDto.groupCount(), detailDto.tagSearchAvailable(), diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/Member.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/Member.java index 39067e754..d32950c2e 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/Member.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/Member.java @@ -8,7 +8,6 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; -import jakarta.validation.constraints.Email; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -17,7 +16,6 @@ import org.hibernate.annotations.Where; import site.timecapsulearchive.core.global.entity.BaseEntity; import site.timecapsulearchive.core.global.util.NullCheck; -import site.timecapsulearchive.core.global.util.TagGenerator; @Entity @Table(name = "member") @@ -51,9 +49,11 @@ public class Member extends BaseEntity { @Column(name = "notification_enabled", nullable = false) private Boolean notificationEnabled; - @Email @Column(name = "email", nullable = false) - private String email; + private byte[] email; + + @Column(name = "email_hash", nullable = false) + private byte[] emailHash; @Column(name = "fcm_token") private String fcmToken; @@ -80,12 +80,13 @@ public class Member extends BaseEntity { private Boolean phoneSearchAvailable = Boolean.FALSE; @Builder - private Member(String profileUrl, String nickname, SocialType socialType, String email, - String authId, String password, String tag, byte[] phone, byte[] phoneHash) { + private Member(String profileUrl, String nickname, SocialType socialType, + String authId, String password, String tag, byte[] phone, byte[] phoneHash, + byte[] email, byte[] emailHash + ) { this.profileUrl = NullCheck.validate(profileUrl, "Entity: profile"); this.nickname = NullCheck.validate(nickname, "Entity: nickname"); this.socialType = NullCheck.validate(socialType, "Entity: socialType"); - this.email = NullCheck.validate(email, "Entity: email"); this.tag = NullCheck.validate(tag, "Entity: tag"); this.authId = NullCheck.validate(authId, "Entity: authId"); this.isVerified = true; @@ -93,10 +94,8 @@ private Member(String profileUrl, String nickname, SocialType socialType, String this.password = password; this.phone = phone; this.phoneHash = phoneHash; - } - - public void updateTagLowerCaseSocialType() { - this.tag = TagGenerator.lowercase(email, socialType); + this.email = email; + this.emailHash = emailHash; } public void updateData(String nickname, String tag) { diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/MemberTemporary.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/MemberTemporary.java index eb1bc73fd..b3058d4f0 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/MemberTemporary.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/entity/MemberTemporary.java @@ -8,7 +8,6 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Table; -import jakarta.validation.constraints.Email; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -17,7 +16,6 @@ import org.hibernate.annotations.Where; import site.timecapsulearchive.core.global.entity.BaseEntity; import site.timecapsulearchive.core.global.util.NullCheck; -import site.timecapsulearchive.core.global.util.TagGenerator; @Entity @Table(name = "member_temporary") @@ -42,10 +40,6 @@ public class MemberTemporary extends BaseEntity { @Enumerated(EnumType.STRING) private SocialType socialType; - @Email - @Column(name = "email", nullable = false) - private String email; - @Column(name = "is_verified", nullable = false) private Boolean isVerified; @@ -55,13 +49,20 @@ public class MemberTemporary extends BaseEntity { @Column(name = "tag", nullable = false, unique = true) private String tag; + @Column(name = "email", nullable = false) + private byte[] email; + + @Column(name = "email_hash", nullable = false) + private byte[] emailHash; + @Builder - public MemberTemporary(String profileUrl, String nickname, SocialType socialType, String email, - String authId, String tag) { + public MemberTemporary(String profileUrl, String nickname, SocialType socialType, + String authId, String tag, byte[] email, byte[] emailHash) { this.profileUrl = NullCheck.validate(profileUrl, "Entity: profile"); this.nickname = NullCheck.validate(nickname, "Entity: nickname"); this.socialType = NullCheck.validate(socialType, "Entity: socialType"); - this.email = NullCheck.validate(email, "Entity: email"); + this.email = email; + this.emailHash = emailHash; this.isVerified = false; this.authId = NullCheck.validate(authId, "Entity: authId"); this.tag = NullCheck.validate(tag, "Entity: tag"); @@ -72,15 +73,12 @@ public Member toMember(final byte[] phoneHash, final byte[] phone) { .profileUrl(profileUrl) .nickname(nickname) .socialType(socialType) - .email(email) .authId(authId) .tag(tag) .phoneHash(phoneHash) .phone(phone) + .emailHash(emailHash) + .email(email) .build(); } - - public void updateTagLowerCaseSocialType() { - this.tag = TagGenerator.lowercase(email, socialType); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java index 26d3e3699..9bbe263a8 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryImpl.java @@ -77,7 +77,10 @@ public Optional findMemberDetailResponseDtoById(final Long memb member.profileUrl, member.tag, member.socialType, - member.email, + Projections.constructor( + ByteArrayWrapper.class, + member.email + ), Projections.constructor( ByteArrayWrapper.class, member.phone diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java index 3b355b1e6..9c1617c73 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/domain/member/service/MemberService.java @@ -1,5 +1,7 @@ package site.timecapsulearchive.core.domain.member.service; +import com.aventrix.jnanoid.jnanoid.NanoIdUtils; +import java.nio.charset.StandardCharsets; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.dao.DataIntegrityViolationException; @@ -22,7 +24,6 @@ import site.timecapsulearchive.core.domain.member.repository.MemberTemporaryRepository; import site.timecapsulearchive.core.global.security.encryption.AESEncryptionManager; import site.timecapsulearchive.core.global.security.encryption.HashEncryptionManager; -import site.timecapsulearchive.core.global.util.TagGenerator; @Slf4j @Service @@ -37,8 +38,13 @@ public class MemberService { @Transactional public Long createMember(final SignUpRequestDto dto) { - final String tag = TagGenerator.generate(dto.email(), dto.socialType()); - final MemberTemporary member = dto.toMemberTemporary(tag); + final String tag = NanoIdUtils.randomNanoId(); + final byte[] emailBytes = dto.email().getBytes(StandardCharsets.UTF_8); + final MemberTemporary member = dto.toMemberTemporary( + tag, + aesEncryptionManager.encryptWithPrefixIV(emailBytes), + hashEncryptionManager.encrypt(emailBytes) + ); final MemberTemporary savedMember = memberTemporaryRepository.save(member); @@ -56,8 +62,7 @@ public MemberStatusDto checkStatus( final String authId, final SocialType socialType ) { - return memberRepository.findIsVerifiedByAuthIdAndSocialType(authId, socialType - ); + return memberRepository.findIsVerifiedByAuthIdAndSocialType(authId, socialType); } /** @@ -159,7 +164,6 @@ public void updateMemberData( } catch (DataIntegrityViolationException e) { throw new MemberTagDuplicatedException(); } - } @Transactional @@ -199,26 +203,19 @@ public void declarationMember(final Long targetId) { .orElseThrow(MemberNotFoundException::new); member.upDeclarationCount(); - } @Transactional - public Long updateVerifiedMember(final Long memberId, final byte[] plain) { + public Long updateVerifiedMember(final Long memberId, final byte[] phoneBytes) { final MemberTemporary memberTemporary = memberTemporaryRepository.findById(memberId) .orElseThrow(MemberNotFoundException::new); memberTemporaryRepository.delete(memberTemporary); - boolean isDuplicateTag = memberRepository.checkTagDuplication(memberTemporary.getTag()); - if (isDuplicateTag) { - log.warn("member tag duplicate - email:{}, tag:{}", memberTemporary.getEmail(), - memberTemporary.getTag()); - memberTemporary.updateTagLowerCaseSocialType(); - log.warn("member tag update - tag: {}", memberTemporary.getTag()); - } - - final Member verifiedMember = memberTemporary.toMember(hashEncryptionManager.encrypt(plain), - aesEncryptionManager.encryptWithPrefixIV(plain)); + final Member verifiedMember = memberTemporary.toMember( + hashEncryptionManager.encrypt(phoneBytes), + aesEncryptionManager.encryptWithPrefixIV(phoneBytes) + ); memberRepository.save(verifiedMember); diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/JwtDsl.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/JwtDsl.java index ab74fd590..284a19081 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/JwtDsl.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/JwtDsl.java @@ -1,51 +1,51 @@ -package site.timecapsulearchive.core.global.config.security; - -import com.fasterxml.jackson.databind.ObjectMapper; -import lombok.RequiredArgsConstructor; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.security.web.util.matcher.RequestMatcher; -import site.timecapsulearchive.core.global.security.jwt.JwtAuthenticationFilter; - -@RequiredArgsConstructor -public class JwtDsl extends AbstractHttpConfigurer { - - private final AuthenticationProvider jwtAuthenticationProvider; - private final ObjectMapper objectMapper; - private final RequestMatcher notRequireAuthenticationMatcher; - - public static JwtDsl jwtDsl( - final AuthenticationProvider authenticationProvider, - final ObjectMapper objectMapper, - final RequestMatcher requestMatcher - ) { - return new JwtDsl( - authenticationProvider, - objectMapper, - requestMatcher - ); - } - - @Override - public void configure(HttpSecurity http) { - http - .authenticationProvider(jwtAuthenticationProvider) - .addFilterBefore( - jwtAuthenticationFilter(http.getSharedObject(AuthenticationManager.class)), - UsernamePasswordAuthenticationFilter.class - ); - } - - private JwtAuthenticationFilter jwtAuthenticationFilter( - final AuthenticationManager authenticationManager - ) { - return new JwtAuthenticationFilter( - authenticationManager, - objectMapper, - notRequireAuthenticationMatcher - ); - } -} +package site.timecapsulearchive.core.global.config.security; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.RequestMatcher; +import site.timecapsulearchive.core.global.security.jwt.JwtAuthenticationFilter; + +@RequiredArgsConstructor +public class JwtDsl extends AbstractHttpConfigurer { + + private final AuthenticationProvider jwtAuthenticationProvider; + private final ObjectMapper objectMapper; + private final RequestMatcher notRequireAuthenticationMatcher; + + public static JwtDsl jwtDsl( + final AuthenticationProvider authenticationProvider, + final ObjectMapper objectMapper, + final RequestMatcher requestMatcher + ) { + return new JwtDsl( + authenticationProvider, + objectMapper, + requestMatcher + ); + } + + @Override + public void configure(HttpSecurity http) { + http + .authenticationProvider(jwtAuthenticationProvider) + .addFilterBefore( + jwtAuthenticationFilter(http.getSharedObject(AuthenticationManager.class)), + UsernamePasswordAuthenticationFilter.class + ); + } + + private JwtAuthenticationFilter jwtAuthenticationFilter( + final AuthenticationManager authenticationManager + ) { + return new JwtAuthenticationFilter( + authenticationManager, + objectMapper, + notRequireAuthenticationMatcher + ); + } +} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/OAuthDsl.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/OAuthDsl.java deleted file mode 100644 index ba6544858..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/OAuthDsl.java +++ /dev/null @@ -1,49 +0,0 @@ -package site.timecapsulearchive.core.global.config.security; - -import lombok.RequiredArgsConstructor; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; - -@RequiredArgsConstructor -public class OAuthDsl extends AbstractHttpConfigurer { - - private final OAuth2UserService customOauth2UserService; - private final AuthenticationSuccessHandler oAuth2LoginSuccessHandler; - private final AuthenticationFailureHandler oauth2LoginFailureHandler; - - public static OAuthDsl oauthDsl( - final OAuth2UserService customOauth2UserService, - final AuthenticationSuccessHandler oAuth2LoginSuccessHandler, - final AuthenticationFailureHandler oauth2LoginFailureHandler - ) { - return new OAuthDsl( - customOauth2UserService, - oAuth2LoginSuccessHandler, - oauth2LoginFailureHandler - ); - } - - @Override - public void init(final HttpSecurity http) throws Exception { - http.oauth2Login(oauth2login -> oauth2login - .userInfoEndpoint( - userInfoEndpointConfig -> userInfoEndpointConfig.userService( - customOauth2UserService) - ) - .authorizationEndpoint(authorization -> authorization - .baseUri("/auth/login/") - ) - .redirectionEndpoint(redirection -> redirection - .baseUri("/auth/login/oauth2/code/*") - ) - .successHandler(oAuth2LoginSuccessHandler) - .failureHandler(oauth2LoginFailureHandler) - ); - } - -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java index 78588a7b9..9fd7a8826 100644 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java +++ b/backend/core/src/main/java/site/timecapsulearchive/core/global/config/security/SecurityConfig.java @@ -13,13 +13,8 @@ import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.access.AccessDeniedHandler; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; import org.springframework.security.web.util.matcher.NegatedRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatchers; @@ -30,13 +25,8 @@ @RequiredArgsConstructor public class SecurityConfig { - private final OAuth2UserService customOauth2UserService; - private final AuthenticationSuccessHandler oAuth2LoginSuccessHandler; - - private final AuthenticationFailureHandler oauth2LoginFailureHandler; private final AuthenticationProvider jwtAuthenticationProvider; private final ObjectMapper objectMapper; - private final AccessDeniedHandler accessDeniedHandler; @Bean @@ -45,7 +35,6 @@ public PasswordEncoder getPasswordEncoder() { } @Bean - @Order(1) public SecurityFilterChain filterChainWithJwt(final HttpSecurity http) throws Exception { http.apply( CommonSecurityDsl.commonSecurityDsl() @@ -93,30 +82,6 @@ private RequestMatcher notRequireAuthenticationMatcher() { antMatcher(HttpMethod.GET, "/actuator/**") ); } - - @Bean - @Order(2) - public SecurityFilterChain filterChainWithOAuth(final HttpSecurity http) throws Exception { - http.apply( - CommonSecurityDsl.commonSecurityDsl() - ); - - http - .securityMatcher("/auth/login/**") - .authorizeHttpRequests(auth -> auth - .anyRequest().permitAll() - ); - - http.apply( - OAuthDsl.oauthDsl( - customOauth2UserService, - oAuth2LoginSuccessHandler, - oauth2LoginFailureHandler - ) - ); - - return http.build(); - } } diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/CustomOAuth2User.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/CustomOAuth2User.java deleted file mode 100644 index 5d8715036..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/CustomOAuth2User.java +++ /dev/null @@ -1,35 +0,0 @@ -package site.timecapsulearchive.core.global.security.oauth.dto; - -import java.util.Collection; -import java.util.Map; -import org.springframework.security.core.GrantedAuthority; -import org.springframework.security.oauth2.core.user.DefaultOAuth2User; - -public class CustomOAuth2User extends DefaultOAuth2User { - - private final boolean isVerified; - private final String email; - private final Long id; - - public CustomOAuth2User( - Collection authorities, - Map attributes, - String nameAttributeKey, - String email, - boolean isVerified, - Long id - ) { - super(authorities, attributes, nameAttributeKey); - this.email = email; - this.isVerified = isVerified; - this.id = id; - } - - public boolean isNotVerified() { - return !isVerified; - } - - public Long getId() { - return id; - } -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/OAuth2UserInfo.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/OAuth2UserInfo.java deleted file mode 100644 index 92bfc96e3..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/OAuth2UserInfo.java +++ /dev/null @@ -1,16 +0,0 @@ -package site.timecapsulearchive.core.global.security.oauth.dto; - -import java.util.Map; - -public abstract class OAuth2UserInfo { - - protected Map attributes; - - protected OAuth2UserInfo(Map attributes) { - this.attributes = attributes; - } - - public abstract String getEmail(); - - public abstract String getImageUrl(); -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/OAuthAttributes.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/OAuthAttributes.java deleted file mode 100644 index 2b80cef91..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/OAuthAttributes.java +++ /dev/null @@ -1,72 +0,0 @@ -package site.timecapsulearchive.core.global.security.oauth.dto; - -import java.util.Map; -import lombok.Builder; -import lombok.Getter; -import site.timecapsulearchive.core.domain.member.entity.Member; -import site.timecapsulearchive.core.domain.member.entity.SocialType; -import site.timecapsulearchive.core.global.security.oauth.dto.google.GoogleOAuth2UserInfo; -import site.timecapsulearchive.core.global.security.oauth.dto.kakao.KakaoOAuth2UserInfo; -import site.timecapsulearchive.core.global.util.TagGenerator; -import site.timecapsulearchive.core.global.util.nickname.MakeRandomNickNameUtil; - - -@Getter -public class OAuthAttributes { - - private final String authId; - private final OAuth2UserInfo OAuth2UserInfo; - - @Builder - private OAuthAttributes(String authId, OAuth2UserInfo OAuth2UserInfo) { - this.authId = authId; - this.OAuth2UserInfo = OAuth2UserInfo; - } - - public static OAuthAttributes of( - final SocialType socialType, - final String userNameAttributeName, - final Map attributes - ) { - if (socialType == SocialType.KAKAO) { - return ofKakao(userNameAttributeName, attributes); - } - - return ofGoogle(userNameAttributeName, attributes); - } - - private static OAuthAttributes ofKakao( - final String userNameAttributeName, - final Map attributes - ) { - final Long authId = (Long) attributes.get(userNameAttributeName); - - return OAuthAttributes.builder() - .authId(String.valueOf(authId)) - .OAuth2UserInfo(new KakaoOAuth2UserInfo(attributes)) - .build(); - } - - public static OAuthAttributes ofGoogle( - final String userNameAttributeName, - final Map attributes - ) { - return OAuthAttributes.builder() - .authId((String) attributes.get(userNameAttributeName)) - .OAuth2UserInfo(new GoogleOAuth2UserInfo(attributes)) - .build(); - } - - public Member OAuthToMember( - final SocialType socialType - ) { - return Member.builder() - .authId(authId) - .nickname(MakeRandomNickNameUtil.makeRandomNickName()) - .email(OAuth2UserInfo.getEmail()) - .profileUrl(OAuth2UserInfo.getImageUrl()) - .socialType(socialType) - .tag(TagGenerator.generate(OAuth2UserInfo.getEmail(), socialType)) - .build(); - } -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/google/GoogleOAuth2UserInfo.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/google/GoogleOAuth2UserInfo.java deleted file mode 100644 index 46c545701..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/google/GoogleOAuth2UserInfo.java +++ /dev/null @@ -1,24 +0,0 @@ -package site.timecapsulearchive.core.global.security.oauth.dto.google; - -import java.util.Map; -import site.timecapsulearchive.core.global.security.oauth.dto.OAuth2UserInfo; - -public class GoogleOAuth2UserInfo extends OAuth2UserInfo { - - private static final String EMAIL = "email"; - private static final String PICTURE = "picture"; - - public GoogleOAuth2UserInfo(Map attributes) { - super(attributes); - } - - @Override - public String getEmail() { - return (String) attributes.get(EMAIL); - } - - @Override - public String getImageUrl() { - return (String) attributes.get(PICTURE); - } -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/kakao/KakaoOAuth2UserInfo.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/kakao/KakaoOAuth2UserInfo.java deleted file mode 100644 index 47746f0dd..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/dto/kakao/KakaoOAuth2UserInfo.java +++ /dev/null @@ -1,51 +0,0 @@ -package site.timecapsulearchive.core.global.security.oauth.dto.kakao; - -import java.util.Map; -import site.timecapsulearchive.core.global.security.oauth.dto.OAuth2UserInfo; - -public class KakaoOAuth2UserInfo extends OAuth2UserInfo { - - private static final String PROFILE_IMAGE_URL = "profile_image_url"; - private static final String KAKAO_ACCOUNT = "kakao_account"; - private static final String PROFILE = "profile"; - private static final String EMAIL = "email"; - - public KakaoOAuth2UserInfo(Map attributes) { - super(attributes); - } - - private static Map getKakaoPofile(final Map account) { - return (Map) account.get(PROFILE); - } - - @Override - public String getEmail() { - final Map account = getKakaoAcoount(); - if (account == null) { - return null; - } - - return (String) account.get(EMAIL); - } - - @Override - public String getImageUrl() { - final Map account = getKakaoAcoount(); - if (account == null) { - return null; - } - - final Map profile = getKakaoPofile(account); - if (profile == null) { - return null; - } - - return (String) profile.get(PROFILE_IMAGE_URL); - } - - private Map getKakaoAcoount() { - return (Map) attributes.get(KAKAO_ACCOUNT); - } - - -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/handler/OAuth2LoginFailureHandler.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/handler/OAuth2LoginFailureHandler.java deleted file mode 100644 index 2b88c9f7f..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/handler/OAuth2LoginFailureHandler.java +++ /dev/null @@ -1,41 +0,0 @@ -package site.timecapsulearchive.core.global.security.oauth.handler; - -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.MediaType; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.stereotype.Component; -import site.timecapsulearchive.core.global.error.ErrorCode; -import site.timecapsulearchive.core.global.error.ErrorResponse; - -@Slf4j -@Component -@RequiredArgsConstructor -public class OAuth2LoginFailureHandler implements AuthenticationFailureHandler { - - private final ObjectMapper objectMapper; - - @Override - public void onAuthenticationFailure( - final HttpServletRequest request, - final HttpServletResponse response, - final AuthenticationException exception - ) throws IOException { - log.info("oauth2 인증 실패", exception); - - final ErrorResponse errorResponse = ErrorResponse.fromErrorCode( - ErrorCode.OAUTH2_NOT_AUTHENTICATED_ERROR - ); - - response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); - response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); - - response.getWriter() - .write(objectMapper.writeValueAsString(errorResponse)); - } -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/handler/OAuth2LoginSuccessHandler.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/handler/OAuth2LoginSuccessHandler.java deleted file mode 100644 index 8518816f5..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/handler/OAuth2LoginSuccessHandler.java +++ /dev/null @@ -1,64 +0,0 @@ -package site.timecapsulearchive.core.global.security.oauth.handler; - -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.io.PrintWriter; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.MediaType; -import org.springframework.security.core.Authentication; -import org.springframework.security.web.authentication.AuthenticationSuccessHandler; -import org.springframework.stereotype.Component; -import site.timecapsulearchive.core.domain.auth.service.TokenManager; -import site.timecapsulearchive.core.global.error.ErrorCode; -import site.timecapsulearchive.core.global.error.ErrorResponse; -import site.timecapsulearchive.core.global.security.oauth.dto.CustomOAuth2User; - -@Slf4j -@Component -@RequiredArgsConstructor(access = AccessLevel.PROTECTED) -public class OAuth2LoginSuccessHandler implements AuthenticationSuccessHandler { - - private final TokenManager tokenService; - private final ObjectMapper objectMapper; - - @Override - public void onAuthenticationSuccess( - final HttpServletRequest request, - final HttpServletResponse response, - final Authentication authentication - ) throws IOException { - - try { - final CustomOAuth2User oAuth2User = (CustomOAuth2User) authentication.getPrincipal(); - - response.setStatus(HttpServletResponse.SC_OK); - response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); - final PrintWriter writer = response.getWriter(); - - if (oAuth2User.isNotVerified()) { - writer.write(objectMapper.writeValueAsString( - tokenService.createTemporaryToken(oAuth2User.getId()) - )); - return; - } - - writer.write(objectMapper.writeValueAsString( - tokenService.createNewToken(oAuth2User.getId()) - )); - - } catch (final Exception exception) { - log.info("oauth2 인증 실패", exception); - - final ErrorResponse errorResponse = ErrorResponse.fromErrorCode( - ErrorCode.INTERNAL_SERVER_ERROR - ); - - response.getWriter() - .write(objectMapper.writeValueAsString(errorResponse)); - } - } -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/service/CustomOAuth2UserService.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/service/CustomOAuth2UserService.java deleted file mode 100644 index 8b5ab1618..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/security/oauth/service/CustomOAuth2UserService.java +++ /dev/null @@ -1,84 +0,0 @@ -package site.timecapsulearchive.core.global.security.oauth.service; - -import java.util.Collections; -import java.util.Map; -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest; -import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.core.user.OAuth2User; -import org.springframework.stereotype.Service; -import site.timecapsulearchive.core.domain.member.entity.Member; -import site.timecapsulearchive.core.domain.member.entity.SocialType; -import site.timecapsulearchive.core.domain.member.repository.MemberRepository; -import site.timecapsulearchive.core.global.security.oauth.dto.CustomOAuth2User; -import site.timecapsulearchive.core.global.security.oauth.dto.OAuthAttributes; - -@Slf4j -@Service -@RequiredArgsConstructor(access = AccessLevel.PROTECTED) -public class CustomOAuth2UserService implements OAuth2UserService { - - private final MemberRepository memberRepository; - - @Override - public OAuth2User loadUser(final OAuth2UserRequest userRequest) - throws OAuth2AuthenticationException { - log.info("CustomOAuth2UserService.loadUser() 실행 - OAuth2 로그인 요청 진입"); - - final OAuth2UserService delegate = new DefaultOAuth2UserService(); - final OAuth2User oAuth2User = delegate.loadUser(userRequest); - - final String registrationId = userRequest.getClientRegistration().getRegistrationId(); - - final SocialType socialType = SocialType.getSocialType(registrationId); - final String userNameAttributeName = userRequest.getClientRegistration() - .getProviderDetails() - .getUserInfoEndpoint() - .getUserNameAttributeName(); - final Map attributes = oAuth2User.getAttributes(); - - final OAuthAttributes extractAttributes = OAuthAttributes.of( - socialType, - userNameAttributeName, - attributes - ); - - final Member createMember = getMember(extractAttributes, socialType); - - return new CustomOAuth2User( - Collections.emptyList(), - attributes, - userNameAttributeName, - createMember.getEmail(), - createMember.getIsVerified(), - createMember.getId() - ); - } - - - private Member getMember(final OAuthAttributes attributes, final SocialType socialType) { - return memberRepository.findMemberByAuthIdAndSocialType( - attributes.getAuthId(), - socialType - ) - .orElseGet(() -> saveMember(socialType, attributes)); - } - - private Member saveMember(final SocialType socialType, final OAuthAttributes attributes) { - final Member createMember = attributes.OAuthToMember(socialType); - - boolean isDuplicateTag = memberRepository.checkTagDuplication(createMember.getTag()); - if (isDuplicateTag) { - log.warn("member tag duplicate - email:{}, tag:{}", createMember.getEmail(), - createMember.getTag()); - createMember.updateTagLowerCaseSocialType(); - log.warn("member tag update - tag: {}", createMember.getTag()); - } - - return memberRepository.save(createMember); - } -} diff --git a/backend/core/src/main/java/site/timecapsulearchive/core/global/util/TagGenerator.java b/backend/core/src/main/java/site/timecapsulearchive/core/global/util/TagGenerator.java deleted file mode 100644 index d02ff813e..000000000 --- a/backend/core/src/main/java/site/timecapsulearchive/core/global/util/TagGenerator.java +++ /dev/null @@ -1,61 +0,0 @@ -package site.timecapsulearchive.core.global.util; - -import java.security.SecureRandom; -import java.util.stream.Collectors; -import site.timecapsulearchive.core.domain.member.entity.SocialType; - -public final class TagGenerator { - - private static final int SIZE = 6; - private static final int BOUND = 10; - private static final String EMAIL_DELIMITER = "@"; - private static final String HYPHEN = "-"; - private static final SecureRandom secureRandom = new SecureRandom(); - - /** - * 이메일과 소셜 타입(대문자)으로 태그를 생성한다. - * - * @param email 이메일 - * @param socialType 소셜 타입 - * @return 생성된 태그 ex) {@code test1234@gmail.com, SocialType.GOOGLE} -> - * {@code "test1234-123456GG"} - */ - public static String generate(final String email, final SocialType socialType) { - final String randomInts = generateRandomInts(); - - final String[] splitEmail = email.split(EMAIL_DELIMITER); - - return splitEmail[0] - + HYPHEN - + randomInts - + Character.toUpperCase(splitEmail[1].charAt(0)) - + socialType.name().charAt(0); - } - - private static String generateRandomInts() { - return secureRandom - .ints(SIZE, 0, BOUND) - .mapToObj(String::valueOf) - .collect(Collectors.joining()); - } - - /** - * 이메일과 소셜 타입(소문자)으로 태그를 생성한다. - * - * @param email 이메일 - * @param socialType 소셜 타입 - * @return 생성된 태그 ex) {@code test1234@gmail.com, SocialType.GOOGLE} -> - * {@code "test1234-123456gg"} - */ - public static String lowercase(final String email, final SocialType socialType) { - final String randomInts = generateRandomInts(); - - final String[] splitEmail = email.split(EMAIL_DELIMITER); - - return splitEmail[0] - + HYPHEN - + randomInts - + Character.toLowerCase(splitEmail[1].charAt(0)) - + Character.toLowerCase(socialType.name().charAt(0)); - } -} diff --git a/backend/core/src/main/resources/db/migration/V36__fix_email.sql b/backend/core/src/main/resources/db/migration/V36__fix_email.sql new file mode 100644 index 000000000..ac6f95625 --- /dev/null +++ b/backend/core/src/main/resources/db/migration/V36__fix_email.sql @@ -0,0 +1,16 @@ +-- drop size email +ALTER TABLE member DROP KEY unique_email; + +ALTER TABLE member_temporary + MODIFY email VARBINARY(255); + +ALTER TABLE member + MODIFY email VARBINARY(255); + +ALTER TABLE member_temporary + ADD email_hash VARBINARY(255); + +ALTER TABLE member + ADD email_hash VARBINARY(255); + +ALTER TABLE member ADD CONSTRAINT unique_email_hash UNIQUE (email_hash); \ No newline at end of file diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java index a739a292f..794d15700 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/config/TestMockMvcSecurityConfig.java @@ -13,7 +13,11 @@ import org.springframework.security.authentication.ProviderManager; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; +import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig; +import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; import org.springframework.security.web.util.matcher.NegatedRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.security.web.util.matcher.RequestMatchers; @@ -23,8 +27,6 @@ import site.timecapsulearchive.core.global.api.limit.ApiLimitCheckInterceptor; import site.timecapsulearchive.core.global.api.limit.ApiLimitProperties; import site.timecapsulearchive.core.global.api.limit.ApiUsageCacheRepository; -import site.timecapsulearchive.core.global.config.security.CommonSecurityDsl; -import site.timecapsulearchive.core.global.config.security.JwtDsl; import site.timecapsulearchive.core.global.security.jwt.JwtAuthenticationFilter; import site.timecapsulearchive.core.global.security.jwt.JwtAuthenticationProvider; @@ -34,11 +36,14 @@ public class TestMockMvcSecurityConfig { @Bean public SecurityFilterChain filterChainWithJwt(final HttpSecurity http) throws Exception { - http.apply( - CommonSecurityDsl.commonSecurityDsl() - ); - http + .csrf(AbstractHttpConfigurer::disable) + .formLogin(AbstractHttpConfigurer::disable) + .httpBasic(AbstractHttpConfigurer::disable) + .headers(header -> header.frameOptions(FrameOptionsConfig::disable)) + .sessionManagement(session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) .securityMatchers( c -> c.requestMatchers(new NegatedRequestMatcher(antMatcher("/auth/login/**"))) ) @@ -49,15 +54,12 @@ public SecurityFilterChain filterChainWithJwt(final HttpSecurity http) throws Ex "/temporary-token/re-issue" ).hasRole(Role.TEMPORARY.name()) .anyRequest().hasRole(Role.USER.name()) - ); - - http.apply( - JwtDsl.jwtDsl( - jwtAuthenticationProvider(), - new ObjectMapper(), - notRequireAuthenticationMatcher() ) - ); + .authenticationProvider(jwtAuthenticationProvider()) + .addFilterBefore( + jwtAuthenticationFilter(), + UsernamePasswordAuthenticationFilter.class + ); return http.build(); } diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java index ea1962e72..1bdd5b175 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberFixture.java @@ -49,11 +49,13 @@ private static void setFieldValue(Object instance, String fieldName, Object valu */ public static Member member(int dataPrefix) { byte[] number = getPhoneBytes(dataPrefix); + byte[] email = (dataPrefix + "test@google.com").getBytes(StandardCharsets.UTF_8); return Member.builder() .socialType(SocialType.GOOGLE) .nickname(dataPrefix + "testNickname") - .email(dataPrefix + "test@google.com") + .email(aesEncryptionManager.encryptWithPrefixIV(email)) + .emailHash(hashEncryptionManager.encrypt(email)) .authId(dataPrefix + "test") .profileUrl(dataPrefix + "test.com") .tag(dataPrefix + "testTag") diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberTemporaryFixture.java b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberTemporaryFixture.java index 0bc76fe65..beee682b1 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberTemporaryFixture.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/common/fixture/domain/MemberTemporaryFixture.java @@ -1,15 +1,25 @@ package site.timecapsulearchive.core.common.fixture.domain; +import java.nio.charset.StandardCharsets; +import site.timecapsulearchive.core.common.dependency.UnitTestDependency; import site.timecapsulearchive.core.domain.member.entity.MemberTemporary; import site.timecapsulearchive.core.domain.member.entity.SocialType; +import site.timecapsulearchive.core.global.security.encryption.AESEncryptionManager; +import site.timecapsulearchive.core.global.security.encryption.HashEncryptionManager; public class MemberTemporaryFixture { + private static final AESEncryptionManager aesEncryptionManager = UnitTestDependency.aesEncryptionManager(); + private static final HashEncryptionManager hashEncryptionManager = UnitTestDependency.hashEncryptionManager(); + public static MemberTemporary memberTemporary(Long memberId) { + byte[] emailBytes = "test_email@gmail.com".getBytes(StandardCharsets.UTF_8); + return MemberTemporary.builder() .authId(memberId + "test_auth_id") .tag(memberId + "test_tag") - .email(memberId + "test_email@gmail.com") + .email(aesEncryptionManager.encryptWithPrefixIV(emailBytes)) + .emailHash(hashEncryptionManager.encrypt(emailBytes)) .nickname(memberId + "test_nickname") .profileUrl(memberId + "test_profile_url") .socialType(SocialType.GOOGLE) diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java index 0759e0e21..8e2d34892 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/repository/MemberQueryRepositoryTest.java @@ -72,7 +72,7 @@ void setup(@Autowired EntityManager entityManager) { softly.assertThat(detailDto.nickname()).isNotBlank(); softly.assertThat(detailDto.profileUrl()).isNotBlank(); softly.assertThat(detailDto.socialType()).isNotNull(); - softly.assertThat(detailDto.email()).isNotBlank(); + softly.assertThat(detailDto.email()).isNotNull(); softly.assertThat(detailDto.phone()).isNotNull(); softly.assertThat(detailDto.friendCount()).isNotNull(); softly.assertThat(detailDto.groupCount()).isNotNull(); diff --git a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java index 3d02027a8..0adba2bf2 100644 --- a/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java +++ b/backend/core/src/test/java/site/timecapsulearchive/core/domain/member/service/MemberServiceTest.java @@ -103,20 +103,4 @@ class MemberServiceTest { //then verify(memberRepository, times(1)).save(any(Member.class)); } - - @Test - void 태그가_중복되면_태그를_교체한다() { - //given - MemberTemporary memberTemporary = MemberTemporaryFixture.memberTemporary(MEMBER_ID); - String originTag = memberTemporary.getTag(); - given(memberTemporaryRepository.findById(anyLong())) - .willReturn(Optional.of(memberTemporary)); - given(memberRepository.checkTagDuplication(any())).willReturn(true); - - //when - memberService.updateVerifiedMember(MEMBER_ID, RECEIVER.getBytes(StandardCharsets.UTF_8)); - - //then - assertThat(memberTemporary.getTag()).isNotEqualTo(originTag); - } }