사실 저 애너테이션을 너무너무 써보고 싶었다. 부트캠프에서 프로젝트를 진행할 때부터, 저 애너테이션으로 사용자 정보를 한 번에 가져올 수 있다면 얼마나 좋을까 생각했다. 왜? 나는 안되니까...
정말 많은 글들에서 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객체를 가져오고자 한다. 실행하면?
진짜... 개발하면서 가장 꼴보기 싫은 예외 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에 익숙하지 못해 이런 초보적인 실수를 저지르지 않았나 싶다. 그래도 뭐 앞으로 공부하면서 알아가면 되는 것 아니겠나? 이번 기회도 좋은 학습 경험으로 삼는다.
'Programming > 삽질일지' 카테고리의 다른 글
[1/19 TIS] 바뀐 Shell, 몰랐던 나, 환경변수 설정에 애먹기 (0) | 2024.01.19 |
---|---|
[12/26 TIS] 혹시 이 글을 보고 계신다면 제발 도와주세요. (1) | 2023.12.27 |
[11/21 TIS] 아니 그니까 그게 왜 비어있는데요; - 메소드 mocking 시 객체를 넘겨주려면 equals()와 hashCode()를 구현하라 (1) | 2023.11.21 |
[11/20 TIS] git push가 먹통이 된 날 (2) | 2023.11.20 |
[8/29 TIS] 나의 리팩토링일지 - 객체가 알아야 할 것은 무엇인가? (0) | 2023.08.29 |
댓글