Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http, LoginAuthentic
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class);
http.authorizeHttpRequests(auth -> {
auth
.requestMatchers(HttpMethod.POST, "users").permitAll()
.requestMatchers("users").permitAll()
.requestMatchers(
"/swagger-ui/**",
"/v3/api-docs/**",
Expand Down
23 changes: 15 additions & 8 deletions src/main/java/com/retrip/auth/application/in/MemberService.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
package com.retrip.auth.application.in;

import com.retrip.auth.application.in.request.MemberCreateRequest;
import com.retrip.auth.application.in.request.MemberDeleteRequest;
import com.retrip.auth.application.in.request.MemberUpdateRequest;
import com.retrip.auth.application.in.response.MemberCreateResponse;
import com.retrip.auth.application.in.response.MemberUpdateResponse;
import com.retrip.auth.application.in.usercase.ManageMemberUseCase;
import com.retrip.auth.application.out.repository.MemberRepository;
import com.retrip.auth.domain.entity.Member;
import com.retrip.auth.domain.exception.MemberNotFoundException;
import com.retrip.auth.domain.exception.common.EntityNotFoundException;
import com.retrip.auth.domain.vo.MemberEmail;
import com.sun.jdi.request.DuplicateRequestException;
import jakarta.transaction.Transactional;
Expand All @@ -28,7 +28,7 @@ public class MemberService implements ManageMemberUseCase {
@Override
public MemberCreateResponse createUser(MemberCreateRequest request) {
String encode = passwordEncoder.encode(request.password());
boolean isDuplicate = !memberRepository.findByEmail(new MemberEmail(request.email())).isEmpty();
boolean isDuplicate = !memberRepository.findByEmailAndIsDeletedFalse(new MemberEmail(request.email())).isEmpty();
if (isDuplicate) {
throw new DuplicateRequestException("Email already exists");
}
Expand All @@ -38,27 +38,34 @@ public MemberCreateResponse createUser(MemberCreateRequest request) {

@Override
public MemberUpdateResponse updateUser(MemberUpdateRequest request) {
String encodePassword = passwordEncoder.encode(request.password());
String encodeNewPassword = passwordEncoder.encode(request.newPassword());

Member member = findByEmail(request.email());
if(isUpdatable(member.getPassword().getValue(), encodePassword)) { //여기에 패스워드 검증 로직이 들어가는게 맞는지..?
if (isSamePassword(member.getPassword().getValue(), request.password())) { //여기에 패스워드 검증 로직이 들어가는게 맞는지..?
member.update(encodeNewPassword, request.name());
}
return MemberUpdateResponse.of(member);
}

@Override
public void deleteUser(MemberDeleteRequest request) {
Member member = findByEmail(request.email());
if (isSamePassword(member.getPassword().getValue(), request.password())) { //여기에 패스워드 검증 로직이 들어가는게 맞는지..?
member.delete();
}
Comment on lines +53 to +55
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제가 생각했을때는 member.delete(request.password()) 로 받아서 해당 패스워드로직 검증을 도메인 안에서 따로 하는게 좋을 거 같아요! ---> 정정 아 passwordEncoder 때문에 도메인이 아니라 서비스 계층에서 검증하고 delete 하는게 맞겠네요 그대로가 좋은 거 같습니다!

}


private Member findByEmail(String email) {
return memberRepository.findByEmail(new MemberEmail(email))
return memberRepository.findByEmailAndIsDeletedFalse(new MemberEmail(email))
.stream().findAny().orElseThrow(MemberNotFoundException::new);
}

private boolean isUpdatable(String password, String inputPassword) {
if (!passwordEncoder.matches(password, inputPassword)) {
private boolean isSamePassword(String password, String inputPassword) {
if (!passwordEncoder.matches(inputPassword, password)) {
throw new BadCredentialsException("Bad credentials");
}
return true;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.retrip.auth.application.in.request;

import io.swagger.v3.oas.annotations.media.Schema;

@Schema(description = "Member 삭제 Request")
public record MemberDeleteRequest(
@Schema(description = "이메일")
String email,
@Schema(description = "비밀번호")
String password
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@


import com.retrip.auth.application.in.request.MemberCreateRequest;
import com.retrip.auth.application.in.request.MemberDeleteRequest;
import com.retrip.auth.application.in.request.MemberUpdateRequest;
import com.retrip.auth.application.in.response.MemberCreateResponse;
import com.retrip.auth.application.in.response.MemberUpdateResponse;
Expand All @@ -10,4 +11,6 @@ public interface ManageMemberUseCase {
MemberCreateResponse createUser(MemberCreateRequest request);

MemberUpdateResponse updateUser(MemberUpdateRequest request);

void deleteUser(MemberDeleteRequest request);
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {
List<Member> findByEmail(MemberEmail email);
List<Member> findByEmailAndIsDeletedFalse(MemberEmail email);
}
8 changes: 8 additions & 0 deletions src/main/java/com/retrip/auth/domain/entity/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,16 @@ public class Member extends BaseEntity {
@Embedded
private Authorities authorities;

private Boolean isDeleted;


public static Member create(String name, String email, String password, List<String> authorities) {
Member member = Member.builder()
.id(UUID.randomUUID())
.name(new MemberName(name))
.email(new MemberEmail(email))
.password(new MemberPassword(password))
.isDeleted(false)
.build();
member.authorities = new Authorities(authorities, member);
return member;
Expand All @@ -52,6 +55,7 @@ public static Member create(String name, String email, String password) {
.id(UUID.randomUUID())
.name(new MemberName(name))
.email(new MemberEmail(email))
.isDeleted(false)
.password(new MemberPassword(password))
.build();
member.authorities = new Authorities(List.of("user"), member);
Expand All @@ -62,4 +66,8 @@ public void update(String password, String name) {
this.password = new MemberPassword(password);
this.name = new MemberName(name);
}

public void delete() {
this.isDeleted = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.retrip.auth.application.config.JwtConfig;
import com.retrip.auth.application.config.UsernamePasswordAuthentication;
import com.retrip.auth.application.in.response.LoginResponse;
import com.retrip.auth.infra.adapter.in.rest.common.ApiResponse;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.security.Keys;
import jakarta.servlet.FilterChain;
Expand All @@ -13,11 +14,9 @@
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

Expand Down Expand Up @@ -47,9 +46,9 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
Authentication authentication = new UsernamePasswordAuthentication(id, password);
Authentication auth = manager.authenticate(authentication);
LoginResponse.TokenResponse tokenResponse = generateToken(auth);

ApiResponse<LoginResponse.TokenResponse> result = ApiResponse.ok(tokenResponse);
// JSON 직렬화
String json = getResponseBody(tokenResponse);
String json = getResponseBody(result);

// 응답 Header 설정
response.setContentType("application/json");
Expand Down Expand Up @@ -98,7 +97,7 @@ public LoginResponse.TokenResponse generateToken(Authentication authentication)
return new LoginResponse.TokenResponse(accessToken, refreshToken);
}

private static String getResponseBody(LoginResponse.TokenResponse tokenResponse) throws JsonProcessingException {
private static String getResponseBody(ApiResponse<LoginResponse.TokenResponse> tokenResponse) throws JsonProcessingException {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.writeValueAsString(tokenResponse);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
package com.retrip.auth.infra.adapter.in.rest.in;

import com.retrip.auth.application.in.request.MemberCreateRequest;
import com.retrip.auth.application.in.request.MemberDeleteRequest;
import com.retrip.auth.application.in.request.MemberUpdateRequest;
import com.retrip.auth.application.in.response.MemberCreateResponse;
import com.retrip.auth.application.in.response.MemberUpdateResponse;
import com.retrip.auth.application.in.usercase.ManageMemberUseCase;
import com.retrip.auth.infra.adapter.in.rest.common.ApiResponse;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
Expand All @@ -20,17 +22,26 @@ public class MemberController {

@PostMapping
@Schema(description = "회원 가입")
public MemberCreateResponse createUser(
public ApiResponse<MemberCreateResponse> createUser(
@RequestBody MemberCreateRequest request
){
return manageMemberUseCase.createUser(request);
return ApiResponse.created(manageMemberUseCase.createUser(request));
}

@PutMapping
@Schema(description = "회원 정보 수정")
public MemberUpdateResponse updateUser(
public ApiResponse<MemberUpdateResponse> updateUser(
@RequestBody MemberUpdateRequest request
){
return manageMemberUseCase.updateUser(request);
return ApiResponse.ok(manageMemberUseCase.updateUser(request));
}

@DeleteMapping()
@Schema(description = "회원 정보 삭제")
public ApiResponse<?> deleteUser(
@RequestBody MemberDeleteRequest request
){
manageMemberUseCase.deleteUser(request);
return ApiResponse.noContent();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public Optional<Member> findByEmailWithAuthorities(String email) {
return Optional.ofNullable(query.selectFrom(member)
.leftJoin(member.authorities.values, authority)
.fetchJoin()
.where(member.email.value.eq(email))
.where(member.email.value.eq(email), member.isDeleted.isFalse())
.fetchOne());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class MemberServiceTest extends BaseMemberServiceTest {
@Test
void 회원가입_중복_회원가입() throws Exception {
// given
memberRepository.save(Member.create("test", "test@naver.com", "1234"));
memberRepository.save(Member.create("test", "test@naver.com", passwordEncoder.encode("1234")));
MemberCreateRequest request = new MemberCreateRequest("test@naver.com", "1111", "중복 테스트");

//when
Expand All @@ -49,7 +49,7 @@ class MemberServiceTest extends BaseMemberServiceTest {
@Test
void 회원정보_수정_패스워드_달라_실패() throws Exception {
// given
memberRepository.save(Member.create("test", "test@naver.com", "1234"));
memberRepository.save(Member.create("test", "test@naver.com", passwordEncoder.encode("1234")));
MemberUpdateRequest request = new MemberUpdateRequest("test@naver.com", "1235", "1111",
"수정 테스트");

Expand Down Expand Up @@ -78,7 +78,7 @@ class MemberServiceTest extends BaseMemberServiceTest {
@Test
void 회원정보_수정_성공() throws Exception {
// given
memberRepository.save(Member.create("test", "test@naver.com", "1234"));
memberRepository.save(Member.create("test", "test@naver.com", passwordEncoder.encode("1234")));
MemberUpdateRequest request = new MemberUpdateRequest("test@naver.com", "1234", "1111",
"수정 테스트");

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;

@SpringBootTest
@Transactional
Expand All @@ -16,6 +17,10 @@ public class BaseMemberServiceTest {
@Autowired
protected MemberRepository memberRepository;


@Autowired
protected PasswordEncoder passwordEncoder;

@BeforeEach
void setUp() {
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,9 @@ void JwtLogin() throws Exception {
// JSON 파싱
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(responseBody);
String accessToken = jsonNode.get("accessToken").asText();
String refreshToken = jsonNode.get("refreshToken").asText();
JsonNode data = jsonNode.get("data");
String accessToken = data.get("accessToken").asText();
String refreshToken = data.get("refreshToken").asText();


// when & then
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.retrip.auth.application.in.request.LoginRequest;
import com.retrip.auth.application.in.request.MemberCreateRequest;
import com.retrip.auth.application.in.request.MemberDeleteRequest;
import com.retrip.auth.application.in.request.MemberUpdateRequest;
import com.retrip.auth.infra.adapter.in.rest.filter.base.BaseLoginAuthenticationTest;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
Expand All @@ -12,13 +14,14 @@
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.transaction.annotation.Transactional;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

@SpringBootTest
@Transactional
@AutoConfigureMockMvc(addFilters = true)
class LoginAuthenticationFilterTest extends BaseLoginAuthenticationTest {

Expand All @@ -43,8 +46,10 @@ class LoginAuthenticationFilterTest extends BaseLoginAuthenticationTest {
.contentType(MediaType.APPLICATION_JSON)
.headers(headers))
.andExpect(status().isOk())
.andExpect(jsonPath("$.accessToken").isNotEmpty())
.andExpect(jsonPath("$.refreshToken").isNotEmpty());
.andExpect(jsonPath("$.success").isBoolean())
.andExpect(jsonPath("$.status").isNotEmpty())
.andExpect(jsonPath("$.data.accessToken").isNotEmpty())
.andExpect(jsonPath("$.data.refreshToken").isNotEmpty());
}

@Test
Expand All @@ -61,7 +66,50 @@ class LoginAuthenticationFilterTest extends BaseLoginAuthenticationTest {
.content(json)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.id").isNotEmpty())
.andExpect(jsonPath("$.email").isNotEmpty());
.andExpect(jsonPath("$.success").isBoolean())
.andExpect(jsonPath("$.status").isNotEmpty())
.andExpect(jsonPath("$.data.id").isNotEmpty())
.andExpect(jsonPath("$.data.email").isNotEmpty());
}
@Test
void 유저_수정_성공() throws Exception {
memberRepository.save(member);

// given
MemberUpdateRequest request = new MemberUpdateRequest("test@naver.com", "1234", "1111",
"수정 테스트");
//when
String json = new ObjectMapper().writeValueAsString(request);

// when & then
mockMvc.perform(put("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").isBoolean())
.andExpect(jsonPath("$.status").isNotEmpty())
.andExpect(jsonPath("$.data.id").isNotEmpty())
.andExpect(jsonPath("$.data.email").isNotEmpty());
}

@Test
void 유저_삭제_성공() throws Exception {
memberRepository.save(member);

// given
MemberDeleteRequest request = new MemberDeleteRequest("test@naver.com", "1234");
//when
String json = new ObjectMapper().writeValueAsString(request);

// when & then
mockMvc.perform(delete("/users")
.contentType(MediaType.APPLICATION_JSON)
.content(json)
)
.andExpect(status().isOk())
.andExpect(jsonPath("$.success").isBoolean())
.andExpect(jsonPath("$.status").isNotEmpty())
.andExpect(jsonPath("$.message").isNotEmpty());
}
}