diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PrincipalMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PrincipalMethodArgumentResolver.java new file mode 100644 index 0000000000..cff75ca7d1 --- /dev/null +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/PrincipalMethodArgumentResolver.java @@ -0,0 +1,68 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.web.servlet.mvc.method.annotation; + +import java.security.Principal; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.core.MethodParameter; +import org.springframework.lang.Nullable; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * Resolves an argument of type {@link Principal}, similar to + * {@link ServletRequestMethodArgumentResolver} but irrespective of whether the + * argument is annotated or not. This is doen to enable custom argument + * resolution of a {@link Principal} argument (with custom annotation). + * + * @author Rossen Stoyanchev + * @since 5.3.1 + */ +public class PrincipalMethodArgumentResolver implements HandlerMethodArgumentResolver { + + + @Override + public boolean supportsParameter(MethodParameter parameter) { + Class paramType = parameter.getParameterType(); + return Principal.class.isAssignableFrom(paramType); + } + + @Override + public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception { + + Class paramType = parameter.getParameterType(); + + HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); + if (request == null) { + throw new IllegalStateException( + "Current request is not of type [HttpServletRequest]: " + webRequest); + } + + Principal principal = request.getUserPrincipal(); + if (principal != null && !paramType.isInstance(principal)) { + throw new IllegalStateException( + "Current user principal is not of type [" + paramType.getName() + "]: " + principal); + } + + return principal; + } + +} diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java index 12a3229404..6b05122002 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerAdapter.java @@ -715,6 +715,7 @@ public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter } // Catch-all + resolvers.add(new PrincipalMethodArgumentResolver()); resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true)); return resolvers; diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java index 43560bdc6f..45ca54595e 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/ServletRequestMethodArgumentResolver.java @@ -50,7 +50,9 @@ import org.springframework.web.servlet.support.RequestContextUtils; *
  • {@link MultipartRequest} *
  • {@link HttpSession} *
  • {@link PushBuilder} (as of Spring 5.0 on Servlet 4.0) - *
  • {@link Principal} + *
  • {@link Principal} but only if not annotated in order to allow custom + * resolvers to resolve it, and the falling back on + * {@link PrincipalMethodArgumentResolver}. *
  • {@link InputStream} *
  • {@link Reader} *
  • {@link HttpMethod} (as of Spring 4.0) diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/PrincipalMethodArgumentResolverTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/PrincipalMethodArgumentResolverTests.java new file mode 100644 index 0000000000..4fa489195c --- /dev/null +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/PrincipalMethodArgumentResolverTests.java @@ -0,0 +1,110 @@ +/* + * Copyright 2002-2020 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.servlet.mvc.method.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; +import java.lang.reflect.Method; +import java.security.Principal; + +import javax.servlet.ServletRequest; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.core.MethodParameter; +import org.springframework.web.context.request.ServletWebRequest; +import org.springframework.web.method.support.ModelAndViewContainer; +import org.springframework.web.testfixture.servlet.MockHttpServletRequest; +import org.springframework.web.testfixture.servlet.MockHttpServletResponse; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link PrincipalMethodArgumentResolver}. + * + * @author Rossen Stoyanchev + */ +public class PrincipalMethodArgumentResolverTests { + + private PrincipalMethodArgumentResolver resolver; + + private ModelAndViewContainer mavContainer; + + private MockHttpServletRequest servletRequest; + + private ServletWebRequest webRequest; + + private Method method; + + + @BeforeEach + public void setup() throws Exception { + resolver = new PrincipalMethodArgumentResolver(); + mavContainer = new ModelAndViewContainer(); + servletRequest = new MockHttpServletRequest("GET", ""); + webRequest = new ServletWebRequest(servletRequest, new MockHttpServletResponse()); + + method = getClass().getMethod("supportedParams", ServletRequest.class, Principal.class); + } + + + @Test + public void principal() throws Exception { + Principal principal = () -> "Foo"; + servletRequest.setUserPrincipal(principal); + + MethodParameter principalParameter = new MethodParameter(method, 1); + assertThat(resolver.supportsParameter(principalParameter)).as("Principal not supported").isTrue(); + + Object result = resolver.resolveArgument(principalParameter, null, webRequest, null); + assertThat(result).as("Invalid result").isSameAs(principal); + } + + @Test + public void principalAsNull() throws Exception { + MethodParameter principalParameter = new MethodParameter(method, 1); + assertThat(resolver.supportsParameter(principalParameter)).as("Principal not supported").isTrue(); + + Object result = resolver.resolveArgument(principalParameter, null, webRequest, null); + assertThat(result).as("Invalid result").isNull(); + } + + @Test // gh-25780 + public void annotatedPrincipal() throws Exception { + Principal principal = () -> "Foo"; + servletRequest.setUserPrincipal(principal); + Method principalMethod = getClass().getMethod("supportedParamsWithAnnotatedPrincipal", Principal.class); + + MethodParameter principalParameter = new MethodParameter(principalMethod, 0); + assertThat(resolver.supportsParameter(principalParameter)).isTrue(); + } + + + @SuppressWarnings("unused") + public void supportedParams(ServletRequest p0, Principal p1) {} + + @Target({ ElementType.PARAMETER }) + @Retention(RetentionPolicy.RUNTIME) + public @interface AuthenticationPrincipal {} + + @SuppressWarnings("unused") + public void supportedParamsWithAnnotatedPrincipal(@AuthenticationPrincipal Principal p) {} + +}