diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml index 49efdb8a9..f06c9ffc6 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/pom.xml @@ -24,8 +24,34 @@ org.springframework spring-webmvc - + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-security + + + org.thymeleaf.extras + thymeleaf-extras-springsecurity6 + + 3.1.1.RELEASE + + + org.springframework.security + spring-security-test + test + + + jakarta.servlet jakarta.servlet-api diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletRequest.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletRequest.java index 5108931c9..574b78c1f 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletRequest.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletRequest.java @@ -446,7 +446,7 @@ public class ProxyHttpServletRequest implements HttpServletRequest { @Override public String getScheme() { - throw new UnsupportedOperationException(); + return "https"; } public void setServerName(String serverName) { @@ -455,7 +455,7 @@ public class ProxyHttpServletRequest implements HttpServletRequest { @Override public String getServerName() { - throw new UnsupportedOperationException(); + return "spring-serverless-web-proxy"; } public void setServerPort(int serverPort) { @@ -464,7 +464,7 @@ public class ProxyHttpServletRequest implements HttpServletRequest { @Override public int getServerPort() { - throw new UnsupportedOperationException(); + return 0; } @Override @@ -582,7 +582,7 @@ public class ProxyHttpServletRequest implements HttpServletRequest { */ @Override public boolean isSecure() { - throw new UnsupportedOperationException(); + return false; } @Override @@ -883,7 +883,7 @@ public class ProxyHttpServletRequest implements HttpServletRequest { @Override public StringBuffer getRequestURL() { - throw new UnsupportedOperationException(); + return new StringBuffer(this.requestURI); } public void setServletPath(String servletPath) { @@ -902,6 +902,9 @@ public class ProxyHttpServletRequest implements HttpServletRequest { @Override @Nullable public HttpSession getSession(boolean create) { + if (this.session == null) { + this.session = new ProxyHttpSession(this.servletContext); + } return this.session; } diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpSession.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpSession.java new file mode 100644 index 000000000..0e41f4836 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpSession.java @@ -0,0 +1,112 @@ +/* + * Copyright 2023-2023 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.cloud.function.serverless.web; + +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import jakarta.servlet.ServletContext; +import jakarta.servlet.http.HttpSession; + +/** + * + * @author Oleg Zhurakousky + * @since 4.x + * + * + */ +public class ProxyHttpSession implements HttpSession { + + private final long creationTime; + + private final String sessionId; + + private final ServletContext servletContext; + + private final Map attributes = new HashMap<>(); + + public ProxyHttpSession(ServletContext servletContext) { + this.creationTime = new Date().getTime(); + this.sessionId = UUID.randomUUID().toString(); + this.servletContext = servletContext; + } + + @Override + public long getCreationTime() { + return this.creationTime; + } + + @Override + public String getId() { + return this.sessionId; + } + + @Override + public long getLastAccessedTime() { + return 0; + } + + @Override + public ServletContext getServletContext() { + return this.servletContext; + } + + @Override + public void setMaxInactiveInterval(int interval) { + + } + + @Override + public int getMaxInactiveInterval() { + return 0; + } + + @Override + public Object getAttribute(String name) { + return this.attributes.get(name); + } + + @Override + public Enumeration getAttributeNames() { + return Collections.enumeration(this.attributes.keySet()); + } + + @Override + public void setAttribute(String name, Object value) { + this.attributes.put(name, value); + } + + @Override + public void removeAttribute(String name) { + this.attributes.remove(name); + } + + @Override + public void invalidate() { + + } + + @Override + public boolean isNew() { + return false; + } + +} diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java index 61724b7da..da07ab890 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyMvc.java @@ -170,6 +170,7 @@ public class ProxyMvc { ProxyFilterChain(DispatcherServlet servlet) { List filters = new ArrayList<>(); servlet.getServletContext().getFilterRegistrations().values().forEach(fr -> filters.add(((ProxyFilterRegistration) fr).getFilter())); + servlet.getWebApplicationContext().getBeansOfType(Filter.class).values().forEach(f -> filters.add(f)); Assert.notNull(filters, "filters cannot be null"); Assert.noNullElements(filters, "filters cannot contain null values"); this.filters = initFilterList(servlet, filters.toArray(new Filter[] {})); diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletContext.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletContext.java index 9adab9664..9b0138fb1 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletContext.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyServletContext.java @@ -42,6 +42,8 @@ import jakarta.servlet.descriptor.JspConfigDescriptor; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.util.ClassUtils; + /** * Empty no-op representation of {@link ServletContext} to satisfy required dependencies to * successfully proxy incoming web requests to target web application. @@ -143,7 +145,9 @@ public class ProxyServletContext implements ServletContext { @Override public String getServerInfo() { - return "serverless-web-proxy"; + return ClassUtils.isPresent("com.amazonaws.serverless.proxy.spring.SpringLambdaContainerHandler", ClassUtils.getDefaultClassLoader()) + ? "aws-serverless-java-container/6.0" + : "serverless-web-proxy"; } @Override diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/serverless/web/RequestResponseTests.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/serverless/web/RequestResponseTests.java index 285867bd9..23941a50f 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/serverless/web/RequestResponseTests.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/serverless/web/RequestResponseTests.java @@ -28,6 +28,7 @@ import org.junit.jupiter.api.Test; import org.springframework.cloud.function.test.app.Pet; import org.springframework.cloud.function.test.app.PetStoreSpringAppConfig; import org.springframework.http.HttpStatus; +import org.springframework.security.test.context.support.WithMockUser; import static org.assertj.core.api.Assertions.assertThat; @@ -77,6 +78,7 @@ public class RequestResponseTests { assertThat(pets.get(0)).isInstanceOf(Pet.class); } + @WithMockUser("spring") @Test public void validateGetPojo() throws Exception { HttpServletRequest request = new ProxyHttpServletRequest(null, "GET", "/pets/6e3cc370-892f-4efe-a9eb-82926ff8cc5b"); diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetStoreSpringAppConfig.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetStoreSpringAppConfig.java index 5943efdf2..91a59e19c 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetStoreSpringAppConfig.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/test/java/org/springframework/cloud/function/test/app/PetStoreSpringAppConfig.java @@ -17,24 +17,44 @@ package org.springframework.cloud.function.test.app; import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; import jakarta.servlet.Filter; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.ServletRequest; import jakarta.servlet.ServletResponse; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpSession; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.core.authority.SimpleGrantedAuthority; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.DefaultSecurityFilterChain; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.context.SecurityContextHolderFilter; +import org.springframework.security.web.csrf.CsrfFilter; +import org.springframework.web.filter.GenericFilterBean; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerMapping; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; + @Configuration @Import({ PetsController.class }) +@EnableWebSecurity public class PetStoreSpringAppConfig { + /* * Create required HandlerMapping, to avoid several default HandlerMapping * instances being created @@ -53,6 +73,53 @@ public class PetStoreSpringAppConfig { return new RequestMappingHandlerAdapter(); } + @Bean + public BeanPostProcessor post() { + return new BeanPostProcessor() { + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (beanName.equals("securityFilterChain")) { + DefaultSecurityFilterChain chain = (DefaultSecurityFilterChain) bean; + ArrayList filters = new ArrayList<>(); + chain.getFilters().forEach(f -> { + if (!(f instanceof CsrfFilter)) { + filters.add(f); + } + }); + bean = new DefaultSecurityFilterChain(chain.getRequestMatcher(), filters); + } + //System.out.println(beanName); + return bean; + } + }; + } + + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .addFilterBefore(new GenericFilterBean() { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + SecurityContext securityContext = SecurityContextHolder.getContext(); + securityContext.setAuthentication(UsernamePasswordAuthenticationToken.authenticated("user", "password", + Collections.singleton(new SimpleGrantedAuthority("USER")))); + HttpSession session = ((HttpServletRequest) request).getSession(); + session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext); + chain.doFilter(request, response); + } + }, SecurityContextHolderFilter.class) + .authorizeHttpRequests((requests) -> requests + .requestMatchers("/", "/pets", "/pets/").permitAll() + .anyRequest().authenticated() + ) + .logout((logout) -> logout.permitAll()); + + return http.build(); + } + @Bean public Filter filter() { return new Filter() {