これは、なにをしたくて書いたもの?
そういえば、Jakarta EE(Java EE)でCORSの設定をしたことがないなと思いまして。
実装方法はいろいろあると思うのですが、すでに用意されているものとかないのかなと思って少し見ていたら、RESTEasyにあったので
軽く試してみることにしました。
RESTEasyのCorsFilter
CORS(オリジン間リソース共有)および使用するヘッダーについては、MDNを参照ということで。
オリジン間リソース共有 (CORS) - HTTP | MDN
RESTEasyが提供するCorsFilter
についてのドキュメントはこちらです。
ほぼ内容がありませんが。設定内容についてはJavadocを見てね、という感じです。
CorsFilter (RESTEasy 6.2.8.Final API)
設定をしたら、Application
にシングルトンとして登録します。
You must allocate this and register it as a singleton provider from your Application class.
これ以上の説明はないので、実際に試してみましょう。
Java SE環境でSeBootstrapを使い、簡単なJakarta RESTful Web Services(JAX-RS)のリソースクラスを作成して確認することにします。
環境
今回の環境はこちら。
$ java --version openjdk 21.0.2 2024-01-16 OpenJDK Runtime Environment (build 21.0.2+13-Ubuntu-122.04.1) OpenJDK 64-Bit Server VM (build 21.0.2+13-Ubuntu-122.04.1, mixed mode, sharing) $ mvn --version Apache Maven 3.9.6 (bc0240f3c744dd6b6ec2920b3cd08dcc295161ae) Maven home: $HOME/.sdkman/candidates/maven/current Java version: 21.0.2, vendor: Private Build, runtime: /usr/lib/jvm/java-21-openjdk-amd64 Default locale: ja_JP, platform encoding: UTF-8 OS name: "linux", version: "5.15.0-105-generic", arch: "amd64", family: "unix"
サンプルコードを用意する
それでは、まずはサンプルとなるソースコードを用意します。
Maven依存関係など。
<properties> <maven.compiler.release>21</maven.compiler.release> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> </properties> <dependencies> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-core</artifactId> <version>6.2.8.Final</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-undertow-cdi</artifactId> <version>6.2.8.Final</version> </dependency> </dependencies>
今回使用するCorsFilter
はresteasy-core
に含まれています。Java SE環境でRESTEasyを起動するためのresteasy-undertow-cdi
にも
含まれているのですが、明示的に指定することにしました。
なんとなくbeans.xml
も用意しておきます。中身は空で。
src/main/resources/META-INF/beans.xml
Application
のサブクラス。
src/main/java/org/littlewings/resteasy/cors/RestApplication.java
package org.littlewings.resteasy.cors; import jakarta.ws.rs.ApplicationPath; import jakarta.ws.rs.core.Application; import org.jboss.resteasy.plugins.interceptors.CorsFilter; import java.util.Set; @ApplicationPath("") public class RestApplication extends Application { @Override public Set<Class<?>> getClasses() { return Set.of(HelloResource.class); } @Override public Set<Object> getSingletons() { CorsFilter corsFilter = new CorsFilter(); // 後で return Set.of(corsFilter); } }
JAX-RSリソースクラス。
src/main/java/org/littlewings/resteasy/cors/HelloResource.java
package org.littlewings.resteasy.cors; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.container.PreMatching; import jakarta.ws.rs.core.MediaType; @Path("hello") public class HelloResource { @GET @Produces(MediaType.TEXT_PLAIN) public String message() { return "Hello World!!"; } }
main
クラス。
src/main/java/org/littlewings/resteasy/cors/App.java
package org.littlewings.resteasy.cors; import jakarta.ws.rs.SeBootstrap; import org.jboss.logging.Logger; import java.util.concurrent.ExecutionException; public class App { public static void main(String... args) throws ExecutionException, InterruptedException { Logger logger = Logger.getLogger(App.class); SeBootstrap.Configuration configuration = SeBootstrap .Configuration .builder() .host("0.0.0.0") .port(8080) .build(); SeBootstrap.Instance instance = SeBootstrap .start(new RestApplication(), configuration) .toCompletableFuture() .get(); logger.info("server startup."); System.console().readLine("> Enter stop."); instance .stop() .toCompletableFuture() .get(); } }
これで準備は完了です。
CorsFilterを設定する
では、Application
のサブクラスに書いていたCorsFilter
の設定をします。
今回はこんな感じにしました。
@Override public Set<Object> getSingletons() { CorsFilter corsFilter = new CorsFilter(); corsFilter.getAllowedOrigins().add("http://localhost:3000"); corsFilter.setAllowedMethods("POST, GET, PUT, DELETE, OPTIONS"); corsFilter.setAllowedHeaders("Content-Type, Origin, Authorization"); corsFilter.setCorsMaxAge(86400); corsFilter.setAllowCredentials(true); return Set.of(corsFilter); }
Access-Control-Allow-Origin
に対する設定方法がないのでは…?と思ったのですが、Set
を取得してこちらに直接追加するみたいです…。
そういえば、ドキュメントにもそう書いていました…。
CorsFilter filter = new CorsFilter(); filter.getAllowedOrigins().add("http://localhost");
その他はsetterがあるんですけどね。
CorsFilter (RESTEasy 6.2.8.Final API)
確認する
準備はできたので、確認してみます。
起動。
$ mvn compile exec:java -Dexec.mainClass=org.littlewings.resteasy.cors.App
起動しました。
INFO: server startup. > Enter stop.
設定したヘッダーがレスポンスに含まれるかどうか、OPTIONS
で確認。
$ curl -i -XOPTIONS -H 'Origin: http://localhost:3000' localhost:8080/hello HTTP/1.1 200 OK Connection: keep-alive Access-Control-Allow-Origin: http://localhost:3000 Vary: Origin Access-Control-Allow-Credentials: true Content-Length: 0 Access-Control-Max-Age: 86400 Date: Sat, 04 May 2024 12:02:17 GMT
指定のオリジンからアクセス。
$ curl -i -H 'Origin: http://localhost:3000' localhost:8080/hello HTTP/1.1 200 OK Connection: keep-alive Access-Control-Allow-Origin: http://localhost:3000 Vary: Origin Access-Control-Allow-Credentials: true Content-Type: text/plain;charset=UTF-8 Content-Length: 13 Date: Sat, 04 May 2024 12:03:05 GMT Hello World!!
許可していないオリジンからのアクセス。
$ curl -i -H 'Origin: http://example.com' localhost:8080/hello HTTP/1.1 403 Forbidden Connection: keep-alive Content-Length: 0 Date: Sat, 04 May 2024 12:03:41 GMT
拒否されました。
OKですね。
少しポイントを
CorsFilter
はJAX-RSのContainerRequestFilter
、ContainerResponseFilter
として実装されています。
CorsFilter (RESTEasy 6.2.8.Final API)
このフィルターには@PreMatching
というアノテーションが付与されているのですが、これはリソースメソッドにマッチする前に
適用することを指示するもののようです。
As stated above, a ContainerRequestFilter that is annotated with @PreMatching is executed upon receiving a client request but before a resource method is matched. Thus, this type of filter has the ability to modify the input to the matching algorithm (see Request Matching) and, consequently, alter its outcome.
Jakarta RESTful Web Services / Filters and Interceptors / Filters
ところで、今回のようなCorsFilter
はApplication#getSingletons
を使って登録するしかなさそうなのですが、現在このメソッドは
非推奨になっているようです。
Deprecated. Automatic discovery of resources and providers or the getClasses method is preferred over getSingletons.
今後はどうしたらいいんでしょうね…。
WildFlyでの設定は?
今回はRESTEasyが実装しているクラスを使いましたが、WildFlyでやろうとするとundertowサブシステムのfilter
を使って登録することに
なるようです。
WildFly Full Model Reference / subsystem=undertow / configuration=filter / response-header
おわりに
RESTEasyの提供するCorsFilter
を使ってCORSの設定をしてみました。
本当、実装方法としてはいろいろと考えられると思うのですが、どうするのが一般的(?)なんでしょうね。