From 8852a5e1784bbf24fac62a2b4eb1a19f7609db6f Mon Sep 17 00:00:00 2001 From: Juergen Hoeller Date: Fri, 11 Mar 2016 15:04:16 +0100 Subject: [PATCH] Support for placeholders in @CrossOrigin attributes Issue: SPR-14010 --- .../RequestMappingHandlerMapping.java | 20 +++-- .../method/annotation/CrossOriginTests.java | 90 +++++++++++++------ 2 files changed, 73 insertions(+), 37 deletions(-) diff --git a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java index 5ffb6aaf5c8..d74a1bbc353 100644 --- a/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java +++ b/spring-webmvc/src/main/java/org/springframework/web/servlet/mvc/method/annotation/RequestMappingHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -109,7 +109,7 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi @Override public void setEmbeddedValueResolver(StringValueResolver resolver) { - this.embeddedValueResolver = resolver; + this.embeddedValueResolver = resolver; } @Override @@ -314,19 +314,19 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi return; } for (String origin : annotation.origins()) { - config.addAllowedOrigin(origin); + config.addAllowedOrigin(resolveCorsAnnotationValue(origin)); } for (RequestMethod method : annotation.methods()) { config.addAllowedMethod(method.name()); } for (String header : annotation.allowedHeaders()) { - config.addAllowedHeader(header); + config.addAllowedHeader(resolveCorsAnnotationValue(header)); } for (String header : annotation.exposedHeaders()) { - config.addExposedHeader(header); + config.addExposedHeader(resolveCorsAnnotationValue(header)); } - String allowCredentials = annotation.allowCredentials(); + String allowCredentials = resolveCorsAnnotationValue(annotation.allowCredentials()); if ("true".equalsIgnoreCase(allowCredentials)) { config.setAllowCredentials(true); } @@ -334,8 +334,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi config.setAllowCredentials(false); } else if (!allowCredentials.isEmpty()) { - throw new IllegalStateException("@CrossOrigin's allowCredentials value must be \"true\", \"false\", " - + "or an empty string (\"\"); current value is [" + allowCredentials + "]."); + throw new IllegalStateException("@CrossOrigin's allowCredentials value must be \"true\", \"false\", " + + "or an empty string (\"\"): current value is [" + allowCredentials + "]"); } if (annotation.maxAge() >= 0 && config.getMaxAge() == null) { @@ -343,4 +343,8 @@ public class RequestMappingHandlerMapping extends RequestMappingInfoHandlerMappi } } + private String resolveCorsAnnotationValue(String value) { + return (this.embeddedValueResolver != null ? this.embeddedValueResolver.resolveStringValue(value) : value); + } + } diff --git a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CrossOriginTests.java b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CrossOriginTests.java index 482adf87893..e1ee78a52af 100644 --- a/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CrossOriginTests.java +++ b/spring-webmvc/src/test/java/org/springframework/web/servlet/mvc/method/annotation/CrossOriginTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2015 the original author or authors. + * Copyright 2002-2016 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. @@ -22,6 +22,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Method; import java.util.Arrays; +import java.util.Properties; import org.junit.Before; import org.junit.Rule; @@ -29,8 +30,10 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.springframework.beans.DirectFieldAccessor; +import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.core.annotation.AnnotatedElementUtils; import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.env.PropertiesPropertySource; import org.springframework.http.HttpHeaders; import org.springframework.mock.web.test.MockHttpServletRequest; import org.springframework.stereotype.Controller; @@ -72,9 +75,15 @@ public class CrossOriginTests { @Before public void setUp() { + StaticWebApplicationContext wac = new StaticWebApplicationContext(); + Properties props = new Properties(); + props.setProperty("myOrigin", "http://example.com"); + wac.getEnvironment().getPropertySources().addFirst(new PropertiesPropertySource("ps", props)); + wac.registerSingleton("ppc", PropertySourcesPlaceholderConfigurer.class); + wac.refresh(); + this.handlerMapping.setRemoveSemicolonContent(false); - this.handlerMapping.setApplicationContext(new StaticWebApplicationContext()); - this.handlerMapping.afterPropertiesSet(); + wac.getAutowireCapableBeanFactory().initializeBean(this.handlerMapping, "hm"); this.request.setMethod("GET"); this.request.addHeader(HttpHeaders.ORIGIN, "http://domain.com/"); @@ -112,10 +121,10 @@ public class CrossOriginTests { HandlerExecutionChain chain = this.handlerMapping.getHandler(request); CorsConfiguration config = getCorsConfiguration(chain, false); assertNotNull(config); - assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray()); - assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray()); assertTrue(config.getAllowCredentials()); - assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedHeaders().toArray()); assertTrue(CollectionUtils.isEmpty(config.getExposedHeaders())); assertEquals(new Long(1800), config.getMaxAge()); } @@ -127,10 +136,10 @@ public class CrossOriginTests { HandlerExecutionChain chain = this.handlerMapping.getHandler(request); CorsConfiguration config = getCorsConfiguration(chain, false); assertNotNull(config); - assertArrayEquals(new String[]{"DELETE"}, config.getAllowedMethods().toArray()); - assertArrayEquals(new String[]{"http://site1.com", "http://site2.com"}, config.getAllowedOrigins().toArray()); - assertArrayEquals(new String[]{"header1", "header2"}, config.getAllowedHeaders().toArray()); - assertArrayEquals(new String[]{"header3", "header4"}, config.getExposedHeaders().toArray()); + assertArrayEquals(new String[] {"DELETE"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[] {"http://site1.com", "http://site2.com"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[] {"header1", "header2"}, config.getAllowedHeaders().toArray()); + assertArrayEquals(new String[] {"header3", "header4"}, config.getExposedHeaders().toArray()); assertEquals(new Long(123), config.getMaxAge()); assertFalse(config.getAllowCredentials()); } @@ -146,6 +155,17 @@ public class CrossOriginTests { assertTrue(config.getAllowCredentials()); } + @Test + public void customOriginDefinedViaPlaceholder() throws Exception { + this.handlerMapping.registerHandler(new MethodLevelController()); + this.request.setRequestURI("/someOrigin"); + HandlerExecutionChain chain = this.handlerMapping.getHandler(request); + CorsConfiguration config = getCorsConfiguration(chain, false); + assertNotNull(config); + assertEquals(Arrays.asList("http://example.com"), config.getAllowedOrigins()); + assertTrue(config.getAllowCredentials()); + } + @Test public void bogusAllowCredentialsValue() throws Exception { exception.expect(IllegalStateException.class); @@ -162,24 +182,24 @@ public class CrossOriginTests { HandlerExecutionChain chain = this.handlerMapping.getHandler(request); CorsConfiguration config = getCorsConfiguration(chain, false); assertNotNull(config); - assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray()); - assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray()); assertFalse(config.getAllowCredentials()); this.request.setRequestURI("/bar"); chain = this.handlerMapping.getHandler(request); config = getCorsConfiguration(chain, false); assertNotNull(config); - assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray()); - assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray()); assertFalse(config.getAllowCredentials()); this.request.setRequestURI("/baz"); chain = this.handlerMapping.getHandler(request); config = getCorsConfiguration(chain, false); assertNotNull(config); - assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray()); - assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray()); assertTrue(config.getAllowCredentials()); } @@ -191,8 +211,8 @@ public class CrossOriginTests { HandlerExecutionChain chain = this.handlerMapping.getHandler(request); CorsConfiguration config = getCorsConfiguration(chain, false); assertNotNull(config); - assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray()); - assertArrayEquals(new String[]{"http://foo.com"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[] {"http://foo.com"}, config.getAllowedOrigins().toArray()); assertTrue(config.getAllowCredentials()); } @@ -204,8 +224,8 @@ public class CrossOriginTests { HandlerExecutionChain chain = this.handlerMapping.getHandler(request); CorsConfiguration config = getCorsConfiguration(chain, false); assertNotNull(config); - assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray()); - assertArrayEquals(new String[]{"http://foo.com"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[] {"http://foo.com"}, config.getAllowedOrigins().toArray()); assertTrue(config.getAllowCredentials()); } @@ -218,10 +238,10 @@ public class CrossOriginTests { HandlerExecutionChain chain = this.handlerMapping.getHandler(request); CorsConfiguration config = getCorsConfiguration(chain, true); assertNotNull(config); - assertArrayEquals(new String[]{"GET"}, config.getAllowedMethods().toArray()); - assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[] {"GET"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray()); assertTrue(config.getAllowCredentials()); - assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedHeaders().toArray()); assertTrue(CollectionUtils.isEmpty(config.getExposedHeaders())); assertEquals(new Long(1800), config.getMaxAge()); } @@ -236,9 +256,9 @@ public class CrossOriginTests { HandlerExecutionChain chain = this.handlerMapping.getHandler(request); CorsConfiguration config = getCorsConfiguration(chain, true); assertNotNull(config); - assertArrayEquals(new String[]{"*"}, config.getAllowedMethods().toArray()); - assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray()); - assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedHeaders().toArray()); assertTrue(config.getAllowCredentials()); assertTrue(CollectionUtils.isEmpty(config.getExposedHeaders())); assertNull(config.getMaxAge()); @@ -253,9 +273,9 @@ public class CrossOriginTests { HandlerExecutionChain chain = this.handlerMapping.getHandler(request); CorsConfiguration config = getCorsConfiguration(chain, true); assertNotNull(config); - assertArrayEquals(new String[]{"*"}, config.getAllowedMethods().toArray()); - assertArrayEquals(new String[]{"*"}, config.getAllowedOrigins().toArray()); - assertArrayEquals(new String[]{"*"}, config.getAllowedHeaders().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedMethods().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedOrigins().toArray()); + assertArrayEquals(new String[] {"*"}, config.getAllowedHeaders().toArray()); assertTrue(config.getAllowCredentials()); assertTrue(CollectionUtils.isEmpty(config.getExposedHeaders())); assertNull(config.getMaxAge()); @@ -343,8 +363,14 @@ public class CrossOriginTests { @RequestMapping("/customOrigin") public void customOriginDefinedViaValueAttribute() { } + + @CrossOrigin("${myOrigin}") + @RequestMapping("/someOrigin") + public void customOriginDefinedViaPlaceholder() { + } } + @Controller private static class MethodLevelControllerWithBogusAllowCredentialsValue { @@ -354,6 +380,7 @@ public class CrossOriginTests { } } + @Controller @CrossOrigin(allowCredentials = "false") private static class ClassLevelController { @@ -374,14 +401,18 @@ public class CrossOriginTests { } + @Target({ElementType.METHOD, ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @CrossOrigin private @interface ComposedCrossOrigin { + String[] origins() default {}; + String allowCredentials() default ""; } + @Controller @ComposedCrossOrigin(origins = "http://foo.com", allowCredentials = "true") private static class ClassLevelMappingWithComposedAnnotation { @@ -401,6 +432,7 @@ public class CrossOriginTests { } } + private static class TestRequestMappingInfoHandlerMapping extends RequestMappingHandlerMapping { public void registerHandler(Object handler) {