일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- 트랜잭션
- springdataredis
- docker
- 스프링
- 도커
- 데이터베이스
- 액세스토큰
- springsecurityoauth2client
- yaml-resource-bundle
- 스프링시큐리티
- 티스토리챌린지
- oauth2
- Spring
- 파이썬
- 리프레시토큰
- githubactions
- springsecurity
- 메시지
- JIRA
- 토이프로젝트
- AWS
- 프로그래머스
- 재갱신
- 국제화
- 오블완
- java
- 소셜로그인
- CI/CD
- 스프링부트
- 백준
- Today
- Total
땃쥐네
[Spring Security] 컨트롤러에서 Authentication 파라미터 사용 본문
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 기준이며 이후 스프링 시큐리티 내부 구현 변경에 따라 달라질 수 있다.
'Spring > Spring Security' 카테고리의 다른 글
[Spring Security] 스프링 시큐리티와 코틀린 DSL (0) | 2024.03.09 |
---|---|
[Spring Security] 스프링부트 시큐리티 의존성 추가로 일어나는 일들 (0) | 2024.03.09 |