로그인한 상태에서 토큰을 이용하여 member 정보 불러와서 이용하기

이정연·2023년 6월 3일
1

project034

목록 보기
8/10
post-custom-banner

필요성 인식

데이터 식별자를 노출하지 않기위한 방법으로 로그인했을때 토큰에서 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가지 조건을 만족해야 했다.

  1. 로그인 했을 때 토큰에서 member class 가져오기
  2. URL path 별 권한을 부여하여 로그인했을때에만 실행할 수 있도록 하기
  • 기본적으로 JWT는 Header, Payload, Signature 로 이루어져 있다.

  • 이중 Payload 에는 서버에서 활용할 수 있는 사용자의 정보가 담겨있다.

  • 이것을 이용하여 토큰을 받아서 자신인지를 인증하고, 권한을 부여받을 수 있으며, member에 접근하여 정보를 가져올 수 도 있다.

  • 뒤에 블로깅한 principal 객체에 직접 접근하기의 3번째 방법을 이용하여 구현했다.


인증된 사용자 정보의 관리 flow

  • Principal에 저장된 사용자의 정보는 최종적으로 SecurityContextHolder에서 다루게 됩니다. 따라서 다음과 같이 username을 불러올 수 있게 됩니다.

SecurityContextHolder.getContext().getAuthentication().getPrincipal();```

해결

  • Controller 에서 사용자의 정보를 얻는 방법이다.

  • 이 경우 principal 객체에 직접 접근하여 username을 얻는다.

  • 로그인을 했을때에는 토큰을 가지고있다. 토큰을 가지고있다면, username을 알 수 있게되고 그것을 알면 나머지 정보를 DB에서 조회할 수 있을것이다.

수정 전 Controller

@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);
    }

수정 후 Controller

@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 층을 새로 만들고 그 안에 메서드를 관리했다.

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 변수를 받을 수 있도록 수정하였다.

기존 MemberService

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;
    }

수정 후 MemberService

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를 조회하는 메서드이다.

findMemberByEmail

 public Member findMemberByEmail(String email){
        Optional<Member> optionalMember = memberRepository.findByEmail(email);
        Member foundMember = optionalMember.orElseThrow(()->
                new BusinessLogicException(ExceptionCode.MEMBER_NOT_EXISTS));
        return foundMember;
    }

MemberRepository

public interface MemberRepository extends JpaRepository<Member, Long> {
    Optional<Member> findByEmail(String email);
}
profile
반갑습니다.
post-custom-banner

0개의 댓글