1818
1919import java .net .URI ;
2020import java .util .ArrayList ;
21+ import java .util .Arrays ;
2122import java .util .List ;
2223import java .util .Map ;
24+ import java .util .function .Function ;
2325
2426import jakarta .servlet .http .HttpServletRequest ;
27+ import org .jspecify .annotations .Nullable ;
2528
2629import org .springframework .security .config .Customizer ;
2730import org .springframework .security .config .annotation .configuration .ObjectPostProcessorConfiguration ;
3033import org .springframework .security .config .annotation .web .configuration .EnableWebSecurity ;
3134import org .springframework .security .web .header .HeaderWriter ;
3235import org .springframework .security .web .header .HeaderWriterFilter ;
36+ import org .springframework .security .web .header .NonceGeneratingFilter ;
3337import org .springframework .security .web .header .writers .CacheControlHeadersWriter ;
3438import org .springframework .security .web .header .writers .ContentSecurityPolicyHeaderWriter ;
3539import org .springframework .security .web .header .writers .CrossOriginEmbedderPolicyHeaderWriter ;
3640import org .springframework .security .web .header .writers .CrossOriginOpenerPolicyHeaderWriter ;
3741import org .springframework .security .web .header .writers .CrossOriginResourcePolicyHeaderWriter ;
42+ import org .springframework .security .web .header .writers .DelegatingRequestMatcherHeaderWriter ;
3843import org .springframework .security .web .header .writers .FeaturePolicyHeaderWriter ;
3944import org .springframework .security .web .header .writers .HpkpHeaderWriter ;
4045import org .springframework .security .web .header .writers .HstsHeaderWriter ;
4550import org .springframework .security .web .header .writers .XXssProtectionHeaderWriter ;
4651import org .springframework .security .web .header .writers .frameoptions .XFrameOptionsHeaderWriter ;
4752import org .springframework .security .web .header .writers .frameoptions .XFrameOptionsHeaderWriter .XFrameOptionsMode ;
53+ import org .springframework .security .web .servlet .util .matcher .PathPatternRequestMatcher ;
54+ import org .springframework .security .web .util .matcher .OrRequestMatcher ;
4855import org .springframework .security .web .util .matcher .RequestMatcher ;
4956import org .springframework .util .Assert ;
5057
7582 * @author Vedran Pavic
7683 * @author Ankur Pathak
7784 * @author Daniel Garnier-Moiroux
85+ * @author Ziqin Wang
7886 * @since 3.2
7987 */
8088public class HeadersConfigurer <H extends HttpSecurityBuilder <H >>
@@ -273,6 +281,14 @@ public HeadersConfigurer<H> defaultsDisabled() {
273281 public void configure (H http ) {
274282 HeaderWriterFilter headersFilter = createHeaderWriterFilter ();
275283 http .addFilter (headersFilter );
284+ configureCspNonceGeneratingFilter (http );
285+ }
286+
287+ private void configureCspNonceGeneratingFilter (H http ) {
288+ ContentSecurityPolicyHeaderWriter writer = this .contentSecurityPolicy .writer ;
289+ if (writer != null && writer .isNonceBased ()) {
290+ http .addFilterBefore (new NonceGeneratingFilter (writer .getNonceAttributeName ()), HeaderWriterFilter .class );
291+ }
276292 }
277293
278294 /**
@@ -302,7 +318,7 @@ private List<HeaderWriter> getHeaderWriters() {
302318 addIfNotNull (writers , this .hsts .writer );
303319 addIfNotNull (writers , this .frameOptions .writer );
304320 addIfNotNull (writers , this .hpkp .writer );
305- addIfNotNull (writers , this .contentSecurityPolicy .writer );
321+ addIfNotNull (writers , this .contentSecurityPolicy .getWriter () );
306322 addIfNotNull (writers , this .referrerPolicy .writer );
307323 addIfNotNull (writers , this .featurePolicy .writer );
308324 addIfNotNull (writers , this .permissionsPolicy .writer );
@@ -937,11 +953,15 @@ public final class ContentSecurityPolicyConfig {
937953
938954 private ContentSecurityPolicyHeaderWriter writer ;
939955
956+ private @ Nullable RequestMatcher requestMatcher ;
957+
940958 private ContentSecurityPolicyConfig () {
941959 }
942960
943961 /**
944- * Sets the security policy directive(s) to be used in the response header.
962+ * Sets the security policy directive(s) to be used in the response header. The
963+ * {@code policyDirectives} may contain {@code {nonce}} as placeholders for a
964+ * generated secure random nonce, e.g., {@code script-src 'self' 'nonce-{nonce}'}.
945965 * @param policyDirectives the security policy directive(s)
946966 * @return the {@link ContentSecurityPolicyConfig} for additional configuration
947967 * @throws IllegalArgumentException if policyDirectives is null or empty
@@ -961,6 +981,70 @@ public ContentSecurityPolicyConfig reportOnly() {
961981 return this ;
962982 }
963983
984+ /**
985+ * Sets the name of the servlet request attribute for the generated nonce. Views
986+ * can read this attribute to render the nonce in HTML.
987+ * @param nonceAttributeName the name of the nonce attribute
988+ * @return the {@link ContentSecurityPolicyConfig} for additional configuration
989+ * @throws IllegalArgumentException if {@code nonceAttributeName} is {@code null}
990+ * or empty
991+ * @since 7.1
992+ */
993+ public ContentSecurityPolicyConfig nonceAttributeName (String nonceAttributeName ) {
994+ this .writer .setNonceAttributeName (nonceAttributeName );
995+ return this ;
996+ }
997+
998+ /**
999+ * Specifies the {@link RequestMatcher} to use for determining when CSP should be
1000+ * applied. The default is to enable CSP in every response if
1001+ * {@link HeadersConfigurer#contentSecurityPolicy(Customizer)} is configured.
1002+ * @param requestMatcher the {@link RequestMatcher} to use
1003+ * @return the {@link ContentSecurityPolicyConfig} for additional configuration
1004+ * @throws IllegalArgumentException if {@code requestMatcher} is null
1005+ * @throws IllegalStateException if a {@link RequestMatcher} is already configured
1006+ * by a previous call of this method or {@link #requireCspMatchers(String...)}
1007+ * @since 7.1
1008+ * @see #requireCspMatchers(String...)
1009+ */
1010+ public ContentSecurityPolicyConfig requireCspMatcher (RequestMatcher requestMatcher ) {
1011+ Assert .notNull (requestMatcher , "RequestMatcher cannot be null" );
1012+ Assert .state (this .requestMatcher == null , "RequireCspMatcher(s) is already configured" );
1013+ this .requestMatcher = requestMatcher ;
1014+ return this ;
1015+ }
1016+
1017+ /**
1018+ * Specifies the matching path patterns for determining when CSP should be
1019+ * applied. The default is to enable CSP in every response if
1020+ * {@link HeadersConfigurer#contentSecurityPolicy(Customizer)} is configured.
1021+ * @param pathPatterns the path patterns to be matched with a
1022+ * {@link PathPatternRequestMatcher}
1023+ * @return the {@link ContentSecurityPolicyConfig} for additional configuration
1024+ * @throws IllegalArgumentException if any path pattern if rejected by
1025+ * {@link PathPatternRequestMatcher.Builder#matcher(String)}
1026+ * @throws IllegalStateException if a {@link RequestMatcher} is already configured
1027+ * by a previous call of this method or {@link #requireCspMatcher(RequestMatcher)}
1028+ * @since 7.1
1029+ * @see #requireCspMatcher(RequestMatcher)
1030+ */
1031+ public ContentSecurityPolicyConfig requireCspMatchers (String ... pathPatterns ) {
1032+ PathPatternRequestMatcher .Builder builder = HeadersConfigurer .this .getRequestMatcherBuilder ();
1033+ // @formatter:off
1034+ return this .requireCspMatcher (new OrRequestMatcher (
1035+ Arrays .stream (pathPatterns )
1036+ .map ((Function <? super String , RequestMatcher >) builder ::matcher )
1037+ .toList ()));
1038+ // @formatter:on
1039+ }
1040+
1041+ HeaderWriter getWriter () {
1042+ if (this .requestMatcher != null ) {
1043+ return new DelegatingRequestMatcherHeaderWriter (this .requestMatcher , this .writer );
1044+ }
1045+ return this .writer ;
1046+ }
1047+
9641048 }
9651049
9661050 public final class ReferrerPolicyConfig {
0 commit comments