Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

회원 정보 수정, 삭제 리팩토링 #134

Merged
merged 10 commits into from
Feb 28, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha
}
chain.doFilter(request, response);
}


Copy link
Member

Choose a reason for hiding this comment

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

이 파일은 어떤 작업이 이루어진걸까요???🤔

}
18 changes: 17 additions & 1 deletion src/main/java/balancetalk/global/jwt/JwtTokenProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -84,12 +84,28 @@ public Authentication getAuthentication(String token) {
// http 헤더로부터 bearer 토큰 가져옴
public String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader("Authorization");
if (bearerToken != null && bearerToken.startsWith("bearer ")) {
if (bearerToken != null && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7); // 실제 토큰만 추출
}
return null;
}

public String getPayload(String token) {
return tokenToJws(token).getBody().getSubject();
}

public Jws<Claims> tokenToJws(final String token) {
try {
return Jwts.parserBuilder().setSigningKey(secretKey).build().parseClaimsJws(token);
} catch (final IllegalArgumentException | MalformedJwtException e) {
throw new IllegalArgumentException("Token이 null이거나 Token 파싱 오류");
} catch (final SignatureException e) {
throw new IllegalArgumentException("토큰의 시크릿 키가 일치하지 않습니다.");
} catch (final ExpiredJwtException e) {
throw new IllegalArgumentException("만료된 토큰 입니다.");
}
}

// 토큰 유효성, 만료일자 확인
public boolean validateToken(String token) {
try {
Expand Down
6 changes: 6 additions & 0 deletions src/main/java/balancetalk/global/jwt/JwtUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package balancetalk.global.jwt;

public class JwtUtils {

// TODO: static으로 메서드 선언 & member 조회 까지
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,17 @@ public ResponseEntity<Resource> downloadFile(Long fileId) {
if (contentType == null) {
contentType = "application/octet-stream";
}

return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getOriginalName() + "\"")
.body(resource);

} catch (IOException e) {
throw new BalanceTalkException(FILE_DOWNLOAD_FAILED);
}

return ResponseEntity.ok()
.contentType(MediaType.parseMediaType(contentType))
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getUploadName() + "\"")
.body(resource);

}

private FileType convertMimeTypeToFileType(String mimeType) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import balancetalk.global.exception.BalanceTalkException;
import balancetalk.global.exception.ErrorCode;
import balancetalk.global.jwt.JwtTokenProvider;
import balancetalk.global.redis.application.RedisService;
import balancetalk.module.member.domain.Member;
import balancetalk.module.member.domain.MemberRepository;
import balancetalk.module.member.dto.*;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authentication.AuthenticationManager;
Expand Down Expand Up @@ -48,23 +48,21 @@ public LoginSuccessDto login(final LoginDto loginDto) {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(loginDto.getEmail(), loginDto.getPassword())
);

TokenDto tokenDto = new TokenDto("Bearer", jwtTokenProvider.createAccessToken(authentication), jwtTokenProvider.createRefreshToken(authentication));
return LoginSuccessDto.builder()
.email(member.getEmail())
.password(member.getPassword())
.role(member.getRole())
.accessToken(jwtTokenProvider.createAccessToken(authentication))
.refreshToken(jwtTokenProvider.createRefreshToken(authentication))
.tokenDto(tokenDto)
.build();
} catch (BadCredentialsException e) {
throw new BadCredentialsException("credential 오류!!");
}
}

@Transactional(readOnly = true)
public MemberResponseDto findById(Long id) {
Member member = memberRepository.findById(id)
.orElseThrow(() -> new BalanceTalkException(ErrorCode.NOT_FOUND_MEMBER));
public MemberResponseDto findMember(HttpServletRequest request) {
Member member = extractMember(request);
return MemberResponseDto.fromEntity(member);
}

Expand All @@ -77,31 +75,35 @@ public List<MemberResponseDto> findAll() {
}

@Transactional
public void updateNickname(Long memberId, final NicknameUpdate nicknameUpdate) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new BalanceTalkException(ErrorCode.NOT_FOUND_MEMBER));
public void updateNickname(final NicknameUpdate nicknameUpdate, HttpServletRequest request) {
Member member = extractMember(request);
member.updateNickname(nicknameUpdate.getNickname());
}

@Transactional
public void updatePassword(Long memberId, final PasswordUpdate passwordUpdate) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new BalanceTalkException(ErrorCode.NOT_FOUND_MEMBER));
member.updatePassword(passwordUpdate.getPassword());
public void updatePassword(final PasswordUpdate passwordUpdate, HttpServletRequest request) {
Member member = extractMember(request);
member.updatePassword(passwordEncoder.encode(passwordUpdate.getPassword()));
}

@Transactional
public void delete(Long memberId, final LoginDto loginDto) {
Member member = memberRepository.findById(memberId)
.orElseThrow(() -> new BalanceTalkException(ErrorCode.NOT_FOUND_MEMBER));

public void delete(final LoginDto loginDto, HttpServletRequest request) {
Member member = extractMember(request);
if (!member.getEmail().equals(loginDto.getEmail())) {
throw new BalanceTalkException(ErrorCode.FORBIDDEN_MEMBER_DELETE);
}
if (!member.getPassword().equals(loginDto.getPassword())) {
throw new BalanceTalkException(ErrorCode.INCORRECT_PASSWORD);

if (!passwordEncoder.matches(loginDto.getPassword(), member.getPassword())) {
throw new BalanceTalkException(ErrorCode.MISMATCHED_EMAIL_OR_PASSWORD);
}
memberRepository.deleteByEmail(member.getEmail());
}

memberRepository.deleteById(memberId);
private Member extractMember(HttpServletRequest request) {
String token = jwtTokenProvider.resolveToken(request);
String email = jwtTokenProvider.getPayload(token);
Member member = memberRepository.findByEmail(email)
.orElseThrow(() -> new BalanceTalkException(ErrorCode.NOT_FOUND_MEMBER));
return member;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@
import java.util.Optional;
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String username);
void deleteByEmail(String email);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ public class LoginSuccessDto {
@Schema(description = "회원 역할", example = "USER")
private Role role;

private String accessToken;
private TokenDto tokenDto;

private String refreshToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
@AllArgsConstructor
public class MemberResponseDto {

@Schema(description = "회원 id", example = "1")
private Long id;

@Schema(description = "회원 닉네임", example = "닉네임")
private String nickname;
@Schema(description = "회원 프로필", example = "../")
Expand All @@ -28,6 +31,7 @@ public class MemberResponseDto {

public static MemberResponseDto fromEntity(Member member) {
return MemberResponseDto.builder()
.id(member.getId())
.nickname(member.getNickname())
//.profilePhoto(file.getPath())
.createdAt(member.getCreatedAt())
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/balancetalk/module/member/dto/TokenDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package balancetalk.module.member.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TokenDto {
private String grantType;
private String accessToken;
private String refreshToken;
}
Original file line number Diff line number Diff line change
@@ -1,24 +1,26 @@
package balancetalk.module.member.presentation;

import balancetalk.global.jwt.JwtTokenProvider;
import balancetalk.module.member.application.MemberService;
import balancetalk.module.member.dto.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;
import java.util.List;

@Slf4j
@RestController
@RequiredArgsConstructor
@Tag(name = "member", description = "회원 API")
@RequestMapping("/members")
public class MemberController {

private final MemberService memberService;

@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/join")
@Operation(summary = "회원 가입", description = "닉네임, 이메일, 비밀번호를 입력하여 회원 가입을 한다.")
Expand All @@ -37,40 +39,41 @@ public LoginSuccessDto login(@Valid @RequestBody LoginDto loginDto) {
}

@ResponseStatus(HttpStatus.OK)
@GetMapping("/{memberId}")
@Operation(summary = "단일 회원 조회", description = "해당 id값과 일치하는 회원 정보를 조회한다.")
public MemberResponseDto findMemberInfo(@PathVariable("memberId") Long memberId) {
return memberService.findById(memberId);
@GetMapping("/find")
@Operation(summary = "단일 회원 조회", description = "해당 jwt 토큰 값과 일치하는 회원 정보를 조회한다.")
public MemberResponseDto findMemberInfo(HttpServletRequest request) {
return memberService.findMember(request);
Copy link
Member

Choose a reason for hiding this comment

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

회원 단건 조회 같은 경우는 사용자 본인이 아닌 타 사용자가 조회할 때 사용하는 API라서 memberId를 직접 전달 받아 조회해야 하지 않을까요??
예를 들어, 회원 A가 회원 B에 대한 정보를 조회하려는데, 토큰에서 추출한 이메일을 통해 조회를 하게 되면 회원 B가 아닌 A 본인에 대한 정보가 응답될 것 같아요.
토큰에서 이메일을 추출해 사용자를 조회하는 로직은 게시글 조회 시 사용자 본인이 추천을 눌렀는지 등을 확인하기 위해 필요한 로직이라고 생각되네용

}

@ResponseStatus(HttpStatus.OK)
@GetMapping
@GetMapping("/findAll")
Copy link
Member

Choose a reason for hiding this comment

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

회원 목록 조회 API의 경우 URL은 "/members"로도 충분해보입니다!
조회라는 의미는 GET 메서드를 통해 충분히 확인할 수 있기도 하고, 회원 단건 조회는 "/members/{memberId}"라는 URL로 중복을 피할 수 있기 때문입니다.

@Operation(summary = "전체 회원 조회", description = "모든 회원 정보를 조회한다.")
public List<MemberResponseDto> findAllMemberInfo() {
return memberService.findAll();
}

@ResponseStatus(HttpStatus.OK)
@PutMapping("/nickname/{memberId}")
@PutMapping("/update/nickname")
Copy link
Member

Choose a reason for hiding this comment

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

이 수정 API를 포함해 삭제 등 모든 API는 메서드를 통해서 CRUD의 성격을 알 수 있습니다.
따라서 "/update/nickname"보다는 "/nickname"을 사용하는게 좋아보이네요!
삭제 같은 경우도 DELETE "/members"가 더 RESTful한 API의 URL이겠네요😃

@Operation(summary = "회원 닉네임 수정", description = "회원 닉네임을 수정한다.")
public String updateNickname(@PathVariable("memberId") Long memberId, @Valid @RequestBody NicknameUpdate nicknameUpdate) {
memberService.updateNickname(memberId, nicknameUpdate);
public String updateNickname(@Valid @RequestBody NicknameUpdate nicknameUpdate, HttpServletRequest request) {
memberService.updateNickname(nicknameUpdate, request);
return "회원 닉네임이 변경되었습니다.";
}

@ResponseStatus(HttpStatus.OK)
@PutMapping("/{memberId}")
@PutMapping("/update/password")
@Operation(summary = "회원 비밀번호 수정", description = "회원 패스워드를 수정한다.")
public String updatePassword(@PathVariable("memberId") Long memberId, @Valid @RequestBody PasswordUpdate passwordUpdate) {
memberService.updatePassword(memberId, passwordUpdate);
public String updatePassword(@Valid @RequestBody PasswordUpdate passwordUpdate, HttpServletRequest request) {
memberService.updatePassword(passwordUpdate, request);
return "회원 비밀번호가 변경되었습니다.";
}

@ResponseStatus(HttpStatus.OK)
@DeleteMapping("/{memberId}")
@DeleteMapping("/delete")
@Operation(summary = "회원 삭제", description = "해당 id값과 일치하는 회원 정보를 삭제한다.")
public String deleteMember(@PathVariable("memberId") Long memberId, @Valid @RequestBody LoginDto loginDto) {
memberService.delete(memberId, loginDto);
public String deleteMember(@Valid @RequestBody LoginDto loginDto, HttpServletRequest request) {
memberService.delete(loginDto, request);
return "회원 탈퇴가 정상적으로 처리되었습니다.";
}

}