001/* 002 * Copyright 2002-2020 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * https://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.web.cors.reactive; 018 019import java.util.LinkedHashMap; 020import java.util.Map; 021 022import org.springframework.http.server.PathContainer; 023import org.springframework.lang.Nullable; 024import org.springframework.web.cors.CorsConfiguration; 025import org.springframework.web.server.ServerWebExchange; 026import org.springframework.web.util.pattern.PathPattern; 027import org.springframework.web.util.pattern.PathPatternParser; 028 029/** 030 * Provide a per reactive request {@link CorsConfiguration} instance based on a 031 * collection of {@link CorsConfiguration} mapped on path patterns. 032 * 033 * <p>Exact path mapping URIs (such as {@code "/admin"}) are supported 034 * as well as Ant-style path patterns (such as {@code "/admin/**"}). 035 * 036 * @author Sebastien Deleuze 037 * @author Brian Clozel 038 * @since 5.0 039 */ 040public class UrlBasedCorsConfigurationSource implements CorsConfigurationSource { 041 042 private final Map<PathPattern, CorsConfiguration> corsConfigurations; 043 044 private final PathPatternParser patternParser; 045 046 047 /** 048 * Construct a new {@code UrlBasedCorsConfigurationSource} instance with default 049 * {@code PathPatternParser}. 050 * @since 5.0.6 051 */ 052 public UrlBasedCorsConfigurationSource() { 053 this(PathPatternParser.defaultInstance); 054 } 055 056 /** 057 * Construct a new {@code UrlBasedCorsConfigurationSource} instance from the supplied 058 * {@code PathPatternParser}. 059 */ 060 public UrlBasedCorsConfigurationSource(PathPatternParser patternParser) { 061 this.corsConfigurations = new LinkedHashMap<>(); 062 this.patternParser = patternParser; 063 } 064 065 066 /** 067 * Set CORS configuration based on URL patterns. 068 */ 069 public void setCorsConfigurations(@Nullable Map<String, CorsConfiguration> corsConfigurations) { 070 this.corsConfigurations.clear(); 071 if (corsConfigurations != null) { 072 corsConfigurations.forEach(this::registerCorsConfiguration); 073 } 074 } 075 076 /** 077 * Register a {@link CorsConfiguration} for the specified path pattern. 078 */ 079 public void registerCorsConfiguration(String path, CorsConfiguration config) { 080 this.corsConfigurations.put(this.patternParser.parse(path), config); 081 } 082 083 @Override 084 @Nullable 085 public CorsConfiguration getCorsConfiguration(ServerWebExchange exchange) { 086 PathContainer lookupPath = exchange.getRequest().getPath().pathWithinApplication(); 087 return this.corsConfigurations.entrySet().stream() 088 .filter(entry -> entry.getKey().matches(lookupPath)) 089 .map(Map.Entry::getValue) 090 .findFirst() 091 .orElse(null); 092 } 093 094}