REST API で CORS を有効化

このガイドでは、レスポンスに Cross-Origin Resource Sharing(CORS)のヘッダーを含む Spring で "Hello, World" RESTful Web サービスを作成するプロセスを説明します。Spring CORS サポートの詳細については、このブログ投稿を参照してください (英語)

構築するもの

次のように、http://localhost:8080/greeting で HTTP GET リクエストを受け入れ、グリーティングの JSON 表現で応答するサービスを構築します。

{"id":1,"content":"Hello, World!"}

次のように、クエリ文字列のオプションの name パラメーターを使用して、グリーティングをカスタマイズできます。

http://localhost:8080/greeting?name=User

name パラメーター値は、次のように、World のデフォルト値をオーバーライドし、レスポンスに反映されます。

{"id":1,"content":"Hello, User!"}

このサービスは、Spring Framework CORS サポートを使用して関連する CORS レスポンスヘッダーを追加するという点で、REST API の作成で説明されているサービスとは少し異なります。

必要なもの

本ガイドの完成までの流れ

ほとんどの Spring 入門ガイドと同様に、最初から始めて各ステップを完了するか、すでに慣れている場合は基本的なセットアップステップをバイパスできます。いずれにしても、最終的に動作するコードになります。

最初から始めるには、Spring Initializr から開始に進みます。

基本スキップするには、次の手順を実行します。

完了したときは、gs-rest-service-cors/complete のコードに対して結果を確認できます。

Spring Initializr から開始

IDE を使用する場合はプロジェクト作成ウィザードを使用します。IDE を使用せずにコマンドラインなどで開発する場合は、この事前に初期化されたプロジェクトからプロジェクトを ZIP ファイルとしてダウンロードできます。このプロジェクトは、このチュートリアルの例に合うように構成されています。

プロジェクトを手動で初期化するには:

  1. IDE のメニューまたはブラウザーから Spring Initializr を開きます。アプリケーションに必要なすべての依存関係を取り込み、ほとんどのセットアップを行います。

  2. Gradle または Maven のいずれかと、使用する言語を選択します。このガイドは、Java を選択したことを前提としています。

  3. 依存関係をクリックして、Spring Web を選択します。

  4. 生成をクリックします。

  5. 結果の ZIP ファイルをダウンロードします。これは、選択して構成された Web アプリケーションのアーカイブです。

EclipseIntelliJ のような IDE は新規プロジェクト作成ウィザードから Spring Initializr の機能が使用できるため、手動での ZIP ファイルのダウンロードやインポートは不要です。
プロジェクトを Github からフォークして、IDE または他のエディターで開くこともできます。

httpclient5 依存関係の追加

テスト (complete/src/test/java/com/example/restservicecors/GreetingIntegrationTests.java 内) には Apache httpclient5 ライブラリが必要です。

Apache httpclient5 ライブラリを Maven に追加するには、次の依存関係を追加します。

<dependency>
  <groupId>org.apache.httpcomponents.client5</groupId>
  <artifactId>httpclient5</artifactId>
  <scope>test</scope>
</dependency>

次のリストは、完成した pom.xml ファイルを示しています。

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.3.0</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>rest-service-cors-complete</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>rest-service-cors-complete</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.apache.httpcomponents.client5</groupId>
			<artifactId>httpclient5</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

Apache httpclient5 ライブラリを Gradle に追加するには、次の依存関係を追加します。

testImplementation 'org.apache.httpcomponents.client5:httpclient5'

次のリストは、完成した build.gradle ファイルを示しています。

plugins {
	id 'org.springframework.boot' version '3.3.0'
	id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.apache.httpcomponents.client5:httpclient5'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
	useJUnitPlatform()
}

リソース表現クラスを作成する

プロジェクトとビルドシステムをセットアップしたため、Web サービスを作成できます。

サービスの相互作用について考えることでプロセスを開始します。

サービスは、/greeting への GET リクエストを処理します。オプションで、クエリ文字列に name パラメーターを使用します。GET リクエストは、あいさつを表すために本文に JSON を含む 200 OK レスポンスを返す必要があります。次のようになります。

{
    "id": 1,
    "content": "Hello, World!"
}

id フィールドは挨拶の一意の識別子であり、content は挨拶のテキスト表現です。

挨拶表現をモデル化するには、リソース表現クラスを作成します。次のリスト(src/main/java/com/example/restservicecors/Greeting.java から)が示すように、id および content データのフィールド、コンストラクター、アクセサーを持つプレーンな古い Java オブジェクトを提供します。

package com.example.restservicecors;

public class Greeting {

	private final long id;
	private final String content;

	public Greeting() {
		this.id = -1;
		this.content = "";
	}

	public Greeting(long id, String content) {
		this.id = id;
		this.content = content;
	}

	public long getId() {
		return id;
	}

	public String getContent() {
		return content;
	}
}
Spring は、Jackson JSON (英語) ライブラリを使用して、型 Greeting のインスタンスを JSON に自動的にマーシャリングします。

リソースコントローラーを作成する

RESTful Web サービスを構築する Spring のアプローチでは、HTTP リクエストはコントローラーによって処理されます。これらのコンポーネントは @Controller (Javadoc) アノテーションによって簡単に識別され、次のリスト(src/main/java/com/example/restservicecors/GreetingController.java から)に示されている GreetingController は、Greeting クラスの新しいインスタンスを返すことにより、/greeting に対する GET リクエストを処理します。

package com.example.restservicecors;

import java.util.concurrent.atomic.AtomicLong;

import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class GreetingController {

	private static final String template = "Hello, %s!";

	private final AtomicLong counter = new AtomicLong();
	@CrossOrigin(origins = "http://localhost:9000")
	@GetMapping("/greeting")
	public Greeting greeting(@RequestParam(required = false, defaultValue = "World") String name) {
		System.out.println("==== get greeting ====");
		return new Greeting(counter.incrementAndGet(), String.format(template, name));
	}

}

このコントローラーは簡潔でシンプルですが、内部ではさまざまなことが行われています。段階的にそれを分解します。

@RequestMapping アノテーションは、/greeting への HTTP リクエストが greeting() メソッドにマップされることを保証します。

前の例では、@RequestMapping(method = RequestMethod.GET) のショートカットとして機能する @GetMapping アノテーションを使用しています。この場合、テストに便利な GET を使用します。Spring は、オリジンが CORS 構成と一致しない場合でも GET リクエストを拒否します。ブラウザーは CORS プリフライトリクエストを送信する必要はありませんが、プリフライトチェックをトリガーする場合は、@PostMapping を使用して、本文で JSON を受け入れることができます。

@RequestParam は、name クエリ文字列パラメーターの値を greeting() メソッドの name パラメーターにバインドします。このクエリ文字列パラメーターは required ではありません。リクエストにない場合、World の defaultValue が使用されます。

メソッド本体の実装は、counter の次の値に基づく id 属性の値と、クエリパラメーターまたはデフォルト値に基づく content の値を使用して、新しい Greeting オブジェクトを作成して返します。また、グリーティング template を使用して、指定された name をフォーマットします。

従来の MVC コントローラーと前述の RESTful Web サービスコントローラーの主な違いは、HTTP レスポンスの本文が作成される方法です。この RESTful Web サービスコントローラーは、ビューテクノロジーに依存して、グリーティングデータを HTML にサーバー側でレンダリングするのではなく、Greeting オブジェクトを生成して返します。オブジェクトデータは、JSON として HTTP レスポンスに直接書き込まれます。

これを実現するために、@RestController (Javadoc) アノテーションは、すべてのメソッドがデフォルトで @ResponseBody (Javadoc) セマンティクスを継承することを前提としています。返されたオブジェクトデータはレスポンス本文に直接挿入されます。

Spring の HTTP メッセージコンバーターのサポートのおかげで、Greeting オブジェクトは自然に JSON に変換されます。Jackson (英語) はクラスパス上にあるため、Spring の MappingJackson2HttpMessageConverter (Javadoc) が自動的に選択され、Greeting インスタンスが JSON に変換されます。

CORS を有効にする

個々のコントローラーまたはグローバルで、クロスオリジンリソース共有(CORS)を有効にできます。次のトピックでは、その方法について説明します。

コントローラーメソッド CORS 設定

RESTful Web サービスのレスポンスに CORS アクセス制御ヘッダーが含まれるように、次のリスト(src/main/java/com/example/restservicecors/GreetingController.java から)が示すように、ハンドラーメソッドに @CrossOrigin アノテーションを追加する必要があります。

	@CrossOrigin(origins = "http://localhost:9000")
	@GetMapping("/greeting")
	public Greeting greeting(@RequestParam(required = false, defaultValue = "World") String name) {
		System.out.println("==== get greeting ====");
		return new Greeting(counter.incrementAndGet(), String.format(template, name));

この @CrossOrigin アノテーションは、この特定のメソッドでのみクロスオリジンリソース共有を有効にします。デフォルトでは、@RequestMapping アノテーションで指定されたすべてのオリジン、すべてのヘッダー、HTTP メソッドを許可します。また、30 分の maxAge が使用されます。次のいずれかのアノテーション属性の値を指定することにより、この動作をカスタマイズできます。

  • origins

  • originPatterns

  • methods

  • allowedHeaders

  • exposedHeaders

  • allowCredentials

  • maxAge.

この例では、http://localhost:9000 のみがクロスオリジンリクエストの送信を許可しています。

また、コントローラークラスレベルで @CrossOrigin アノテーションを追加して、このクラスのすべてのハンドラーメソッドで CORS を有効にすることもできます。

グローバル CORS 設定

きめの細かいアノテーションベースの構成に加えて (または代替として)、いくつかのグローバル CORS 構成も定義できます。これは Filter の使用に似ていますが、Spring MVC 内で宣言して、きめの細かい @CrossOrigin 構成と組み合わせることができます。デフォルトでは、すべてのオリジンと GETHEADPOST メソッドが許可されます。

次のリスト(src/main/java/com/example/restservicecors/GreetingController.java から)は、GreetingController クラスの greetingWithJavaconfig メソッドを示しています。

	@GetMapping("/greeting-javaconfig")
	public Greeting greetingWithJavaconfig(@RequestParam(required = false, defaultValue = "World") String name) {
		System.out.println("==== in greeting ====");
		return new Greeting(counter.incrementAndGet(), String.format(template, name));
greetingWithJavaconfig メソッドと greeting メソッド(コントローラーレベルの CORS 構成で使用)の違いは、ルート(/greeting ではなく /greeting-javaconfig)と @CrossOrigin オリジンの存在です。

次のリスト(src/main/java/com/example/restservicecors/RestServiceCorsApplication.java から)は、アプリケーションクラスに CORS マッピングを追加する方法を示しています。

	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:9000");
			}
		};
	}

プロパティ(例の allowedOrigins など)を簡単に変更したり、この CORS 構成を特定のパスパターンに適用したりできます。

グローバルレベルとコントローラーレベルの CORS 構成を組み合わせることができます。

アプリケーションクラスの作成

Spring Initializr は、必要最低限のアプリケーションクラスを作成します。次のリスト(initial/src/main/java/com/example/restservicecors/RestServiceCorsApplication.java から)は、その初期クラスを示しています。

package com.example.restservicecors;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class RestServiceCorsApplication {

	public static void main(String[] args) {
		SpringApplication.run(RestServiceCorsApplication.class, args);
	}

}

クロスオリジンリソース共有の処理方法を構成するメソッドを追加する必要があります。次のリスト(complete/src/main/java/com/example/restservicecors/RestServiceCorsApplication.java から)は、その方法を示しています。

	@Bean
	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:9000");
			}
		};
	}

次のリストは、完成したアプリケーションクラスを示しています。

package com.example.restservicecors;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class RestServiceCorsApplication {

	public static void main(String[] args) {
		SpringApplication.run(RestServiceCorsApplication.class, args);
	}

	@Bean
	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/greeting-javaconfig").allowedOrigins("http://localhost:9000");
			}
		};
	}

}

@SpringBootApplication は、次のすべてを追加する便利なアノテーションです。

  • @Configuration: アプリケーションコンテキストの Bean 定義のソースとしてクラスにタグを付けます。

  • @EnableAutoConfiguration: クラスパス設定、他の Bean、さまざまなプロパティ設定に基づいて Bean の追加を開始するよう Spring Boot に指示します。例: spring-webmvc がクラスパスにある場合、このアノテーションはアプリケーションに Web アプリケーションとしてフラグを立て、DispatcherServlet のセットアップなどの主要な動作をアクティブにします。

  • @ComponentScan: Spring に、com/example パッケージ内の他のコンポーネント、構成、サービスを探して、コントローラーを検出させるように指示します。

main() メソッドは、Spring Boot の SpringApplication.run() メソッドを使用してアプリケーションを起動します。XML が 1 行もないことに気付きましたか? web.xml ファイルもありません。この Web アプリケーションは 100% 純粋な Java であり、接続機能やインフラストラクチャの構成に対処する必要はありませんでした。

実行可能 JAR を構築する

コマンドラインから Gradle または Maven を使用してアプリケーションを実行できます。必要なすべての依存関係、クラス、リソースを含む単一の実行可能 JAR ファイルを構築して実行することもできます。実行可能な jar を構築すると、開発ライフサイクル全体、さまざまな環境などで、アプリケーションとしてサービスを簡単に提供、バージョン管理、デプロイできます。

Gradle を使用する場合、./gradlew bootRun を使用してアプリケーションを実行できます。または、次のように、./gradlew build を使用して JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar build/libs/gs-rest-service-cors-0.1.0.jar

Maven を使用する場合、./mvnw spring-boot:run を使用してアプリケーションを実行できます。または、次のように、./mvnw clean package で JAR ファイルをビルドしてから、JAR ファイルを実行できます。

java -jar target/gs-rest-service-cors-0.1.0.jar
ここで説明する手順は、実行可能な JAR を作成します。クラシック WAR ファイルを作成することもできます。

ロギング出力が表示されます。サービスは数秒以内に起動して実行されるはずです。

サービスをテストする

サービスが開始されたため、ブラウザーで http://localhost:8080/greeting にアクセスすると、次のように表示されます。

{"id":1,"content":"Hello, World!"}

http://localhost:8080/greeting?name=User にアクセスして、name クエリ文字列パラメーターを提供します。次のように、content 属性の値は Hello, World! から Hello User! に変わります。

{"id":2,"content":"Hello, User!"}

この変更は、GreetingController の @RequestParam 配置が期待どおりに機能することを示しています。name パラメーターには World のデフォルト値が指定されていますが、クエリ文字列を介して常に明示的にオーバーライドできます。

また、id 属性が 1 から 2 に変更されました。これは、複数のリクエストにわたって同じ GreetingController インスタンスに対して作業していること、およびその counter フィールドが予想どおりに各呼び出しで増加していることを証明しています。

これで、CORS ヘッダーが適切に配置されていることをテストし、別のオリジンの Javascript クライアントがサービスにアクセスできるようになります。そのためには、サービスを使用する Javascript クライアントを作成する必要があります。次のリストは、このようなクライアントを示しています。

最初に、次の内容の hello.js (complete/public/hello.js から)という名前の単純な Javascript ファイルを作成します。

$(document).ready(function() {
    $.ajax({
        url: "http://localhost:8080/greeting"
    }).then(function(data, status, jqxhr) {
       $('.greeting-id').append(data.id);
       $('.greeting-content').append(data.content);
       console.log(jqxhr);
    });
});

このスクリプトは、jQuery を使用して http://localhost:8080/greeting で REST サービスを使用します。次のリスト ( complete/public/index.html から) に示すように、index.html によってロードされます。

<!DOCTYPE html>
<html>
    <head>
        <title>Hello CORS</title>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
        <script src="hello.js"></script>
    </head>

    <body>
        <div>
            <p class="greeting-id">The ID is </p>
            <p class="greeting-content">The content is </p>
        </div>
    </body>
</html>

CORS の動作をテストするには、別のサーバーまたはポートからクライアントを起動する必要があります。そうすることで、2 つのアプリケーション間の衝突が回避されるだけでなく、クライアントコードがサービスとは異なるオリジンから提供されるようになります。

ローカルホストのポート 9000 で実行されているクライアントを起動するには、アプリケーションをポート 8080 で実行したままにし、別のターミナルで次の Maven コマンドを実行します。

./mvnw spring-boot:run -Dspring-boot.run.jvmArguments='-Dserver.port=9000'

Gradle を使用する場合は、次のコマンドを使用できます。

./gradlew bootRun --args="--server.port=9000"

アプリが起動したら、ブラウザーで http://localhost:9000 を開きます。サービスレスポンスには関連する CORS ヘッダーが含まれているため、ID とコンテンツがページにレンダリングされ、次のように表示されます。

Model data retrieved from the REST service is rendered into the DOM if the proper CORS headers are in the response.

ここで、ポート 9000 で実行中のアプリケーションを停止し、ポート 8080 で実行中のアプリケーションを維持し、別のターミナルで次の Maven コマンドを実行します。

./mvnw spring-boot:run -Dspring-boot.run.jvmArguments='-Dserver.port=9001'

Gradle を使用する場合は、次のコマンドを使用できます。

./gradlew bootRun --args="--server.port=9001"

アプリが起動したら、ブラウザーで http://localhost:9001 を開きます。ここで、次のように表示されます。

The browser will fail the request if the CORS headers are missing (or insufficient for theclient) from the response. No data will be rendered into the DOM.

ここでは、http://localhost:9001 ではなく http://localhost:9000 からのクロスオリジンリクエストのみが許可されているため、CORS ヘッダーが欠落している (またはクライアントにとって不十分である) ため、ブラウザーはリクエストに失敗し、値が DOM にレンダリングされません。

要約

おめでとう! Spring とのクロスオリジンリソース共有を含む RESTful Web サービスを開発しました。

コードを入手する