Issue
I'm trying to hot-reload a change in the content security policy (CSP) of my Spring Boot application, i.e. the user should be able to change it via an admin UI without restarting the server.
The regular approach in Spring Boot is:
@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>"
)
)
}
}
... but this doesn't allow for reconfiguration once it has been assigned.
Can I make this (re-)configurable at runtime? Reloading the application context is not an option, I need to be able to adapt only this particular setting.
Solution
Easy-Peasy, we only need to expose a (n appropriate) HeaderWriter
as a bean! ContentSecurityPolicyHeaderWriter
looks appropriate & sufficient for us, but we are also free to implement a custom:
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);
}
Then with:
@Autowired
private ContentSecurityPolicyHeaderWriter myHeadersWriter;
@Override
public void configure(HttpSecurity http) throws Exception {
// ... lots more config here...
http.headers()
.addHeaderWriter(myHeadersWriter);
}
..., we can change the header value with this demo controllers:
@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!";
}
We can test:
@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"));
}
}
... also in browser:
All in one github.(sorry all in main class!:)
Refs: only this
Answered By - xerx593