Ether Framework
Unified API docs for Ether modules
Loading...
Searching...
No Matches
CorsPolicy.java
Go to the documentation of this file.
1package dev.rafex.ether.http.security.cors;
2
3/*-
4 * #%L
5 * ether-http-security
6 * %%
7 * Copyright (C) 2025 - 2026 Raúl Eduardo González Argote
8 * %%
9 * Permission is hereby granted, free of charge, to any person obtaining a copy
10 * of this software and associated documentation files (the "Software"), to deal
11 * in the Software without restriction, including without limitation the rights
12 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 * copies of the Software, and to permit persons to whom the Software is
14 * furnished to do so, subject to the following conditions:
15 *
16 * The above copyright notice and this permission notice shall be included in
17 * all copies or substantial portions of the Software.
18 *
19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 * THE SOFTWARE.
26 * #L%
27 */
28
29import java.util.LinkedHashMap;
30import java.util.List;
31import java.util.Map;
32import java.util.Objects;
33
34/**
35 * Política de seguridad CORS para controlar el acceso entre orígenes.
36 * <p>
37 * Configura los encabezados HTTP CORS que permiten o restringen el acceso
38 * a recursos desde dominios diferentes al servidor.
39 * </p>
40 *
41 * @param allowAnyOrigin Permite cualquier origen (riesgo de seguridad, usar solo en desarrollo)
42 * @param allowedOrigins Lista de orígenes permitidos explícitamente
43 * @param allowedMethods Métodos HTTP permitidos (GET, POST, etc.)
44 * @param allowedHeaders Headers permitidos en las solicitudes
45 * @param exposedHeaders Headers expuestos en la respuesta
46 * @param allowCredentials Indica si se permiten credenciales (cookies, tokens)
47 * @param maxAgeSeconds Tiempo de cache de la preflight request
48 * @param varyOrigin Agrega header Vary: Origin para evitar cache incorrecto
49 */
50public record CorsPolicy(boolean allowAnyOrigin, List<String> allowedOrigins, List<String> allowedMethods,
51 List<String> allowedHeaders, List<String> exposedHeaders, boolean allowCredentials, int maxAgeSeconds,
52 boolean varyOrigin) {
53
54 public CorsPolicy {
55 allowedOrigins = copyList(allowedOrigins);
56 allowedMethods = copyList(allowedMethods);
57 allowedHeaders = copyList(allowedHeaders);
58 exposedHeaders = copyList(exposedHeaders);
59 }
60
61 public static CorsPolicy permissive() {
62 return new CorsPolicy(true, List.of(), defaultMethods(), List.of("*"), List.of(), false, 3600, true);
63 }
64
65 public static CorsPolicy strict(final List<String> allowedOrigins) {
66 return new CorsPolicy(false, allowedOrigins, defaultMethods(), List.of("content-type", "authorization"),
67 List.of(), false, 3600, true);
68 }
69
70 public boolean isOriginAllowed(final String origin) {
71 if (allowAnyOrigin) {
72 return true;
73 }
74 if (origin == null || origin.isBlank()) {
75 return false;
76 }
77 for (final var allowed : allowedOrigins) {
78 if (matches(allowed, origin)) {
79 return true;
80 }
81 }
82 return false;
83 }
84
85 public Map<String, String> responseHeaders(final String origin) {
86 final var headers = new LinkedHashMap<String, String>();
87 if (allowAnyOrigin) {
88 headers.put("Access-Control-Allow-Origin", "*");
89 } else if (isOriginAllowed(origin)) {
90 headers.put("Access-Control-Allow-Origin", origin);
91 }
92 headers.put("Access-Control-Allow-Methods", String.join(", ", allowedMethods));
93 headers.put("Access-Control-Allow-Headers", String.join(", ", allowedHeaders));
94 if (!exposedHeaders.isEmpty()) {
95 headers.put("Access-Control-Expose-Headers", String.join(", ", exposedHeaders));
96 }
97 if (allowCredentials) {
98 headers.put("Access-Control-Allow-Credentials", "true");
99 }
100 headers.put("Access-Control-Max-Age", Integer.toString(maxAgeSeconds));
101 if (varyOrigin) {
102 headers.put("Vary", "Origin");
103 }
104 return headers;
105 }
106
107 private static List<String> copyList(final List<String> values) {
108 return values == null ? List.of() : List.copyOf(values);
109 }
110
111 private static boolean matches(final String allowed, final String candidate) {
112 if (allowed == null || candidate == null) {
113 return false;
114 }
115 if ("*".equals(allowed)) {
116 return true;
117 }
118 return Objects.equals(allowed, candidate);
119 }
120
121 private static List<String> defaultMethods() {
122 return List.of("GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD");
123 }
124}
record CorsPolicy(boolean allowAnyOrigin, List< String > allowedOrigins, List< String > allowedMethods, List< String > allowedHeaders, List< String > exposedHeaders, boolean allowCredentials, int maxAgeSeconds, boolean varyOrigin)
Política de seguridad CORS para controlar el acceso entre orígenes.
Security response headers and default header profiles.