3434import java .util .function .Consumer ;
3535import java .util .function .Function ;
3636import java .util .function .Supplier ;
37+ import java .util .stream .Stream ;
3738
3839import org .apache .commons .logging .Log ;
3940import org .apache .commons .logging .LogFactory ;
41+ import org .jspecify .annotations .Nullable ;
4042import reactor .core .publisher .Mono ;
4143import reactor .util .context .Context ;
4244
@@ -2460,8 +2462,6 @@ protected void configure(ServerHttpSecurity http) {
24602462 */
24612463 public final class HeaderSpec {
24622464
2463- private final List <ServerHttpHeadersWriter > writers ;
2464-
24652465 private CacheControlServerHttpHeadersWriter cacheControl = new CacheControlServerHttpHeadersWriter ();
24662466
24672467 private ContentTypeOptionsServerHttpHeadersWriter contentTypeOptions = new ContentTypeOptionsServerHttpHeadersWriter ();
@@ -2476,7 +2476,7 @@ public final class HeaderSpec {
24762476
24772477 private PermissionsPolicyServerHttpHeadersWriter permissionsPolicy = new PermissionsPolicyServerHttpHeadersWriter ();
24782478
2479- private ContentSecurityPolicyServerHttpHeadersWriter contentSecurityPolicy = new ContentSecurityPolicyServerHttpHeadersWriter ();
2479+ private ContentSecurityPolicySpec contentSecurityPolicy = new ContentSecurityPolicySpec ();
24802480
24812481 private ReferrerPolicyServerHttpHeadersWriter referrerPolicy = new ReferrerPolicyServerHttpHeadersWriter ();
24822482
@@ -2486,13 +2486,9 @@ public final class HeaderSpec {
24862486
24872487 private CrossOriginResourcePolicyServerHttpHeadersWriter crossOriginResourcePolicy = new CrossOriginResourcePolicyServerHttpHeadersWriter ();
24882488
2489- private final ContentSecurityPolicyNonceGeneratingWebFilter nonceGeneratingFilter = new ContentSecurityPolicyNonceGeneratingWebFilter ();
2489+ private List < ServerHttpHeadersWriter > customHeadersWriters = new ArrayList <> ();
24902490
24912491 private HeaderSpec () {
2492- this .writers = new ArrayList <>(Arrays .asList (this .cacheControl , this .contentTypeOptions , this .hsts ,
2493- this .frameOptions , this .xss , this .featurePolicy , this .permissionsPolicy , this .contentSecurityPolicy ,
2494- this .referrerPolicy , this .crossOriginOpenerPolicy , this .crossOriginEmbedderPolicy ,
2495- this .crossOriginResourcePolicy ));
24962492 }
24972493
24982494 /**
@@ -2546,7 +2542,7 @@ public HeaderSpec frameOptions(Customizer<FrameOptionsSpec> frameOptionsCustomiz
25462542 */
25472543 public HeaderSpec writer (ServerHttpHeadersWriter serverHttpHeadersWriter ) {
25482544 Assert .notNull (serverHttpHeadersWriter , "serverHttpHeadersWriter cannot be null" );
2549- this .writers .add (serverHttpHeadersWriter );
2545+ this .customHeadersWriters .add (serverHttpHeadersWriter );
25502546 return this ;
25512547 }
25522548
@@ -2562,12 +2558,18 @@ public HeaderSpec hsts(Customizer<HstsSpec> hstsCustomizer) {
25622558 }
25632559
25642560 protected void configure (ServerHttpSecurity http ) {
2565- ServerHttpHeadersWriter writer = new CompositeServerHttpHeadersWriter (this .writers );
2561+ Stream <ServerHttpHeadersWriter > builtInWriters = Stream
2562+ .of (this .cacheControl , this .contentTypeOptions , this .hsts , this .frameOptions , this .xss ,
2563+ this .featurePolicy , this .permissionsPolicy , this .contentSecurityPolicy .getWriter (),
2564+ this .referrerPolicy , this .crossOriginOpenerPolicy , this .crossOriginEmbedderPolicy ,
2565+ this .crossOriginResourcePolicy )
2566+ .filter (Objects ::nonNull );
2567+ ServerHttpHeadersWriter writer = new CompositeServerHttpHeadersWriter (
2568+ Stream .concat (builtInWriters , this .customHeadersWriters .stream ()).toList ());
25662569 HttpHeaderWriterWebFilter result = new HttpHeaderWriterWebFilter (writer );
25672570 http .addFilterAt (result , SecurityWebFiltersOrder .HTTP_HEADERS_WRITER );
2568- if (this .contentSecurityPolicy .isNonceBased ()) {
2569- http .addFilterBefore (this .nonceGeneratingFilter , SecurityWebFiltersOrder .HTTP_HEADERS_WRITER );
2570- }
2571+ http .addFilterBefore (this .contentSecurityPolicy .getNonceGeneratingFilter (),
2572+ SecurityWebFiltersOrder .HTTP_HEADERS_WRITER );
25712573 }
25722574
25732575 /**
@@ -2588,7 +2590,7 @@ public HeaderSpec xssProtection(Customizer<XssProtectionSpec> xssProtectionCusto
25882590 * @return the {@link HeaderSpec} to customize
25892591 */
25902592 public HeaderSpec contentSecurityPolicy (Customizer <ContentSecurityPolicySpec > contentSecurityPolicyCustomizer ) {
2591- contentSecurityPolicyCustomizer .customize (new ContentSecurityPolicySpec () );
2593+ contentSecurityPolicyCustomizer .customize (this . contentSecurityPolicy );
25922594 return this ;
25932595 }
25942596
@@ -2683,7 +2685,7 @@ private CacheSpec() {
26832685 * @return the {@link HeaderSpec} to configure
26842686 */
26852687 public HeaderSpec disable () {
2686- HeaderSpec .this .writers . remove ( HeaderSpec . this . cacheControl ) ;
2688+ HeaderSpec .this .cacheControl = null ;
26872689 return HeaderSpec .this ;
26882690 }
26892691
@@ -2704,7 +2706,7 @@ private ContentTypeOptionsSpec() {
27042706 * @return the {@link HeaderSpec} to configure
27052707 */
27062708 public HeaderSpec disable () {
2707- HeaderSpec .this .writers . remove ( HeaderSpec . this . contentTypeOptions ) ;
2709+ HeaderSpec .this .contentTypeOptions = null ;
27082710 return HeaderSpec .this ;
27092711 }
27102712
@@ -2736,7 +2738,7 @@ public HeaderSpec mode(XFrameOptionsServerHttpHeadersWriter.Mode mode) {
27362738 * @return the {@link HeaderSpec} to continue configuring
27372739 */
27382740 public HeaderSpec disable () {
2739- HeaderSpec .this .writers . remove ( HeaderSpec . this . frameOptions ) ;
2741+ HeaderSpec .this .frameOptions = null ;
27402742 return HeaderSpec .this ;
27412743 }
27422744
@@ -2795,7 +2797,7 @@ public HstsSpec preload(boolean preload) {
27952797 * @return the {@link HeaderSpec} to continue configuring
27962798 */
27972799 public HeaderSpec disable () {
2798- HeaderSpec .this .writers . remove ( HeaderSpec . this . hsts ) ;
2800+ HeaderSpec .this .hsts = null ;
27992801 return HeaderSpec .this ;
28002802 }
28012803
@@ -2816,7 +2818,7 @@ private XssProtectionSpec() {
28162818 * @return the {@link HeaderSpec} to continue configuring
28172819 */
28182820 public HeaderSpec disable () {
2819- HeaderSpec .this .writers . remove ( HeaderSpec . this . xss ) ;
2821+ HeaderSpec .this .xss = null ;
28202822 return HeaderSpec .this ;
28212823 }
28222824
@@ -2844,8 +2846,14 @@ public final class ContentSecurityPolicySpec {
28442846
28452847 private static final String DEFAULT_SRC_SELF_POLICY = "default-src 'self'" ;
28462848
2849+ private final ContentSecurityPolicyServerHttpHeadersWriter writer = new ContentSecurityPolicyServerHttpHeadersWriter ();
2850+
2851+ private @ Nullable String nonceAttributeName ;
2852+
2853+ private @ Nullable ServerWebExchangeMatcher exchangeMatcher ;
2854+
28472855 private ContentSecurityPolicySpec () {
2848- HeaderSpec . this .contentSecurityPolicy .setPolicyDirectives (DEFAULT_SRC_SELF_POLICY );
2856+ this .writer .setPolicyDirectives (DEFAULT_SRC_SELF_POLICY );
28492857 }
28502858
28512859 /**
@@ -2856,7 +2864,7 @@ private ContentSecurityPolicySpec() {
28562864 * @return the {@link HeaderSpec} to continue configuring
28572865 */
28582866 public HeaderSpec reportOnly (boolean reportOnly ) {
2859- HeaderSpec . this .contentSecurityPolicy .setReportOnly (reportOnly );
2867+ this .writer .setReportOnly (reportOnly );
28602868 return HeaderSpec .this ;
28612869 }
28622870
@@ -2869,7 +2877,7 @@ public HeaderSpec reportOnly(boolean reportOnly) {
28692877 * @return the {@link HeaderSpec} to continue configuring
28702878 */
28712879 public HeaderSpec policyDirectives (String policyDirectives ) {
2872- HeaderSpec . this .contentSecurityPolicy .setPolicyDirectives (policyDirectives );
2880+ this .writer .setPolicyDirectives (policyDirectives );
28732881 return HeaderSpec .this ;
28742882 }
28752883
@@ -2884,7 +2892,8 @@ public HeaderSpec policyDirectives(String policyDirectives) {
28842892 * @since 7.1
28852893 */
28862894 public ContentSecurityPolicySpec nonceAttributeName (String nonceAttributeName ) {
2887- HeaderSpec .this .nonceGeneratingFilter .setAttributeName (nonceAttributeName );
2895+ Assert .hasLength (nonceAttributeName , "NonceAttributeName must not be null or empty" );
2896+ this .nonceAttributeName = nonceAttributeName ;
28882897 return this ;
28892898 }
28902899
@@ -2903,11 +2912,8 @@ public ContentSecurityPolicySpec nonceAttributeName(String nonceAttributeName) {
29032912 */
29042913 public ContentSecurityPolicySpec exchangeMatcher (ServerWebExchangeMatcher matcher ) {
29052914 Assert .notNull (matcher , "Matcher must not be null" );
2906- // Replace the CSP writer in the list with a matcher-decorated writer
2907- int idx = HeaderSpec .this .writers .indexOf (HeaderSpec .this .contentSecurityPolicy );
2908- Assert .state (idx >= 0 , "ExchangeMatcher(s) is already configured" );
2909- HeaderSpec .this .writers .set (idx , new ServerWebExchangeDelegatingServerHttpHeadersWriter (matcher ,
2910- HeaderSpec .this .contentSecurityPolicy ));
2915+ Assert .state (this .exchangeMatcher == null , "ExchangeMatcher(s) is already configured" );
2916+ this .exchangeMatcher = matcher ;
29112917 return this ;
29122918 }
29132919
@@ -2930,8 +2936,19 @@ public ContentSecurityPolicySpec exchangeMatchers(String... pathPatterns) {
29302936 return this .exchangeMatcher (ServerWebExchangeMatchers .pathMatchers (pathPatterns ));
29312937 }
29322938
2933- private ContentSecurityPolicySpec (String policyDirectives ) {
2934- HeaderSpec .this .contentSecurityPolicy .setPolicyDirectives (policyDirectives );
2939+ ServerHttpHeadersWriter getWriter () {
2940+ if (this .exchangeMatcher != null ) {
2941+ return new ServerWebExchangeDelegatingServerHttpHeadersWriter (this .exchangeMatcher , this .writer );
2942+ }
2943+ return this .writer ;
2944+ }
2945+
2946+ ContentSecurityPolicyNonceGeneratingWebFilter getNonceGeneratingFilter () {
2947+ var filter = new ContentSecurityPolicyNonceGeneratingWebFilter ();
2948+ if (this .nonceAttributeName != null ) {
2949+ filter .setAttributeName (this .nonceAttributeName );
2950+ }
2951+ return filter ;
29352952 }
29362953
29372954 }
0 commit comments