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) {}
+
+}