데이터 식별자를 노출하지 않기위한 방법으로 로그인했을때 토큰에서 member data를 추출하는 방법을 이용하기로 했다.
부트캠프를 진행하며 배운 코드에는 다음과 같이 URI path에 member-id가 노출되게 된다.
@PatchMapping("{member-id}")
public ResponseEntity patchMember(@PathVariable("member-id") @Positive long Id,
@RequestBody MemberDto.Patch memberPatch)
이렇게 되면, 사용자의 데이터가 노출될 위험이 있기 때문에 API에 노출하지 않는 방향으로 기본 CRUD는 로그인을 했을 경우에만 가능하도록 계획했다.
이 방법을 이용하려면 2가지 조건을 만족해야 했다.
- 로그인 했을 때 토큰에서 member class 가져오기
- URL path 별 권한을 부여하여 로그인했을때에만 실행할 수 있도록 하기
기본적으로 JWT는 Header, Payload, Signature 로 이루어져 있다.
이중 Payload 에는 서버에서 활용할 수 있는 사용자의 정보가 담겨있다.
이것을 이용하여 토큰을 받아서 자신인지를 인증하고, 권한을 부여받을 수 있으며, member에 접근하여 정보를 가져올 수 도 있다.
뒤에 블로깅한 principal 객체에 직접 접근하기의 3번째 방법을 이용하여 구현했다.
SecurityContextHolder.getContext().getAuthentication().getPrincipal();```
Controller 에서 사용자의 정보를 얻는 방법이다.
이 경우 principal 객체에 직접 접근하여 username을 얻는다.
로그인을 했을때에는 토큰을 가지고있다. 토큰을 가지고있다면, username을 알 수 있게되고 그것을 알면 나머지 정보를 DB에서 조회할 수 있을것이다.
@PatchMapping("{member-id}")
public ResponseEntity patchMember(@PathVariable("member-id") @Positive long Id,
@RequestBody MemberDto.Patch memberPatch){
memberPatch.setId(Id);
Member member = mapper.MemberPatchToMember(memberPatch);
Member updateMember = memberService.updateMember(member);
return new ResponseEntity<>(
new SingleResponseDto<>(mapper.MemberToMemberResponseDto(updateMember)),HttpStatus.OK);
}
@PatchMapping
public ResponseEntity patchMember(Principal principal,
@RequestBody MemberDto.Patch memberPatch){
Member member = mapper.MemberPatchToMember(memberPatch);
Member updateMember = memberFindService.updateMember(member, principal.getName());
return new ResponseEntity<>(
new SingleResponseDto<>(mapper.MemberToMemberResponseDto(updateMember)),HttpStatus.OK);
}
- 더이상 URL에 데이터 식별자를 안넣어도 기능이 가능하게 되었다.
- principal을 통해 가져온 username을 이용해서 DB에서 조회를 해야하기 때문에 member와 username 2가지 변수를 담을 메서드가 새로 필요해졌다.
- 2가지 변수를 담을 메서드들을 관리하기위해 memberFindService 층을 새로 만들고 그 안에 메서드를 관리했다.
@Service
public class MemberFindService {
private final QuestionService questionService;
private final MemberService memberService;
private final BlogService blogService;
public MemberFindService(QuestionService questionService, MemberService memberService,
BlogService blogService) {
this.questionService = questionService;
this.memberService = memberService;
this.blogService = blogService;
}
.
.
.
@Transactional
public Member updateMember(Member member, String email){
return memberService.updateMember(member, email);
}
}
- updateMmber라는 메서드를 만들었고, 그 안에 2가지 변수를 받을 수 있도록 구성했다.
- 그리고 MemberService 계층에서 MemberFindService 변수를 받을 수 있도록 수정하였다.
public Member updateMember(Member member){
Member foundMember = findVerifiedMember(member.getId());
Optional.ofNullable(member.getNickname())
.ifPresent(nickname -> foundMember.setNickname(nickname));
Optional.ofNullable(member.getPassword())
.ifPresent(password -> foundMember.setPassword(password));
Optional.ofNullable(member.getLocation())
.ifPresent(location -> foundMember.setLocation(location));
Optional.ofNullable(member.getMemberStatus())
.ifPresent(memberStatus -> foundMember.setMemberStatus(memberStatus));
foundMember.setModifiedAt(LocalDateTime.now());
Member updateMember = memberRepository.save(foundMember);
return updateMember;
}
public Member updateMember(Member member, String email){
Member foundMember = findMemberByEmail(email);<--------변경
Optional.ofNullable(member.getNickname())
.ifPresent(nickname -> foundMember.setNickname(nickname));
Optional.ofNullable(member.getPassword())
.ifPresent(password -> foundMember.setPassword(password));
Optional.ofNullable(member.getLocation())
.ifPresent(location -> foundMember.setLocation(location));
Optional.ofNullable(member.getMemberStatus())
.ifPresent(memberStatus -> foundMember.setMemberStatus(memberStatus));
foundMember.setModifiedAt(LocalDateTime.now());
Member updateMember = memberRepository.save(foundMember);
return updateMember;
}
- 추가된 findMemberByEmail메서드는 principal로 받은 email을이용하여 member를 조회하는 메서드이다.
public Member findMemberByEmail(String email){
Optional<Member> optionalMember = memberRepository.findByEmail(email);
Member foundMember = optionalMember.orElseThrow(()->
new BusinessLogicException(ExceptionCode.MEMBER_NOT_EXISTS));
return foundMember;
}
public interface MemberRepository extends JpaRepository<Member, Long> {
Optional<Member> findByEmail(String email);
}