我正在尝试重新加载Spring Boot应用程序的内容安全策略(CSP)的更改,即用户应该能够通过管理员UI更改它而无需重新启动服务器。
Spring Boot中的常规方法是:
@Configuration
class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) {
// ... lots more config here...
http.headers()
.addHeaderWriter(
StaticHeadersWriter(
"Content-Security-Policy",
"<some policy string>"
)
)
}
}
…但是一旦被分配,这就不允许重新配置。
我可以在运行时使其(重新)可配置吗?重新加载应用程序上下文不是一个选项,我需要能够只适应这个特定的设置。
Easy-PEasy,我们只需要将(n适当的)HeaderWriter
公开为bean!ContentSecurityPolicHeaderWriter
看起来很合适
private static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
@Bean
public ContentSecurityPolicyHeaderWriter myWriter(
@Value("${#my.policy.directive:DEFAULT_SRC_SELF_POLICY}") String initalDirectives
) {
return new ContentSecurityPolicyHeaderWriter(initalDirectives);
}
然后用:
@Autowired
private ContentSecurityPolicyHeaderWriter myHeadersWriter;
@Override
public void configure(HttpSecurity http) throws Exception {
// ... lots more config here...
http.headers()
.addHeaderWriter(myHeadersWriter);
}
…,我们可以使用这些演示控制器更改标头值:
@GetMapping("/")
public String home() {
myHeadersWriter.setPolicyDirectives(DEFAULT_SRC_SELF_POLICY);
return "header reset!";
}
@GetMapping("/foo")
public String foo() {
myHeadersWriter.setPolicyDirectives("FOO");
return "Hello from foo!";
}
@GetMapping("/bar")
public String bar() {
myHeadersWriter.setPolicyDirectives("BAR");
return "Hello from bar!";
}
我们可以测试:
@SpringBootTest
@AutoConfigureMockMvc
class DemoApplicationTests {
@Autowired
private MockMvc mockMvc;
@Test
public void testHome() throws Exception {
this.mockMvc.perform(get("/"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("header reset!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, DEFAULT_SRC_SELF_POLICY));
}
@Test
public void testFoo() throws Exception {
this.mockMvc.perform(get("/foo"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello from foo!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, "FOO"));
}
@Test
public void testBar() throws Exception {
this.mockMvc.perform(get("/bar"))
.andDo(print())
.andExpect(status().isOk())
.andExpect(content().string(containsString("Hello from bar!")))
.andExpect(header().string(CONTENT_SECURITY_POLICY_HEADER, "BAR"));
}
}
…也在浏览器中:
所有在一个github。(对不起,所有在主类!:)
裁判:只有这个
(我)接受的答案的问题是:
(只是为了展示案例,但是:)我们在(每个)请求上修改“单例范围属性”!!!
当我们添加这样的“压力”测试包装时。
(…等到所有线程在java完成它们的工作??-
它严重失败(标头没有“预期”值):
@Test
void testParallel() throws Exception {
// 200 cycles, with 0 (== #cpu) threads ...
final StressTester<Void> stressTestHome = new StressTester<>(Void.class, 200, 0, // ... and these (three) jobs (firing requests at our app):
() -> {
home(); // here the original tests
return null;
},
() -> {
foo(); // ... with assertions ...
return null;
},
() -> {
bar(); // ... moved to private (non Test) methods
return null;
}
);
stressTestHome.test(); // run it, collect it and:
stressTestHome.printErrors(System.out);
assertTrue(stressTestHome.getExceptionList().isEmpty());
}
与模拟一样,与(完整)服务器模式一样…;(;(;(
当我们想从较低的作用域(比单例…所以任何其他作用域)更改该标头时,我们会遇到同样的问题:) ;(;(;(
如果我们想要该标头的单例范围策略,并且只触发重新加载(对于所有后续请求),我们可以停止阅读。(答案1是确定的,因为我实际上“最初理解”了这个问题
但是如果我们想要带有spring-security的“per request header”,我们必须通过这个测试!:)
一种可能的解决方案:方法注入!
所以回到我们自定义的HeaderWriter
实现:
package com.example.demo;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.security.web.header.HeaderWriter;
// abstract!
public abstract class MyContentSecurityPolicyHeaderWriter implements HeaderWriter {
// ... no state!!!
public static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy";
public static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";
@Override // how cool, that there is a HttpServletRequest/-Response "at hand" !?!
public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
if (!response.containsHeader(CONTENT_SECURITY_POLICY_HEADER)) {
// responsible for the header key, but for the value we ask: delegate
response.setHeader(CONTENT_SECURITY_POLICY_HEADER, policyDelegate().getPolicyDirectives());
}
}
// TLDR xDxD
protected abstract MyContentSecurityDelegate policyDelegate();
}
再次感谢!;)
有了这个微小的(但受管理的)“上下文持有者”:
package com.example.demo;
import lombok.*;
@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class MyContentSecurityDelegate {
@Getter
@Setter
private String policyDirectives;
}
我们这样做(使用spring-java-config,如何使用@Bean创建bean在Spring启动抽象类):
@Configuration
class FreakyConfig {
@Value("${my.policy.directive:DEFAULT_SRC_SELF_POLICY}")
private String policy;
@Bean
@RequestScope // !! (that is suited for our controllers)
public MyContentSecurityDelegate delegate() {
return MyContentSecurityDelegate.of(policy);
}
@Bean
public MyContentSecurityPolicyHeaderWriter myWriter() {
return new MyContentSecurityPolicyHeaderWriter() { // anonymous inner class
@Override
protected MyContentSecurityDelegate policyDelegate() {
return delegate(); // with request scoped delegate.
}
};
}
}
…然后我们的控制器这样做(自动配线
@Autowired // !
private MyContentSecurityDelegate myRequestScopedDelegate;
@GetMapping("/foo")
public String foo() {
// !!
myRequestScopedDelegate.setPolicyDirectives("FOO");
return "Hello from foo!";
}
然后所有测试都通过!:)推送到(同一个)github。
但是为了实现目标:“写入标头请求(甚至线程)特定”,我们可以使用任何其他技术(匹配我们的堆栈
>
servlet
>
带spring-mvc/不带
java x. ser v let.*
:
任何作为Spring bean的Servlet
、Filter
或servlet*Listener
实例都向嵌入式容器注册。
从将Servlet、过滤器和侦听器注册为Spring Bean
或反应性…
Mo'链接:
编码快乐!