1818
1919import java .net .URI ;
2020import java .util .ArrayList ;
21+ import java .util .Arrays ;
2122import java .util .List ;
2223import java .util .Map ;
2324
2425import jakarta .servlet .http .HttpServletRequest ;
26+ import org .jspecify .annotations .Nullable ;
2527
2628import org .springframework .security .config .Customizer ;
2729import org .springframework .security .config .annotation .configuration .ObjectPostProcessorConfiguration ;
3032import org .springframework .security .config .annotation .web .configuration .EnableWebSecurity ;
3133import org .springframework .security .web .header .HeaderWriter ;
3234import org .springframework .security .web .header .HeaderWriterFilter ;
35+ import org .springframework .security .web .header .NonceGeneratingFilter ;
3336import org .springframework .security .web .header .writers .CacheControlHeadersWriter ;
3437import org .springframework .security .web .header .writers .ContentSecurityPolicyHeaderWriter ;
3538import org .springframework .security .web .header .writers .CrossOriginEmbedderPolicyHeaderWriter ;
3639import org .springframework .security .web .header .writers .CrossOriginOpenerPolicyHeaderWriter ;
3740import org .springframework .security .web .header .writers .CrossOriginResourcePolicyHeaderWriter ;
41+ import org .springframework .security .web .header .writers .DelegatingRequestMatcherHeaderWriter ;
3842import org .springframework .security .web .header .writers .FeaturePolicyHeaderWriter ;
3943import org .springframework .security .web .header .writers .HpkpHeaderWriter ;
4044import org .springframework .security .web .header .writers .HstsHeaderWriter ;
4549import org .springframework .security .web .header .writers .XXssProtectionHeaderWriter ;
4650import org .springframework .security .web .header .writers .frameoptions .XFrameOptionsHeaderWriter ;
4751import org .springframework .security .web .header .writers .frameoptions .XFrameOptionsHeaderWriter .XFrameOptionsMode ;
52+ import org .springframework .security .web .servlet .util .matcher .PathPatternRequestMatcher ;
53+ import org .springframework .security .web .util .matcher .OrRequestMatcher ;
4854import org .springframework .security .web .util .matcher .RequestMatcher ;
4955import org .springframework .util .Assert ;
5056
7581 * @author Vedran Pavic
7682 * @author Ankur Pathak
7783 * @author Daniel Garnier-Moiroux
84+ * @author Ziqin Wang
7885 * @since 3.2
7986 */
8087public class HeadersConfigurer <H extends HttpSecurityBuilder <H >>
@@ -273,6 +280,14 @@ public HeadersConfigurer<H> defaultsDisabled() {
273280 public void configure (H http ) {
274281 HeaderWriterFilter headersFilter = createHeaderWriterFilter ();
275282 http .addFilter (headersFilter );
283+ configureCspNonceGeneratingFilter (http );
284+ }
285+
286+ private void configureCspNonceGeneratingFilter (H http ) {
287+ ContentSecurityPolicyHeaderWriter writer = this .contentSecurityPolicy .writer ;
288+ if (writer != null && writer .isNonceBased ()) {
289+ http .addFilterBefore (new NonceGeneratingFilter (writer .getNonceAttributeName ()), HeaderWriterFilter .class );
290+ }
276291 }
277292
278293 /**
@@ -302,7 +317,7 @@ private List<HeaderWriter> getHeaderWriters() {
302317 addIfNotNull (writers , this .hsts .writer );
303318 addIfNotNull (writers , this .frameOptions .writer );
304319 addIfNotNull (writers , this .hpkp .writer );
305- addIfNotNull (writers , this .contentSecurityPolicy .writer );
320+ addIfNotNull (writers , this .contentSecurityPolicy .getWriter () );
306321 addIfNotNull (writers , this .referrerPolicy .writer );
307322 addIfNotNull (writers , this .featurePolicy .writer );
308323 addIfNotNull (writers , this .permissionsPolicy .writer );
@@ -937,11 +952,15 @@ public final class ContentSecurityPolicyConfig {
937952
938953 private ContentSecurityPolicyHeaderWriter writer ;
939954
955+ private @ Nullable RequestMatcher requestMatcher ;
956+
940957 private ContentSecurityPolicyConfig () {
941958 }
942959
943960 /**
944- * Sets the security policy directive(s) to be used in the response header.
961+ * Sets the security policy directive(s) to be used in the response header. The
962+ * {@code policyDirectives} may contain {@code {nonce}} as placeholders for a
963+ * generated secure random nonce, e.g., {@code script-src 'self' 'nonce-{nonce}'}.
945964 * @param policyDirectives the security policy directive(s)
946965 * @return the {@link ContentSecurityPolicyConfig} for additional configuration
947966 * @throws IllegalArgumentException if policyDirectives is null or empty
@@ -961,6 +980,66 @@ public ContentSecurityPolicyConfig reportOnly() {
961980 return this ;
962981 }
963982
983+ /**
984+ * Sets the name of the servlet request attribute for the generated nonce. Views
985+ * can read this attribute to render the nonce in HTML.
986+ * @param nonceAttributeName the name of the nonce attribute
987+ * @return the {@link ContentSecurityPolicyConfig} for additional configuration
988+ * @throws IllegalArgumentException if {@code nonceAttributeName} is {@code null}
989+ * or empty
990+ * @since 7.1
991+ */
992+ public ContentSecurityPolicyConfig nonceAttributeName (String nonceAttributeName ) {
993+ this .writer .setNonceAttributeName (nonceAttributeName );
994+ return this ;
995+ }
996+
997+ /**
998+ * Specifies the {@link RequestMatcher} to use for determining when CSP should be
999+ * applied. The default is to enable CSP in every response if
1000+ * {@link HeadersConfigurer#contentSecurityPolicy(Customizer)} is configured.
1001+ * @param requestMatcher the {@link RequestMatcher} to use
1002+ * @return the {@link ContentSecurityPolicyConfig} for additional configuration
1003+ * @throws IllegalArgumentException if {@code requestMatcher} is null
1004+ * @throws IllegalStateException if a {@link RequestMatcher} is already configured
1005+ * by a previous call of this method or {@link #requireCspMatchers(String...)}
1006+ * @since 7.1
1007+ * @see #requireCspMatchers(String...)
1008+ */
1009+ public ContentSecurityPolicyConfig requireCspMatcher (RequestMatcher requestMatcher ) {
1010+ Assert .notNull (requestMatcher , "RequestMatcher cannot be null" );
1011+ Assert .state (this .requestMatcher == null , "RequireCspMatcher(s) is already configured" );
1012+ this .requestMatcher = requestMatcher ;
1013+ return this ;
1014+ }
1015+
1016+ /**
1017+ * Specifies the matching path patterns for determining when CSP should be
1018+ * applied. The default is to enable CSP in every response if
1019+ * {@link HeadersConfigurer#contentSecurityPolicy(Customizer)} is configured.
1020+ * @param pathPatterns the path patterns to be matched with a
1021+ * {@link PathPatternRequestMatcher}
1022+ * @return the {@link ContentSecurityPolicyConfig} for additional configuration
1023+ * @throws IllegalArgumentException if any path pattern if rejected by
1024+ * {@link PathPatternRequestMatcher.Builder#matcher(String)}
1025+ * @throws IllegalStateException if a {@link RequestMatcher} is already configured
1026+ * by a previous call of this method or {@link #requireCspMatcher(RequestMatcher)}
1027+ * @since 7.1
1028+ * @see #requireCspMatcher(RequestMatcher)
1029+ */
1030+ public ContentSecurityPolicyConfig requireCspMatchers (String ... pathPatterns ) {
1031+ PathPatternRequestMatcher .Builder builder = HeadersConfigurer .this .getRequestMatcherBuilder ();
1032+ OrRequestMatcher matcher = new OrRequestMatcher (Arrays .stream (pathPatterns ).map (builder ::matcher ).toList ());
1033+ return this .requireCspMatcher (matcher );
1034+ }
1035+
1036+ HeaderWriter getWriter () {
1037+ if (this .requestMatcher != null ) {
1038+ return new DelegatingRequestMatcherHeaderWriter (this .requestMatcher , this .writer );
1039+ }
1040+ return this .writer ;
1041+ }
1042+
9641043 }
9651044
9661045 public final class ReferrerPolicyConfig {
0 commit comments