땃쥐네

[Spring Security] 컨트롤러에서 Authentication 파라미터 사용 본문

Spring/Spring Security

[Spring Security] 컨트롤러에서 Authentication 파라미터 사용

ttasjwi 2024. 6. 12. 17:40

 

1. 의문점

    @GetMapping("user")
    fun user(authentication: Authentication): OAuth2User {
        val token = authentication as OAuth2AuthenticationToken
        val user = token.principal
        return user
    }

 

스프링 시큐리티를 사용할 때, Authentication 객체를 컨트롤러에서 바로 바인딩받아 사용할 수 있다.

그런데 이 Authentication 객체는 어떻게 주입되는걸까?

 

 

2. 디버거로 찾기: 어느 HandlerMethodArguemntResolver가 작동했을까? 

컨트롤러의 파라미터 바인딩 동작 확인은 어떤 HandlerMethodArguementResolver가 작동했는 지 확인해야한다.

ArgumentResolver는 우리의 웹요청을 기반으로 컨트롤러의 파라미터에 주입되는 인자(Arguement) 객체를 만들어주는 역할을 수행한다.

 

그런데 어떤 ArguementResolver 가 작동했는 지 알려면 디버거를 걸어봐야한다.

스프링 MVC의 시작점인 DispatcherServlet부터 시작해서 따라가 찾아보기로 했다. 

 

2.1 DispatcherServlet

 

디스패처 서블릿에서 HandlerAdapter.handler 하는 부분에 디버거를 걸어보면

RequestMappingHandlerAdapter가 핸들러 처리를 실질적으로 수행하는 것을 볼 수 있다.

 

RequestMappingHandlerAdapter는 우리가 Spring MVC를 사용할 때, 컨트롤러에 대한 처리를 우선적으로 담당하는 HandlerAdapter구현체이다.

 

2.2 RequestMappingHandlerAdapter

 

RequestMappingHandlerAdapter 에서는 내부적으로 InvocalbeMethod 객체를 생성해서 invokeAndHandle 을 호출하여 실질적으로 컨트롤러의 메서드를 실행한다.

 

2.3 InvocalbeMethod

InvocableHandlerMethod 는 내부적으로 HandlerMethodArgumentResolverComposite 를 가진다. 이것은 여러개의 HandlerMethodArgumentResolver 목록을 가지고 있고 이들에게 argument에 대한 resolve를 위임한다.

 

2.4 HandlerMethodArgumentResolverComposite

 

디버거를 걸어보면 실제 resolve를 수행하는 HandlerMethodArgumentResolver 는 ServletRequestMethodArguementResolver 임을 알 수 있다.

 

 

3. ServletRequestMethodArguementResolver

public class ServletRequestMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return (WebRequest.class.isAssignableFrom(paramType) ||
				ServletRequest.class.isAssignableFrom(paramType) ||
				MultipartRequest.class.isAssignableFrom(paramType) ||
				HttpSession.class.isAssignableFrom(paramType) ||
				PushBuilder.class.isAssignableFrom(paramType) ||
				(Principal.class.isAssignableFrom(paramType) && !parameter.hasParameterAnnotations()) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}

 

supportsParameter 에서, 어노테이션이 걸려있지 않은 Principal 을 잡아 가로채는 것을 확인할 수 있다.

 

public interface Authentication extends Principal, Serializable {

 

org.springframework.security.core.Authentication 은 java.security.Principal 의 하위 인터페이스이므로 여기서 가로채이는 것이다.

 

 

 

여기서, resolveArgument 를 보면 Principal 을 resolve 할 때 request 객체로부터 getUserPrincipal 을 통해 Principal 을 얻어오는 것을 알 수 있다.

 

이 때 사용되는 request 구현체는 SecurityContextHolderAwareRequestWrapper 클래스의 인스턴스임을 알 수 있다.

 

	@Override
	public Principal getUserPrincipal() {
		Authentication auth = getAuthentication();
		if ((auth == null) || (auth.getPrincipal() == null)) {
			return null;
		}
		return auth;
	}

	private Authentication getAuthentication() {
		Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication();
		return (this.trustResolver.isAuthenticated(auth)) ? auth : null;
	}

 

SecurityContextHolderAwareRequestWrapper 내부 코드를 까보면

 

SecurityContextHolderStrategy 를 통해 Authentication 을 가져오고

인증된 Authentication 은 Authentication 을 그대로 반환하고, 인증되지 않았다면 null 을 반환하는 것을 알 수 있다.

 

스프링 시큐리티 인증/인가 프로세스 과정에서 미인증 사용자 인증객체는 AnonymousAuthenticationToken 형태로 SecurityContextHolderStrategy에 저장되는데, 이것을 그대로 반환하지 않고 null로 반환하는 점은 주의할 포인트다.

 

따라서 컨트롤러에서 Authentication 을 바인딩받아 사용할 때는 미인증 사용자(익명 사용자)는 AnonymousAuthenticationToken 이 아닌 null로 바인딩되는 점에 주의해야한다.

 

 

4. 정리

    @GetMapping("user")
    fun user(authentication: Authentication): OAuth2User {
        val token = authentication as OAuth2AuthenticationToken
        val user = token.principal
        return user
    }

 

- 컨트롤러에서 Authentication 을 바인딩받아서 사용할 수 있다.

- 이 Authentication을 만들어주는 HandlerMethodArguemntResolver 는 ServletRequestMethodArguementResolver 이다.

- 스프링 시큐리티를 사용할 경우 request 객체는 SecurityContextHolderAwareRequestWrapper 인데 이것은 내부적으로 SecurityContextHolderStratetgy를 갖고 있다.

- 인증된 사용자의 경우 Authentication을 가져와 그대로 반환한다.

- 미인증 사용자(익명사용자)의 경우 AnonymousAuthenticationToken이 주입되지 않고, null 이 바인딩된다.

- 이 구현사항은 Spring Security 6.2.4 기준이며 이후 스프링 시큐리티 내부 구현 변경에 따라 달라질 수 있다.

 

 

 

Comments