From 6c3f54d2b1bbbb499653ff3e1c31760118efb43b Mon Sep 17 00:00:00 2001 From: Tim Quinn Date: Mon, 8 Jan 2024 15:45:33 -0600 Subject: [PATCH] Add a little logic to CORS config processing and update the CORS doc Signed-off-by: Tim Quinn --- .../main/java/io/helidon/cors/Aggregator.java | 23 +++- .../io/helidon/cors/CorsSupportHelper.java | 4 +- .../main/java/io/helidon/cors/LogHelper.java | 13 +- .../helidon/cors/CrossOriginConfigTest.java | 61 ++++++++- cors/src/test/resources/configMapperTest.yaml | 7 + .../main/asciidoc/includes/attributes.adoc | 1 + docs/src/main/asciidoc/includes/cors.adoc | 124 ++++++------------ docs/src/main/asciidoc/mp/cors/cors.adoc | 11 +- docs/src/main/asciidoc/se/cors.adoc | 116 ++++++++-------- 9 files changed, 204 insertions(+), 156 deletions(-) diff --git a/cors/src/main/java/io/helidon/cors/Aggregator.java b/cors/src/main/java/io/helidon/cors/Aggregator.java index 33167701cc6..64f8b5b96a1 100644 --- a/cors/src/main/java/io/helidon/cors/Aggregator.java +++ b/cors/src/main/java/io/helidon/cors/Aggregator.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2023 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -118,10 +118,23 @@ public Aggregator build() { Builder config(Config config) { if (config.exists()) { - ConfigValue configValue = config.map(CrossOriginConfig::builder); - if (configValue.isPresent()) { - CrossOriginConfig crossOriginConfig = configValue.get().build(); - addPathlessCrossOrigin(crossOriginConfig); + // The config node might be a simple config block for a single cross-origin config (containing allow-origins, + // allow-methods, etc.). Or it might be a mapped cross-origin config introduced by "paths" and + // containing a list, each element of which has a path-pattern plus the "plain" cross-origin config settings + // (allow-origins, etc.) + Config pathsNode = config.get(CrossOriginConfig.CORS_PATHS_CONFIG_KEY); + if (pathsNode.exists()) { + ConfigValue configValue = config.map(MappedCrossOriginConfig::builder); + if (configValue.isPresent()) { + MappedCrossOriginConfig mappedCrossOriginConfig = configValue.get().build(); + mappedCrossOriginConfig.forEach(this::addCrossOrigin); + } + } else { + ConfigValue configValue = config.map(CrossOriginConfig::builder); + if (configValue.isPresent()) { + CrossOriginConfig crossOriginConfig = configValue.get().build(); + addPathlessCrossOrigin(crossOriginConfig); + } } } return this; diff --git a/cors/src/main/java/io/helidon/cors/CorsSupportHelper.java b/cors/src/main/java/io/helidon/cors/CorsSupportHelper.java index e8a3be7dffd..55da8e53272 100644 --- a/cors/src/main/java/io/helidon/cors/CorsSupportHelper.java +++ b/cors/src/main/java/io/helidon/cors/CorsSupportHelper.java @@ -472,7 +472,7 @@ Optional processCorsRequest( CrossOriginConfig crossOriginConfig = crossOriginOpt.get(); - // If enabled but not whitelisted, deny request + // If enabled but not allow-listed, deny request List allowedOrigins = Arrays.asList(crossOriginConfig.allowOrigins()); Optional originOpt = requestAdapter.firstHeader(HeaderNames.ORIGIN); if (!allowedOrigins.contains("*") && !contains(originOpt, allowedOrigins, CorsSupportHelper::compareOrigins)) { @@ -555,7 +555,7 @@ R processCorsPreFlightRequest(CorsRequestAdapter requestAdapter, CorsResponse } CrossOriginConfig crossOrigin = crossOriginOpt.get(); - // If enabled but not whitelisted, deny request + // If enabled but not allow-listed, deny request List allowedOrigins = Arrays.asList(crossOrigin.allowOrigins()); if (!allowedOrigins.contains("*") && !contains(originOpt, allowedOrigins, CorsSupportHelper::compareOrigins)) { return forbid(requestAdapter, diff --git a/cors/src/main/java/io/helidon/cors/LogHelper.java b/cors/src/main/java/io/helidon/cors/LogHelper.java index 6cb13a7f94b..638d5e78980 100644 --- a/cors/src/main/java/io/helidon/cors/LogHelper.java +++ b/cors/src/main/java/io/helidon/cors/LogHelper.java @@ -96,20 +96,23 @@ static void logIsRequestTypeNormal(boolean result, boolean silent, CorsReque } if (host.isEmpty()) { - reasonsWhyNormal.add("header " + HeaderNames.HOST + " is absent"); + reasonsWhyNormal.add("header " + HeaderNames.HOST + "/requested URI is absent"); } else { - factorsWhyCrossHost.add(String.format("header %s is present (%s)", HeaderNames.HOST, host)); + factorsWhyCrossHost.add(String.format("header %s/requested URI is present (%s)", HeaderNames.HOST, host)); } if (originOpt.isPresent()) { String partOfOriginMatchingHost = "://" + host; if (originOpt.get() .contains(partOfOriginMatchingHost)) { - reasonsWhyNormal.add(String.format("header %s '%s' matches header %s '%s'", HeaderNames.ORIGIN, + reasonsWhyNormal.add(String.format("header %s '%s' matches header/requested URI %s '%s'", HeaderNames.ORIGIN, effectiveOrigin, HeaderNames.HOST, host)); } else { - factorsWhyCrossHost.add(String.format("header %s '%s' does not match header %s '%s'", HeaderNames.ORIGIN, - effectiveOrigin, HeaderNames.HOST, host)); + factorsWhyCrossHost.add(String.format("header %s '%s' does not match header/requested URI %s '%s'", + HeaderNames.ORIGIN, + effectiveOrigin, + HeaderNames.HOST, + host)); } } diff --git a/cors/src/test/java/io/helidon/cors/CrossOriginConfigTest.java b/cors/src/test/java/io/helidon/cors/CrossOriginConfigTest.java index 9d81ebe7f99..db1ae8c47d7 100644 --- a/cors/src/test/java/io/helidon/cors/CrossOriginConfigTest.java +++ b/cors/src/test/java/io/helidon/cors/CrossOriginConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022 Oracle and/or its affiliates. + * Copyright (c) 2020, 2024 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,14 @@ import java.util.List; import java.util.Optional; +import io.helidon.common.testing.junit5.OptionalMatcher; +import io.helidon.common.uri.UriInfo; import io.helidon.config.Config; import io.helidon.config.ConfigSources; import io.helidon.config.MissingValueException; +import io.helidon.http.HeaderNames; +import io.helidon.http.Status; +import io.helidon.http.WritableHeaders; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; @@ -175,4 +180,58 @@ void testOrdering() { crossOriginConfigOpt.get().pathPattern(), is(crossOriginConfigs.get(1).pathPattern())); } + + @Test + void testBuiltInServiceConfig() { + Config node = testConfig.get("built-in-service"); + assertThat(node, is(notNullValue())); + assertThat(node.exists(), is(true)); + CorsSupportHelper helper = + CorsSupportHelper.builder() + .config(node) + .build(); + + // In the first test, use an unacceptable origin and make sure we get a 403 response. + WritableHeaders headers = WritableHeaders.create(); + headers.add(HeaderNames.ORIGIN, "http://bad.com") + .add(HeaderNames.HOST, "someProxy.com") + .add(HeaderNames.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + UriInfo uriInfo = UriInfo.builder() + .scheme("http") + .host("localhost") + .path("/") + .port(80) + .build(); + CorsRequestAdapter req = new TestCorsServerRequestAdapter("/observe/health", + uriInfo, + "GET", + headers); + CorsResponseAdapter resp = new TestCorsServerResponseAdapter(); + Optional resultOpt = helper.processRequest(req, resp); + + assertThat("Response from GET to built-in service path", + resultOpt, + OptionalMatcher.optionalPresent()); + assertThat("Response status from secure PUT to unsecured path", + resultOpt.get().status(), + is(Status.FORBIDDEN_403.code())); + + // In the next test, use an acceptable origin in the request. There should be no response because that's how + // the CORS support helper lets the caller know that it's an acceptable CORS request. + headers = WritableHeaders.create(); + headers.add(HeaderNames.ORIGIN, "http://roothere.com") + .add(HeaderNames.HOST, "someProxy.com") + .add(HeaderNames.ACCESS_CONTROL_REQUEST_METHOD, "GET"); + req = new TestCorsServerRequestAdapter("/observe/health", + uriInfo, + "GET", + headers); + resp = new TestCorsServerResponseAdapter(); + + resultOpt = helper.processRequest(req, resp); + + assertThat("Response from GET to built-in service path", + resultOpt, + OptionalMatcher.optionalEmpty()); + } } diff --git a/cors/src/test/resources/configMapperTest.yaml b/cors/src/test/resources/configMapperTest.yaml index 54e7e24556c..cd3098ce3f9 100644 --- a/cors/src/test/resources/configMapperTest.yaml +++ b/cors/src/test/resources/configMapperTest.yaml @@ -73,3 +73,10 @@ same-host-diff-ports: allow-origins: [ "http://here.com:8080"] - path-pattern: "/greet" allow-origins: [ "http://here.com"] + + +built-in-service: + paths: + - path-pattern: "/observe/health" + allow-origins: ["http://roothere.com"] + allow-methods: ["GET", "HEAD"] diff --git a/docs/src/main/asciidoc/includes/attributes.adoc b/docs/src/main/asciidoc/includes/attributes.adoc index ecde61aae06..716802f78bf 100644 --- a/docs/src/main/asciidoc/includes/attributes.adoc +++ b/docs/src/main/asciidoc/includes/attributes.adoc @@ -197,6 +197,7 @@ endif::[] :config-git-javadoc-base-url: {javadoc-base-url}/io.helidon.config.git :config-mapping-javadoc-base-url: {javadoc-base-url}/io.helidon.config.objectmapping :configurable-javadoc-base-url: {javadoc-base-url}/io.helidon.common.configurable +:cors-javadoc-base-url: {javadoc-base-url}/io.helidon.cors :faulttolerance-javadoc-base-url: {javadoc-base-url}/io.helidon.faulttolerance :grpc-server-javadoc-base-url: {javadoc-base-url}/io.helidon.webserver.grpc :health-javadoc-base-url: {javadoc-base-url}/io.helidon.health.checks diff --git a/docs/src/main/asciidoc/includes/cors.adoc b/docs/src/main/asciidoc/includes/cors.adoc index 4a5f5d3ff60..db4018981c0 100644 --- a/docs/src/main/asciidoc/includes/cors.adoc +++ b/docs/src/main/asciidoc/includes/cors.adoc @@ -30,17 +30,21 @@ ifndef::h1-prefix[:h1-prefix: SE] = CORS Shared content // tag::cors-intro[] +ifdef::se-flavor[:built-in-service-prefix: /observe] +ifndef::se-flavor[:built-in-service-prefix: ] + The link:https://www.w3.org/TR/cors[cross-origin resource sharing (CORS) protocol] helps developers control if and how REST resources served by their applications can be shared across origins. Helidon {flavor-uc} includes an implementation of CORS that you can use to add CORS behavior -to the services you develop. You can define your application's CORS behavior programmatically using the Helidon CORS API alone, or +to the services you develop. You can define your application's CORS behavior programmatically using the Helidon CORS API alone or together with configuration. Helidon also provides three built-in services that add their -own endpoints to your application -- health, metrics, and OpenAPI -- that have integrated CORS support. +own endpoints to your application--health, metrics, and OpenAPI--that have integrated CORS support. By adding very little code to your application, you control how all the resources in -your application -- the ones you write and the ones provided by the Helidon built-in services -- can be shared across origins. +your application--the ones you write and the ones provided by the Helidon built-in services--can be shared across origins. === Before You Begin +ifdef::se-flavor[==== Planning Your Resource Sharing] Before you revise your application to add CORS support, you need to decide what type of cross-origin sharing you want to allow for each resource your application exposes. For example, suppose for a given resource you want to allow unrestricted sharing for GET, HEAD, and POST requests @@ -49,8 +53,8 @@ origins `foo.com` and `there.com`. Your application would implement two types of CORS sharing: more relaxed for the simple requests and stricter for others. -Once you know the type of sharing you want to allow for each of your resources -- including any from built-in -services -- you can change your application accordingly. +Once you know the type of sharing you want to allow for each of your resources--including any from built-in +services--you can change your application accordingly. // end::cors-intro[] // The add-cors-dependency tag's contents is reused from other SE and MP pages. @@ -70,9 +74,9 @@ the following dependency in your project: Support in Helidon for CORS configuration uses two closely-related cross-origin configuration formats: basic and mapped. Each format corresponds to a class in the Helidon CORS library. -The basic format corresponds to the link:{webserver-cors-javadoc-base-url}/io/helidon/cors/CrossOriginConfig.html[`CrossOriginConfig`] +The basic format corresponds to the link:{ws-cors-javadoc}/CrossOriginConfig.html[`CrossOriginConfig`] class, and the mapped format corresponds to the -link:{webserver-cors-javadoc-base-url}/io/helidon/cors/MappedCrossOriginConfig.html[`MappedCrossOriginConfig`] class. +link:{ws-cors-javadoc}/MappedCrossOriginConfig.html[`MappedCrossOriginConfig`] class. //end::cors-configuration-formats-intro[] //tag::basic-cross-origin-config[] @@ -87,10 +91,10 @@ one or more key/value pairs. Each key-value pair assigns one characteristic of C {basic-table-intro} [[config-key-table]] -include::{rootdir}/includes/cors.adoc[tag=cors-config-table] +include::cors.adoc[tag=cors-config-table] The following example of basic cross-origin -ifdef::se-flavor[configuration, when loaded and used by the application,] +ifdef::se-flavor[configuration, when explicitly loaded and used by your application code,] ifndef::se-flavor[configuration] limits cross-origin resource sharing for `PUT` and `DELETE` operations to only `foo.com` and `there.com`: @@ -185,8 +189,8 @@ If the cross-origin configuration is disabled (`enabled` = false), then the Heli // tag::mapped-config[] // tag::mapped-config-prefix[] -Helidon represents mapped CORS information as a section, identified by a configuration -key of your choosing, that contains: +[[mapped-config-descr]] +Helidon represents mapped CORS information as a config section that contains: * An optional `enabled` setting which defaults to `true` and applies to the whole mapped CORS config section, and @@ -196,8 +200,7 @@ key of your choosing, that contains: ** a `path-pattern` path pattern that maps that basic CORS config section to the resource(s) it affects. -You can use mapped configuration to your advantage if you want to allow your users to override the CORS behavior set up -in the application code. +You can use mapped configuration to your advantage if you want to specify all CORS behavior using configuration (with no explicit coding changes) or to allow your users to override the CORS behavior that your code explicitly sets up. The following example illustrates the mapped cross-origin configuration format. @@ -239,7 +242,7 @@ the top-level resource in the app (the `path-pattern` key and value). <6> Begins the basic CORS config section for `/`; it permits sharing of resources at the top-level path with all origins for the indicated HTTP methods. -Path patterns can be any expression accepted by the link:{webserver-javadoc-base-url}/io/helidon/webserver/PathMatcher.html[`PathMatcher`] class. +Path patterns can be any expression accepted by the link:{http-javadoc-base-url}/io/helidon/http/PathMatcher.html[`PathMatcher`] class. NOTE: Be sure to arrange the entries in the order that you want Helidon to check them. Helidon CORS support searches the cross-origin entries in the order you define them until it finds an entry that @@ -285,7 +288,7 @@ This scenario is exactly the situation CORS addresses: an application in the bro Integrating CORS support into these built-in services allows such third-party web sites and their browser applications -- or more generally, apps from any other origin -- to work with your Helidon application. -Because all three of these built-in Helidon services serve only `GET` endpoints, by default the +Because all three of these built-in Helidon services serve primarily `GET` endpoints, by default the integrated CORS support in all three services permits any origin to share their resources using `GET`, `HEAD`, and `OPTIONS` HTTP requests. You can customize the CORS set-up for these built-in services independently from each other using @@ -303,88 +306,47 @@ to your own endpoints. To use built-in services with CORS support and customize the CORS behavior: -. Add the built-in service or services to your application. The health, metrics, and OpenAPI services automatically -include default CORS support. +. Add the built-in service or services to your application. . {blank} + -- -Add a dependency on the Helidon {flavor-uc} CORS artifact to your Maven `pom.xml` file. +Add a dependency on the `io.helidon.webserver:helidon-webserver-cors` CORS artifact to your Maven `pom.xml` file. NOTE: If you want the built-in services to support CORS, then you need to add the CORS dependency even if your own endpoints do not use CORS. -- . Use -ifdef::se-flavor[the Helidon API or] -configuration to customize the CORS behavior as needed. +configuration to set up the CORS behavior by path as needed. The documentation for the individual built-in services describes how to add each service to your application, including -adding a Maven -ifdef::se-flavor[dependency and including the service in your application's routing rules.] -ifndef::se-flavor[dependency.] -In your -application's configuration file, the configuration for each service appears under its own key. -[%autowidth] -|==== -| Helidon Service Documentation | Configuration Key -| xref:{health-page}[health] | `health` -| xref:{metrics-page}[metrics] | `metrics` -| xref:{openapi-page}[OpenAPI] | `openapi` -|==== - -The link:{helidon-github-tree-url}/examples/quickstarts/helidon-quickstart-{flavor-lc}[Helidon {flavor-uc} QuickStart example] -uses these services, so you can use that as a template for your -own application, or use the example project itself to experiment with customizing the CORS -behavior in the built-in services. +adding a Maven dependency for the built-in feature. + // end::builtin-getting-started[] // tag::configuring-cors-for-builtin-services[] ==== Configuring CORS for Built-in Services -You can -ifdef::se-flavor[also ] -use configuration to control whether and how each of the built-in services works with CORS. - -ifdef::se-flavor[] -Your application can pass configuration to the builder for each built-in service. -endif::[] -In the configuration for the health, metrics, and OpenAPI services, you can add a section for CORS. +Use configuration to control whether and how each of the built-in services works with CORS. +In the `cors` configuration section add a block for each built-in service using its path as described in the +ifdef::se-flavor[xref:mapped-config-descr[mapped ]] +CORS configuration section. // Tag the following example so we can exclude it from MP which supplies its own complete example. // tag::se-config-example[] The following example restricts sharing of the -`/health` resource, provided by the health built-in service, to only the origin `\http://there.com`. -[source,hocon] +`{built-in-service-prefix}/health` resource, provided by the health built-in service, to only the origin `\http://there.com`. +[source,hocon,subs="attributes+"] ---- -health: - cors: - allow-origins: [http://there.com] +cors: + paths: + - path-pattern: "{built-in-service-prefix}/health" + allow-origins: [http://there.com] + - path-pattern: "{built-in-service-prefix}/metrics" + allow-origins: [http://foo.com] ---- // end::se-config-example[] -// tag::se-code-changes-for-builtin-services-config[] -Modify your application to load the `health` config node and use it to construct the `HealthSupport` service. -The following code shows this change in the the QuickStart SE example. -[source,java] ----- -HealthSupport health = HealthSupport.builder() - .config(config.get("health")) // <1> - .add(HealthChecks.healthChecks()) // Adds a convenient set of checks - .build(); ----- -<1> Use the `health` config section (if present) to configure the health service. - -// end::se-code-changes-for-builtin-services-config[] - -You have full control over the CORS configuration for a built-in Helidon service. Use a CORS config section -as described in -ifdef::se-flavor[] -xref:#using-config-from-app[Using Configuration for CORS]. -endif::[] -ifndef::se-flavor[] -xref:#config-table[the configuration table]. -endif::[] - // end::configuring-cors-for-builtin-services[] // tag::accessing-shared-resources-intro[] @@ -401,9 +363,9 @@ Build and run the QuickStart application as usual. // tag::accessing-shared-resources-main[] ===== Retrieve Metrics The metrics service rejects attempts to access metrics on behalf of a disallowed origin. -[source,bash] +[source,bash,subs="attributes+"] ---- -curl -i -H "Origin: http://other.com" http://localhost:8080/metrics +curl -i -H "Origin: http://other.com" http://localhost:8080{built-in-service-prefix}/metrics ---- [source, listing] @@ -415,9 +377,9 @@ connection: keep-alive ---- But accesses from `foo.com` succeed. -[source,bash] +[source,bash,subs="attributes+"] ---- -curl -i -H "Origin: http://foo.com" http://localhost:8080/metrics +curl -i -H "Origin: http://foo.com" http://localhost:8080{built-in-service-prefix}/metrics ---- [source, listing] @@ -438,9 +400,9 @@ base_classloader_loadedClasses_count 3568 ===== Retrieve Health The health service rejects requests from origins not specifically approved. -[source,bash] +[source,bash,subs="attributes+"] ---- -curl -i -H "Origin: http://foo.com" http://localhost:8080/health +curl -i -H "Origin: http://foo.com" http://localhost:8080{built-in-service-prefix}/health ---- [source, listing] @@ -453,9 +415,9 @@ connection: keep-alive And responds successfully only to cross-origin requests from `\http://there.com`. -[source,bash] +[source,bash,subs="attributes+"] ---- -curl -i -H "Origin: http://there.com" http://localhost:8080/health +curl -i -H "Origin: http://there.com" http://localhost:8080{built-in-service-prefix}/health ---- [source, listing] diff --git a/docs/src/main/asciidoc/mp/cors/cors.adoc b/docs/src/main/asciidoc/mp/cors/cors.adoc index 12e772dc5db..828a9e2fbfe 100644 --- a/docs/src/main/asciidoc/mp/cors/cors.adoc +++ b/docs/src/main/asciidoc/mp/cors/cors.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2022, 2023 Oracle and/or its affiliates. + Copyright (c) 2022, 2024 Oracle and/or its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -222,7 +222,7 @@ include::{rootdir}/includes/cors.adoc[tag=understanding-cors-support-in-services include::{rootdir}/includes/cors.adoc[tag=builtin-getting-started] -include::{rootdir}/includes/cors.adoc[tags=configuring-cors-for-builtin-services;!se-config-example;!se-code-changes-for-builtin-services-config] +include::{rootdir}/includes/cors.adoc[tags=configuring-cors-for-builtin-services;!se-config-example] The following example restricts sharing of @@ -232,9 +232,10 @@ The following example restricts sharing of .Configuration which restricts sharing of the health and metrics resources [source,properties] ---- -health.cors.allow-origins=http://there.com - -metrics.cors.allow-origins=http://foo.com +cors.paths.0.path-pattern=/health +cors.paths.0.allow-origins=http://there.com +cors.paths.1.path-pattern=/metrics +cors.paths.1.allow-origins=http://foo.com ---- include::{rootdir}/includes/cors.adoc[tag=accessing-shared-resources-intro] diff --git a/docs/src/main/asciidoc/se/cors.adoc b/docs/src/main/asciidoc/se/cors.adoc index ea35cfe1c20..4dbae785989 100644 --- a/docs/src/main/asciidoc/se/cors.adoc +++ b/docs/src/main/asciidoc/se/cors.adoc @@ -1,6 +1,6 @@ /////////////////////////////////////////////////////////////////////////////// - Copyright (c) 2022, 2023 Oracle and/or its affiliates. + Copyright (c) 2022, 2024 Oracle and/or its affiliates. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -29,6 +29,9 @@ :rootdir: {docdir}/.. include::{rootdir}/includes/se.adoc[] +:ws-cors-javadoc: {webserver-cors-javadoc-base-url}/io/helidon/webserver/cors +:cors-javadoc: {cors-javadoc-base-url}/io/helidon/cors +:ws-cors-artifact: io.helidon.webserver:helidon-webserver-cors == Contents @@ -42,6 +45,22 @@ include::{rootdir}/includes/se.adoc[] == Overview include::{rootdir}/includes/cors.adoc[tag=cors-intro] +// Still in the Before You Begin section... + +==== Choosing How To Implement CORS +You can add CORS support to your application in either or both of the following ways, depending on your specific requirements: + +* Use configuration and Helidon's automatic feature detection: *recommended*. ++ +If you add the Helidon CORS Maven artifact to your project, at runtime Helidon automatically discovers it and activates it according to configuration. You do not need to change your Java code. Instead, you control your application's CORS behavior entirely using configuration linked to the resource paths your application exposes. ++ +This is the simplest way to set up CORS for your service, and we recommend you use this approach. +* Use the Helidon CORS API to add CORS processing to the routing for specific services or endpoints in your application. ++ +Your code creates one or more link:{ws-cors-javadoc}/CorsSupport.html[`CorsSupport`] instances and adds them to routing rules. + +The following sections briefly illustrate each approach. + include::{rootdir}/includes/dependencies.adoc[] [source,xml,subs="attributes+"] @@ -52,21 +71,36 @@ include::{rootdir}/includes/dependencies.adoc[] ---- +If you also choose to write Java code to add CORS behavior explicitly to your application also add the following dependency as well: +[source,xml,subs="attributes+"] +---- + + io.helidon.cors + helidon-cors + +---- + == API +=== Using the Config-only Approach +If you add the `{ws-cors-artifact}` Maven artifact to your project you do not have to add any CORS-specific code to your application to implement CORS. Express the CORS behavior you want in configuration, associating path patterns with the CORS settings you want to apply to the matching paths. + +See the xref:_configuration[configuration] section below for more information. + +=== Adding Code to Include CORS in Routing Rules Every Helidon SE application explicitly creates routing rules that govern how Helidon delivers each incoming -request to the code that needs to respond. To add CORS behavior to endpoints, you need to make only minimal +request to the code that needs to respond. You can add CORS behavior to specific endpoints with only minimal changes to how you set up the routing for those endpoints. Using the Helidon SE CORS API, you define the CORS behavior that you want and then include that behavior as you build the routing rules for the services in your application. The Helidon SE CORS API provides two key classes that you use in your application: -* `CorsSupport` - Represents information about resource sharing for a single resource. +* link:{ws-cors-javadoc}/CorsSupport.html[`CorsSupport`] - Represents information about resource sharing for a single resource. Typically, you create one `CorsSupport` instance for each distinct resource in your application (such as the `/greet` resource in the QuickStart greeting application) that should participate in CORS. -* `CrossOriginConfig` - Represents the details for a particular type of sharing, such as which origins are +* link:{cors-javadoc}/CrossOriginConfig.html[`CrossOriginConfig`] - Represents the details for a particular type of sharing, such as which origins are allowed to have access using which HTTP methods, etc. Create one instance of `CrossOriginConfig` for each different type of sharing you need. @@ -82,10 +116,10 @@ by including CORS into the routing you construct for your application. For each distinct resource or subresource your application exposes: -. Create a link:{webserver-cors-javadoc-base-url}/io/helidon/webserver/cors/CorsSupport.html[`CorsSupport`] instance +. Create a link:{ws-cors-javadoc}/CorsSupport.html[`CorsSupport`] instance corresponding to the resource. . For each different type of sharing you want to provide for that resource: -.. Create a link:{webserver-cors-javadoc-base-url}/io/helidon/webserver/cors/CrossOriginConfig.html[`CrossOriginConfig`] instance. +.. Create a link:{cors-javadoc}/CrossOriginConfig.html[`CrossOriginConfig`] instance. The `CrossOriginConfig` Java class represents the details for a particular type of sharing, such as which origins are allowed to share via which HTTP methods, etc. .. Add the `CrossOriginConfig` to the `CorsSupport` instance for this resource. @@ -143,9 +177,24 @@ By adding the few additional lines described above you allow the greeting applic == Configuration -You can use configuration in combination with the Helidon CORS SE API to add CORS support to your resources by -replacing some Java code with declarative configuration. This also gives your users a way to override the -CORS behavior of your services without requiring the code to change. +You can use configuration instead of or in combination with the Helidon CORS SE API to add CORS support to your resources by +replacing some Java code with declarative configuration. + +=== Configuration for Automatic CORS Processing +Recall that simply by adding the `{ws-cors-artifact}` artifact to your project you allow Helidon to automatically use configuration to set up CORS behavior throughout your application. + +To use this automatic support, make sure your configuration contains a `cors` section which contains mapped CORS configuration as described below and as shown in the following example. +[source,hocon,subs="attributes+"] +---- +cors: + paths: + - path-pattern: /greeting + allow-origins: ["http://foo.com", "http://there.com", "http://other.com"] + allow-methods: ["PUT", "DELETE"] + - path-pattern: / + allow-methods: ["GET", "HEAD", "OPTIONS", "POST"] +---- + include::{rootdir}/includes/cors.adoc[tag=cors-configuration-formats-intro] include::{rootdir}/includes/cors.adoc[tag=basic-cross-origin-config] @@ -235,54 +284,7 @@ include::{rootdir}/includes/cors.adoc[tag=cors-and-requested-uri-wrapup] include::{rootdir}/includes/cors.adoc[tag=understanding-cors-support-in-services] include::{rootdir}/includes/cors.adoc[tag=builtin-getting-started] -==== Controlling CORS for Built-in Services - -===== Using the API -Although services such as health, metrics, and OpenAPI are built into Helidon, to use them your application must create -instances of the services and then use those instances in building your application's routing rules. - -Recall that each -service type has a `Builder` class. To control the CORS behavior of a built-in service using the API, follow these steps: - -. Create a `Builder` for the type of service of interest. -. Build an instance of `CrossOriginConfig` with the settings you want. -. Invoke the `builder.crossOriginConfig` method, passing that `CrossOriginConfig` instance. -. Invoke the builder's `build` method to initialize the service instance. -. Use the service instance in preparing the routing rules. - -The following excerpt shows changes to the - link:{helidon-github-tree-url}/examples/quickstarts/helidon-quickstart-se[Helidon SE QuickStart example] which limit -sharing of the `/metrics` endpoint to `\http://foo.com`. - -[source,java] ----- -private static Routing createRouting(Config config) { - CrossOriginConfig.Builder metricsCrossOriginConfigBuilder = CrossOriginConfig.builder() // <1> - .allowOrigins("http://foo.com"); - RestServiceSettings.Builder restServiceSettingsBuilder = RestServiceSettings.builder() - .crossOriginConfig(metricsCrossOriginConfigBuilder); // <2> - MetricsSupport metrics = MetricsSupport.builder() - .restServiceSettings(restServiceSettingsBuilder) // <3> - .build(); - GreetService greetService = new GreetService(config); - HealthSupport health = HealthSupport.builder() - .addLiveness(HealthChecks.healthChecks()) // Adds a convenient set of checks - .build(); - - return Routing.builder() - .register(health) // Health at "/health" - .register(metrics) // Metrics at "/metrics" // <4> - .register("/greet", greetService) - .build(); -} ----- -<1> Create the `CrossOriginConfig.Builder` for metrics, limiting sharing to `\http://foo.com`. -<2> Use the `CrossOriginConfig.Builder` instance in constructing -the `RestServiceSetting.Builder` (which assigns common settings such as the CORS configuration and the web context for the service endpoint). -<3> Use the `RestServiceSetting.Builder` in preparing the `MetricsSupport` service. -<4> Use the `MetricsSupport` object in creating the routing rules. - -include::{rootdir}/includes/cors.adoc[tag=configuring-cors-for-builtin-services,leveloffset=+2] +include::{rootdir}/includes/cors.adoc[tag=configuring-cors-for-builtin-services,leveloffset=+1] include::{rootdir}/includes/cors.adoc[tag=accessing-shared-resources-intro] [source,bash]