쌩로그

Controller의 메서드 중 매개변수로 Principal을 쓸 수 있는 이유 본문

Spring/Spring Security

Controller의 메서드 중 매개변수로 Principal을 쓸 수 있는 이유

.쌩수. 2023. 6. 20. 10:50
반응형

목차

  1. 개요
  2. 본론
      2-1. 요청 Header의 JWT와 인증된 사용자 Principal
      2-2. 예시 사진
      2-3. JWT의 사용자 정보를 SpringSecurityContext에 담는 Filter
      2-4. 확인 과정
      2-5. Authentication 객체와 Principal 객체의 관계
  3. 요약

ChatGPT는 나를 성장시켜준다...

1. 개요

간단하게 결론부터 말하자면 제 곧 내..
(공교롭게도 ㄹㅇ;;;;)

일단 지금 JWT를 서버에서 어떻게 관리할지 고민하고, 알아보던 차에 각 필터 메서드들을 천천히 보다보니...
"어..? 이게 이렇게 된 거였나...?"라는 호기심에 GPT한테 물어보고 진짜냐고 물어봤을 때 맞다고 하길레 포스팅한다.
그냥 지나갔지만, 궁금한 부분이긴 했던터라... 포스팅을 하게되었다.

2. 본론

2-1. 요청 Header의 JWT와 인증된 사용자 Principal의 인과 관계

이전 프로젝트도 그렇고, 전 프로젝트도 그렇고
JWT를 발급 받은 클라이언트는 서버로 요청을 보낼 때 Header의 Authorization JWT 토큰을
Bearer(한칸 띄우기)JWT토큰값123123123 이런식으로 담아서 요청을 보내게 된다.

✅참고로... 저기 중간에 한 칸 띄우는거 무시하면 안 된다... 
콤마 하나로 인증된 사용자임에도 불구하고, NPE(NullPointException)이 나와서 시큐리티 필터를 계속 뚫어져라 봤었다.
그 덕분에 공부도 되었지만, 삽질도 그만큼 많이 했다... 
이 글의 말미에 이와 관련된 포스팅에 당시의 내 모든 감정이 모두 느껴질 것이다...

여튼,
서버로 Header에 인증된 사용자 정보가 담긴 JWT가 담긴 요청이 오면,
// 스프링 시큐리티에서 JWT에 관해 검증을 시도하게되고,
// 이를 통과하면 인증된 사용자라고 서버에서 판별하게 된다.
이 과정에서 Header에 Authorization 키에 JWT가 value로 들어오는데,
API 계층(Controller 쪽)에서 API와 매핑된 메서드의 매개변수인증된 User의 정보가 담긴 Principal 객체를 받을 수가 있는데, 이게 어떻게 가능하냐는 것이다...

JWT는 JWT고, Principal은 Principal인데, JWT를 가져왔다고해서 Pricipal을 매개변수로 받고, 인증된 사용자라고 어떻게 알 수가 있는건지...
사실 구현하면서도 이해하지 못했다...

2-2. 예시 사진

전 프로젝트도 마찬가지였지만, 이번에도 이렇게 했다

JWT를 Header의 Authorization에 담아서 서버로 요청을 보내게 되면
서버에서는 매개변수로 자바에서 정의한 Principal을 사용해서 getName() 메서드를 통해서 email을 추출하고,
추출한 email을 통해서 회원과 연관된 로직이 수행되도록 했다.

2-3. JWT의 사용자 정보를 SpringSecurityContext에 담는 Filter

일단 스프링 시큐리티에서는 갖가지 필터를 지나치게 된다.

그런데 이 중 어떤 필터에서 스프링 시큐리티 컨텍스트 (SecurityContext)에 인증된 사용자의 정보가 담기냐는 것이다..
결론부터 말하면,

여기다!
바로 JwtVerificationFilter...

✅참고로 이 거쳐가는 필터들의 로그로 보려면, Security Configuration(구성정보) 클래스에 debug 모드를 활성화 해주면된다.
아래와 같이..

여기 어떤 메서드가 있는지 아래를 살펴보면

// Authentication 객체를 SecurityContext에 저장하기 위한 메서드  
// 이 메서드가 있기 때문에, 컨트롤러의 매개변수로 Principal을 받을 수 있나..? ㅇㅇ!! 
@SuppressWarnings("unchecked")  
private void setAuthenticationToContext(Map<String, Object> claims) {  
    String username = (String) claims.get("username");  // JWT에서 파싱한 Claims에서 'username'을 얻는다.  
    List<GrantedAuthority> authorities = authorityUtils.createAuthorities((List) claims.get("roles"));  // JWT의 Claims에서 얻은 권한 정보를 기반으로 권한리스트를 얻는다.  
    Authentication authentication = new UsernamePasswordAuthenticationToken(username, null, authorities);   //  
    SecurityContextHolder.getContext().setAuthentication(authentication);   // SecurityContext에 Authentication 객체 저장  
}

doFilterInternal 메서드 안에 setAuthenticationToContext 메서드가 있다.
(참고로 주석을 달면서 작성했지만,, 레퍼런스를 참고하면서 코드를 작성했다.)

일단 구현은 해야하니깐, 레퍼런스의 설명을 보면서, 주석도 작성하면서 메서드를 작성했다.

여튼 이 메서드를 통해서 시큐리티의 컨텍스트에 정보가 담기게 되고 클라이언트가 JWT토큰을 Header에 담아서 서버로 요청을 보내면, 컨트롤러의 각 매핑 메서드에서 매개변수로 인증된 사용자 정보를 Principal객체로 얻어올 수 있는 것이다..

2-4. 확인 과정

개요에서 말한 것처럼 ChatGPT가 말해줬다.

참고로 GPT는 아니면, 아니라고, 확실하지 않다면 미안하다 확실하다고 말한다.
아래 사진처럼...

이번엔 진짜냐고 물어봤는데 맞다고 확신있게 대답해서 고맙다고 했다.

ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

2-5. Authentication 객체와 Principal 객체의 관계

저 글을 보다 보니 SpringSecurity에서 정의한 Principal이라고 말하는데, 지금 내가 사용한 Principal 객체는 java.Security에 정의되어있는 Principal 객체다.

코드 일부만 노출해보면

import java.security.Principal;

@RestController  
@RequestMapping("/members")  
@Slf4j  
public class MemberController {  

    private final MemberService memberService;  
    private final MemberMapper mapper;  

    public MemberController(MemberService memberService, MemberMapper mapper) {  
        this.memberService = memberService;  
        this.mapper = mapper;  
    }  

    // 회원 정보 수정  
    @PatchMapping  
    public ResponseEntity update(Principal principal, @RequestBody MemberDto.Patch patch) {  
        Member findMember = memberService.findMember(principal.getName());   // 이메일 정보로 사용자를 찾아온다.  
        // memberService의 updateMember를 통해 사용자의 정보를 수정한다.   // PatchDto를 mapper를 통해서 엔티티로 매핑한다.  
        Member updatedMember = memberService.updateMember(findMember, mapper.memberDtoPatchToMember(patch));  
        return ResponseEntity.ok(new SingleResponse<>(mapper.memberToMemberResponse(updatedMember)));  
    }

import 부분에 java.security.Principal이 있다. 그래서 역시 GPT한테 물어봤다.

그래서 확인 들어갔다.
인텔리제이에서 Authentication인터페이스의 소스코드를 보니, Spring Security에서 정의한 인터페이스였고, Principal 객체상속받고 있었다.

Principal 객체를 보니.
java.security에서 정의한 객체였음을 확인했다..

3. 요약

클라이언트가 서버로 JWT를 Header에 담아서 요청을 보낼 때,
사용자의 인증정보를 컨트롤러쪽에서 매개변수로 Principal 객체를 받을 수 있는데,
어떻게 인증된 사용자의 정보가 담긴 Principal 객체를 어떤 과정에서 사용할 수 있게 되었는지를 발견하고 알아보았다.

어느 필터에서 어떤 메서드를 통해서 발견하게 되었는지 확인해서 그에 대한 내용을 정리하고 공부하는 차원에서 정리하게 되었다.

번외로 스프링 시큐리티의 인증 객체Authentication자바 시큐리티 라이브러리Principal상속 받는다는 것도 알게 되었다.

참고

ChatGPT
근데 얘가 모든 걸 알려주지는 않기 때문에 적절한 구글링도 필수다..

Bearer 오타 주의 글

끝.

내가 성장하는 것이 다른 사람에게 좋은 영향을 줄 수 있다.

그래서 나는 잘 되야만 한다.

나는 진짜 ㄹㅇ 조만간 잘 될 사람이다.

어차피 잘 될 것이고, 지금도 잘 된 사람이다..

요즘 폭풍 성장 중이다.

어쨋든간 나는 어잘될사다.

728x90
Comments