본문 바로가기
Programming/삽질일지

[12/6 TIS] @AuthenticationPrincipal 사용기 - null 해결법

by JKROH 2023. 12. 6.
반응형

 사실 저 애너테이션을 너무너무 써보고 싶었다. 부트캠프에서 프로젝트를 진행할 때부터, 저 애너테이션으로 사용자 정보를 한 번에 가져올 수 있다면 얼마나 좋을까 생각했다. 왜? 나는 안되니까...

 

 정말 많은 글들에서 Spring Security를 적용하면 해당 애너테이션을 통해 사용자 정보를 가져올 수 있다고 한다. 그런데 내가 하면 안됐다. 정확히는 넘어와야 할 객체가 항상 null이었다. 오늘은 이 문제를 해결하고 직접 사용해보자.

 

@PostMapping
public ResponseEntity<FreeBoardDto.Response> postFreeBoard(@AuthenticationPrincipal Member writer,
                                                           @RequestBody FreeBoardDto.Post dto) {
    FreeBoardDto.Response responseDto = freeBoardService.postFreeBoard(writer, dto);
    return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
}

 

 처음 시도해 본 코드다. @AuthenticationPrincial 을 통해 Member객체를 가져오고자 한다. 실행하면?

언제나 반가운 NullPointerException ^^

 

 진짜... 개발하면서 가장 꼴보기 싫은 예외 1위를 꼽으라면 단연 NullPointerException이 아닐까? 뭐 값이 다르다거나, 다른 예외도 아니고. 그냥 없다고 해버리면 힘이 탁 풀린다.

 

 아무튼 우리는 이 문제를 해결해야 한다. 신에게 @AuthenticationPrincipal null 을 여쭤보았고, StackOverflow의 한 글을 통해 해답을 알 수 있을 것 같았다.

가끔은 노따봉 댓글에서 답을 찾곤 한다

 

 SecurityContextHolder에 넣어줄 Authentication을 만들 때, UserDetails를 넣어주라는 말이었다. 바로 내 코드를 확인했다.

    private void setAuthenticationToContext(Map<String, Object> claims) {
        String username = (String) claims.get("email");
        List<GrantedAuthority> authorities = authorityUtils.createAuthorities((List<String>) claims.get("roles"));

        Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, authorities);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

 

 오... 나는 String 값인 이메일을 넣어주고 있었다. 그럼 위에 코드에서 String을 받아오면 잘 받아지나? 바로 실행해봤다.

    @PostMapping
    public ResponseEntity<FreeBoardDto.Response> postFreeBoard(@AuthenticationPrincipal String email,
                                                               @RequestBody FreeBoardDto.Post dto) {
        System.out.println(email);
//        FreeBoardDto.Response responseDto = freeBoardService.postFreeBoard(writer, dto);
//        return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
        return null;
    }

 

 

 String 값인 이메일이 출력되었다! 아 저 부분이 문제구나 싶었다. 바로 UserDetails를 넣어주게 바꿨다.

    private void setAuthenticationToContext(Map<String, Object> claims) {
        String username = (String) claims.get("email");
        UserDetails memberDetails = memberDetailsService.loadUserByUsername(username);
        List<GrantedAuthority> authorities = authorityUtils.createAuthorities((List<String>) claims.get("roles"));

        Authentication authentication = new UsernamePasswordAuthenticationToken(memberDetails, null, authorities);
        SecurityContextHolder.getContext().setAuthentication(authentication);
    }

 

 memberDetilasService는 UserDetailsService를 구현한 클래스로 새롭게 필터를 담당하는 클래스에 주입해주었다. 그리고 loadUserByUserName()을 통해 사용자 정보를 뽑아왔고, 이를 Authentication의 Principal에 넣어줬다.

 

 그리고 다시 Member가 제대로 넘어가는지 확인했다.

    @PostMapping
    public ResponseEntity<FreeBoardDto.Response> postFreeBoard(@AuthenticationPrincipal Member writer,
                                                               @RequestBody FreeBoardDto.Post dto) {

        System.out.println("writer-id : " + writer.getId());
        System.out.println("writer-email : " + writer.getEmail());
        System.out.println("writer-name : " + writer.getName());
//        FreeBoardDto.Response responseDto = freeBoardService.postFreeBoard(writer, dto);
//        return new ResponseEntity<>(responseDto, HttpStatus.CREATED);
        return null;
    }

 

 

무 야 호

 당연히 디버깅해서 Member 객체가 잘 넘어왔는지도 확인해봤다. 잘 넘어오더라.

 

 생각해보면 당연했다. @AuthenticationPrincipal 만 봐도, 아 Authentication에 담긴 Principal을 가져오는거구나를 눈치챘어야 했다. 그런데, 내가 Principal을 어디서 넣어주더라? 를 찾아보면 저 필터밖에 없었다. 아직 Spring Security에 익숙하지 못해 이런 초보적인 실수를 저지르지 않았나 싶다. 그래도 뭐 앞으로 공부하면서 알아가면 되는 것 아니겠나? 이번 기회도 좋은 학습 경험으로 삼는다.

반응형

댓글