[스프링(spring)]시큐리티는 어떻게 동작할까??(1/6)@AuthenticationPrincipal 어노테이션

allnight5·2023년 1월 4일
0

스프링

목록 보기
22/62

참조사이트 1
참조사이트2
Authentication 인터페이스를 구현한 UsernamePasswordAuthenticationToken을 SecurityContextHolder에 설정하게 된다.

참조사이트1에서 많은내용을 설명해주신다

스프링 시큐리티는 SecurityContext에 인증된 Authentication 객체를 넣어두고 현재 스레드 내에서 공유되도록 관리하고 있는데요.

우선 알고가야하는 클래스와 인터페이스가 존재한다고 합니다.

기본 클래스와 인터페이스

아래 클래스와 인터페이스에대하여 하나하나 찾아보려고합니다.

SecurityContextHolder : 클래스. 특정 전략에 따라 Security Context 영역을 관리하는 인터페이스입니다. 해당 클래스가 관리하는 SecurityContext 객체는 getter로 접근이 가능하다.(getContext() 메소드) 이는 현재 스레드 기준의SecurityContext를 반환한다.

SecurityContext : 인터페이스. Security Context 인터페이스. Authentication에 대한 접근 getter를 정의해놓음.

SecurityContextImpl : 클래스. SecurityContext 인터페이스를 구현한 객체. Authentication 객체에 대한 getter/setter를 정의해 놓은 객체. 해당 구현체를 통해 내부적으로 현재 스레드의(아마 1번의 request 요청일 것으로 생각되지만..) Security Context 를 생성하여 인증 후 Authentication 객체를 넣어놓는 역할을 한다.

Authentication : 인증 정보에 대한 부분을 정의해놓은 인터페이스. Principal과 Credentials, Authorities 에 대한 정의가 되어있다. 여러 구현체가 있지만 제가 썻던 스프링 시큐리티 내용에서는 UsernamePasswordAuthenticationToken 클래스를 활용했었고

UsernamePasswordAuthenticationToken의 경우
Principal -> Athentication -> AbstratAuthenticationToken -> UsernamePasswordAuthenticationToken
UsernamePasswordAuthenticationToken 구현체 및 세션 정보를 보관하는 객체에서 필요한 정보를 뽑아내는 메소드를 가지고 있습니다

  • principal의 의미는 "인증되는 주체의 ID" 이고,
  • Credentials 은 "주체가 정확한지 증명하는 것",
  • Authorities는 아시다시피 "권한"의 내용입니다.

UserDetails : 사용자 정보(username, password 등)을 가지는 인터페이스. 이를 구현하여 실제 로그인에 사용할 클래스를 만들면 된다. 스프링 시큐리티 내부적으로 직접 사용하지는 않고 Authentication 으로 캡슐화하여 저장된다. 따라서 UserDetails 구현체의 정보는 Spring Security Context에 저장된 Authentication 객체가 가져간다고 볼 수 있다.

HandlerMethodArgumentResolver : 인터페이스. 특정 전략에 따라 한 request에서 넘어온 인자들을 메소드 파라미터로 해석할 수 있도록 도와줌.

AuthenticationPrincipalArgumentResolver : 스프링 시큐리티에서 HandlerMethodArgumentResolver 를 구현한 구현체로 @AuthenticationPrincipal 어노테이션이 실제로 사용되는 부분.

해당 컨트롤러 메소드의 @AuthenticationPrincipal 어노테이션은 실제 어떻게 동작하고 있을까??

현재 스레드가 세션을 물고있고 있다면 이미 SecurityContextHolder의 getContext() 메소드를 통해 SecurityContext 객체를 얻고 그 안의 getAuthentication() 메소드를 통해 Authentication (인증객체)를 얻게 될 것입니다.
아래그림을 보면 AuthenticationPrincipalArgumentResolver가 @AuthenticationPrincipal가 파라미터로 되어있는 부분을 찾아서 거기에 데이터를 넣어준다.

참조사이트1에있는 것을 다시 한번 만들어봤다.

AuthenticationPrincipal는
SecurityContextHolder.getContext().getAuthentication().getPrincipal()라고 간단히 생각할수도있다.

findMethodAnnotaion의 경우 이 클래스안에 있는 메소드 중하나로 어노테이션을 사용한 곳을 찾는데 사용할수있다.

아래와 같이 구성되어있다.
다른 메소드로 검사하고 찾는것같기도하다.?

	private <T extends Annotation> T findMethodAnnotation(Class<T> annotationClass, MethodParameter parameter) {
		T annotation = parameter.getParameterAnnotation(annotationClass);
		if (annotation != null) {
			return annotation;
		}
		Annotation[] annotationsToSearch = parameter.getParameterAnnotations();
		for (Annotation toSearch : annotationsToSearch) {
			annotation = AnnotationUtils.findAnnotation(toSearch.annotationType(), annotationClass);
			if (annotation != null) {
				return annotation;
			}
		}
		return null;
	}

이는 현재 스레드에 Security Context에 저장된 Authentication 객체가 존재하는지에 따라 실행되므로 존재하지 않는다면 구현 메소드 내용과 같이 해당부분은 null로 바인딩 될것이다. Security Context 에 인증정보가 저장되어있다는 전제하이고, 이는 곧 사용자는 로그인하였다는 전제하이다. 또한 개발자가 @AuthenticationPrincipal 어노테이션을 활용하여 인증주체의 ID를 주입한다는 개발내용이 있었다는 것이다.

데이터가 null로 되어있지 않으려면

다른 클래스를 인터페이스인 UserDetails를 상속하여 내용을 구현한 클래스를 활용하여 Authentication에 데이터를 넣어주지 않는다면 항상 null값을가져오게 될것이다.

간단히 살펴보면 세션을 사용한다면 브라우저의 JSESSIONID를 활용하여 사용자 세션정보를 구분할 수 있으므로 특별히 요청 전에 세션정보만 잘 가져온다면 추가적인 처리가 덜한다고 하니 찾아봐야겠다.

JWT 같은 토큰을 활용한다면 요청된 요청마다의 토큰 정보(곧 세션정보가 될)를 읽어 매번 인증을 진행할 것이다. 따라서 매 요청마다 Filter를 활용하여 SecurityContext에 요청마다 인증되는 Authentication 객체를 set 시킬 것이고 이후에 Controller 에서 @AuthenticationPrincipal을 활용하여 가져올 수 있는 부분이다.
또한 principal 을 인증주체의 ID라고 정의하였으니 인증객체의 principal에 로그인 ID를 저장하고 @AuthenticationPrincipal로 Principal을 불러올 때, String loginId와 같이 유니크한 ID 값을 주입받아 해당 아이디를 조회하여 처리하는 부분이 있을 것이고, 비슷하게 principal의 인증주체의 id(혹은 데이터베이스 pk)를 hash 혹은 equals의 key로 설정하여 Unique 특성이 보장된다는 전제하에 principal에 UserDetails 구현 객체(사용자 인증정보)를 담아 직접 객체를 주입하여 처리할 수도 있는 부분이기 때문에 매우 다양하게 사용된다.

profile
공부기록하기

0개의 댓글