简体   繁体   English

Spring 引导:如何在运行时更改内容安全策略?

[英]Spring Boot: How to change the Content Security Policy at runtime?

I'm trying to hot-reload a change in the content security policy (CSP) of my Spring Boot application, ie the user should be able to change it via an admin UI without restarting the server.我正在尝试热重新加载我的 Spring 引导应用程序的内容安全策略 (CSP) 的更改,即用户应该能够通过管理 UI 更改它而无需重新启动服务器。

The regular approach in Spring Boot is: 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>"
                 )
            )
    } 
}

... 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.重新加载应用程序上下文不是一个选项,我只需要能够调整这个特定的设置。

Easy-Peasy;十分简单; ;) ;)

We only need to implement our custom, smarter (singleton bean?;;) HeaderWriter .我们只需要实现我们自定义的、更智能的(单例 bean?;;) HeaderWriter Unfortunately we cannot extend ContentSecurityPolicyHeaderWriter , since it is final, but Ctrl+C , Ctrl+V ;), there we go:不幸的是,我们不能扩展ContentSecurityPolicyHeaderWriter ,因为它是最终的,但是Ctrl+CCtrl+V ;),我们有 go:

@Getter // lombok
@Setter // lombok
@Component // (spring) for "cool things" ...
static final class MyContentSecurityPolicyHeaderWriter implements HeaderWriter {

  public static final String CONTENT_SECURITY_POLICY_HEADER = "Content-Security-Policy";

  public static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'";

  // ...like this!
  @Value("${my.policy.directive:DEFAULT_SRC_SELF_POLICY}")
  private String policyDirectives;

  /**
   * Creates a new instance. Default value: default-src 'self'
   */
  public MyContentSecurityPolicyHeaderWriter() {
    setPolicyDirectives(DEFAULT_SRC_SELF_POLICY);
  }

  /**
   * Creates a new instance
   *
   * @param policyDirectives maps to {@link #setPolicyDirectives(String)}
   * @throws IllegalArgumentException if policyDirectives is null or empty
   */
  public MyContentSecurityPolicyHeaderWriter(String policyDirectives) {
    setPolicyDirectives(policyDirectives);
  }

  /**
   * @see org.springframework.security.web.header.HeaderWriter#writeHeaders(jakarta.servlet.http.HttpServletRequest,
   * jakarta.servlet.http.HttpServletResponse)
   */
  @Override
  public void writeHeaders(HttpServletRequest request, HttpServletResponse response) {
    if (!response.containsHeader(CONTENT_SECURITY_POLICY_HEADER)) {
      response.setHeader(CONTENT_SECURITY_POLICY_HEADER, policyDirectives);
    }
  }

  /**
   * Sets the security policy directive(s) to be used in the response header.
   *
   * @param policyDirectives the security policy directive(s)
   * @throws IllegalArgumentException if policyDirectives is null or empty
   */
  public void setPolicyDirectives(String policyDirectives) {
    Assert.hasLength(policyDirectives, "policyDirectives cannot be null or empty");
    this.policyDirectives = policyDirectives;
  }

  @Override
  public String toString() {
    return getClass().getName() + " [policyDirectives=" + policyDirectives + "]";
  }

}

(We deleted the report* stuff, made it a @Component , and the policyDirectives accessible.) (我们删除了report*内容,将其设为@Component ,并且policyDirectives可访问。)

Then with:然后:

@Autowired
private MyContentSecurityPolicyHeaderWriter myHeadersWriter;

@Override
public void configure(HttpSecurity http) throws Exception {
  // ... lots more config here...
  http.headers()
    .addHeaderWriter(myHeadersWriter);
}

And 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.多合一 github。 (sorry all in main class::) (对不起所有主要的 class::)


Refs: only this参考: 只有这个

The problem with the (my) accepted answer is: (我的)接受的答案的问题是:

(just for the show case, but:) We modify "singleton scope property" on (every) request!!! (仅用于展示案例,但是:)我们在(每个)请求上修改“单例 scope 属性”!!!

When we add a "stress" test wrapper like this .当我们像这样添加“压力”测试包装器时。

(... wait until all threads finish their work in java ?? -> ExecutorCompletionService , since Java:1.5;) (...等到所有线程在 java ?? -> ExecutorCompletionService中完成它们的工作,因为 Java:1.5;)

It badly fails (header has not the "expected" value):严重失败(标题没有“预期”值):

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

As in mock as in (full) server mode... ;(;(;(在模拟和(完整)服务器模式中一样...... ;(;(;(

We will encounter the same problem, when we want to change that header from a "lower scope" (than singleton ..so any other scope:);(;(;(我们遇到同样的问题,当我们想从“较低的范围”(比 singleton ..so 任何其他 scope:) 更改 header 时

If we want singleton scope policy for that header, and only "trigger the reload" (for all subsequent requests), we can stop reading.如果我们想要header 的 singleton scope 策略,并且只“触发重新加载”(对于所有后续请求),我们可以停止阅读。 (answer 1 is ok, as i actually "initially understood" the question & answered:) (答案 1 没问题,因为我实际上“最初理解”了这个问题并回答了:)

But if we want that "per request header" with , we have to pass this test: :)但是如果我们想要的“每个请求标头” 我们必须通过这个测试::)


One possible solution: Method Injection !一种可能的解决方案: 方法注入

So back to our custom HeaderWriter implementation:回到我们自定义的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();
}

Thanks, again;;) 再次感谢;;)

With this tiny (but managed) "context holder":有了这个小小的(但有管理的)“上下文持有者”:

package com.example.demo;
import lombok.*;

@NoArgsConstructor
@AllArgsConstructor(staticName = "of")
public class MyContentSecurityDelegate {

  @Getter
  @Setter
  private String policyDirectives;
}

We do this (with ):我们这样做(使用 ):

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

..then our controllers do that (autowire & "talk" to the delegate): ..然后我们的控制器会这样做(自动连接并与代表“交谈”):

@Autowired // !
private MyContentSecurityDelegate myRequestScopedDelegate;

@GetMapping("/foo")
public String foo() {
  // !!
  myRequestScopedDelegate.setPolicyDirectives("FOO");
  return "Hello from foo!";
}

Then all tests pass: :) pushed to (same)github .然后所有测试都通过::)推送到(相同的)github


But to achieve the goal: "Write headers request (even thread) specific", we can use any other technique (matching our stack & needs, beyond ):但是为了实现目标:“写头请求(甚至线程)特定”,我们可以使用任何其他技术(匹配我们的堆栈和需求,除了 ):

Mo' Links:莫链接:

Happy Coding!快乐编码!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何在Spring Security中为选择性的HTTP模式启用内容安全策略 - How to enable content security policy to selective http patterns in spring security 在JSP / Spring中禁用内容安全策略 - Disable Content Security Policy in JSP/Spring 如何通过 Spring 引导动态更改 Spring Security 中的 client_secret - How to dynamically change client_secret in Spring Security with Spring boot Spring Boot 2.4.0 版本中 CORS 策略的更改 - Change of CORS policy in spring boot version 2.4.0 如何通过Spring Boot应用程序和Spock测试在运行时更改服务器端口 - How to change server port in runtime with a spring boot application and spock testing 如何在运行时 spring 启动时切换/更改 bean 实现? - How to switch/change bean implementation at runtime spring boot? 具有安全性的Spring Boot不提供我的静态内容 - Spring Boot with Security is not serving my static content Spring Boot 提供被安全阻止的静态内容 - Spring Boot serving static content blocked by security 如何使用Spring Security在运行时切换安全模型? - How to switch security model in runtime with spring security? 如何在 Android WebView 中禁用内容安全策略? - How to disable Content Security Policy in Android WebView?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM