GH-1034 Add implementation of ProxyHttpSession, fix Spring Security interaction

This commit is contained in:
Oleg Zhurakousky
2023-05-10 07:47:46 +02:00
parent 74aaadea98
commit 05685c647c
7 changed files with 222 additions and 7 deletions

View File

@@ -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;
}

View File

@@ -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<String, Object> 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<String> 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;
}
}

View File

@@ -170,6 +170,7 @@ public class ProxyMvc {
ProxyFilterChain(DispatcherServlet servlet) {
List<Filter> 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[] {}));

View File

@@ -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

View File

@@ -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");

View File

@@ -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<Filter> 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() {