작성
·
604
1
안녕하세요 강의 잘 보고 있습니다.
다름이 아니라 제가 Spring Security로 예제를 만들어보고 있었습니다. 그런데 익명 사용자인 경우 컨트롤러의 Authentication 파라미터로 주입되지 않더라고요.
@GetMapping("/authentication")
@ResponseBody
public String authenticationTest(Authentication authentication)
예를 들면 컨트롤러에 이런 함수를 만들면 일반적인 인증 사용자인 경우에는 Authentication 객체가 주입되지만 익명 사용자인 경우에는 Authentication객체가 주입되지 않았습니다.
이유를 찾아보니 표준 서블릿 API 스펙을 따르기 위해서라고 합니다. 다른 응용 프로그램이 spring security의 구현에 의지하지 않도록 하기 위해서고 굳이 익명 사용자인 경우에도Authentication 객체를 주입받고 싶으면 spring security의 getContext().getAuthentication() api를 사용하면 된다고 합니다. 그래서 이유에 대해서는 제가 이렇게 찾아서 궁금한 부분은 아닌데요.
https://github.com/spring-projects/spring-security/issues/4011
이 링크에 가보시면 내부적으로 왜 Authentication 객체가 익명 사용자인 경우 컨트롤러에 주입이 안 되는지 나온 부분이 있습니다.
SecurityContextHolderAwareRequestWrapper이 객체에 아래와 같은 부분이 있다는 것입니다.
private Authentication getAuthentication() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (!trustResolver.isAnonymous(auth)) {
return auth;
}
return null;
}
SecurityContextHolderAwareRequestWrapper 객체는 HttpServletRequest가 RequestCacheAwareFilter를 통과하면서 재구성된 request 객체라고 합니다.
그래서 SecurityContextHolderAwareRequestWrapper객체에서 getAuthentication()라는 함수를 호출해서 Controller의 Authentication 타입 파라미터에 주입해주는 Security전용 HandlerMethodArgumentResolver가 있을 것 같은데, 제가 아무리 찾아봐도 정확히 이런 Resolver가 있다는 글을 못 찾겠더라고요.
혹시 아신다면 알려주시면 감사하겠습니다.
답변 2
3
네
저도 여러 가지로 찾아 봤지만 아직은 찾지 못했습니다.
그리고 답변을 떠나 위와 같은 질문의 방식과 해결방안을 찾아가는 과정은 훌륭한 개발자로 성장하는 좋은 마인드라고 생각합니다.
정답을 찾으면 다시 답변하도록 하겠습니다.
0
저도 이게 궁금해서 디버거를 걸어봤는데요
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {
private final List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
@Override
@Nullable
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
디스패처 서블릿에서 HandlerAdapter 로 핸들러 처리를 위임하고(RequestMappingHandlerResolver) 쭉 타고 가다보면
HandlerMethodArgumentResolverComposite 로 올 수 있습니다.
우리 메서드의 arugment를 실제 resolve 하는 argument REsolver가 나오는 지점에 breakpoint를 걸고 확인해봤어요.
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);
}
ServletRequestMethodArgumentResolver 여기서 파라미터에 어노테이션이 없는 Principal 을 잡아 처리하네요!
else if (Principal.class.isAssignableFrom(paramType)) {
Principal userPrincipal = request.getUserPrincipal();
if (userPrincipal != null && !paramType.isInstance(userPrincipal)) {
throw new IllegalStateException(
"Current user principal is not of type [" + paramType.getName() + "]: " + userPrincipal);
}
return userPrincipal;
}
resolveArgument 메서드에 가보면 request(요청객체) 에서 getPrincipal 을 하고 그것을 반환하는 구조로 되어 있네요.
그리고 작성자님께서 언급하신
SecurityContextHolderAwareRequestWrapper 쪽에 가보면
private Authentication getAuthentication() {
Authentication auth = this.securityContextHolderStrategy.getContext().getAuthentication();
return (this.trustResolver.isAuthenticated(auth)) ? auth : null;
}
이 request 객체가 securityContextHolderStrategy 를 통해 authentication 을 찾아 반환하는 구조로 되어있어요.
감사합니다!