Help us understand the problem. What is going on with this article?

Spring MVC で CORS 設定

More than 1 year has passed since last update.

はじめに

Angular と Spring Boot による SPA 開発時、
クライアントサイドとサーバサイドアプリを、
別々のマシンにホスティングする場合の考慮事項をまとめた記事です。

  • Same Origin Policy
  • Cross-Origin Resource Sharing
  • Spring MVC の Cross-Origin Resource Sharing 設定
  • サーバホスト名解決

の4構成でご説明します。

なお、サンプルのアプリケーションを公開しています。

Same Origin Policy

同一オリジンポリシーと訳されます。

下の図は、

でそれぞれ動作している場合を表しています。

Same Origin Policy.png

Webブラウザからは、https://angular.example.com にアクセスして、
index.html や jsファイルがダウンロードされて、
Webブラウザ上で Angular アプリが動作します。

この時、クライアントサイドはデータの取得を行うためSpring Boot が動作しているサーバ https://boot.example.com に、非同期通信を行いますが、そのリクエストがエラーとなります。

同一オリジンポリシー - MDN web docs にあるように、
https://angular.example.com から読み込まれたスクリプトファイルから、
https://boot.example.com のリソースへアクセスできないように制限がかけられるためです。

これを Same Origin Policy(同一オリジンポリシー)といいます。

Cross-Origin Resource Sharing

CORS と略されます。

Same Origin Policy(同一オリジンポリシー)はセキュアなWebアプリケーションを構築する上では重要な概念です。
一方で、クライアントサイドとサーバサイドを別々のマシンで動作させる必要がある場合には、Cross-Origin Resource Sharing(オリジン間リソース共有と訳されます) を利用します。

基本的な仕組みは、HTTPリクエストヘッダとHTTPレスポンスヘッダのやり取りになります。

まず、クライアントサイドからは、Originというリクエストヘッダーを設定してリクエストを送ります。この時の値は、スクリプトファイルの生成元、https://angular.example.com を設定します。

Origin: https://angular.example.com

次に、リクエストを受け取ったサーバは、リクエストヘッダーのOriginの値が正当なものか判定します。
もし正当なものであれば、Access-Control-Allow-Originというレスポンスヘッダーを添えて、正常にレスポンスを返却します。

Access-Control-Allow-Origin: https://angular.example.com

より詳細な情報は、 オリジン間リソース共有 (CORS) - MDN web docs を参照してください。

Spring MVC の Cross-Origin Resource Sharing 設定

Spring MVC では簡単に Cross-Origin Resource Sharing の設定が行えます。
今回は、

  • コントローラークラス・メソッドに個別にアノテーションで設定する
  • Java Configでアプリケーション全体で設定する

の二つの方法をご紹介します。

なお、環境構築の簡略化のため、Spring Boot を使う事を前提とします。

コントローラークラス・メソッドに個別にアノテーションで設定する

@CrossOrigin アノテーションを使います。
実際の使い方は下記です。

package com.example.server.web.rest;

import com.example.server.service.TaskService;
import com.example.server.web.response.TaskResponse;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/tasks")
public class TaskRestController {

    private final TaskService todoService;

    public TaskRestController(TaskService todoService) {
        this.todoService = todoService;
    }

    @GetMapping
    @CrossOrigin
    public List<TaskResponse> findAll() {
        return this.todoService.findAll().stream().map(todo -> new TaskResponse(todo)).collect(Collectors.toList());
    }
}

異なるオリジンからのアクセスを許容するコントローラーメソッドにアノテーションを付加します。
こうすることによって、Cross-Origin Resource Sharing が有効になります。

ただし、デフォルトの設定では、許容する Origin がワイルドカード、
つまり世界中すべてのWebサイトで生成されたスクリプトからリクエストを許容する設定になっています。

@CrossOrigin アノテーションの origins 属性を使って許容する Origin を設定することができます。

具体的な方法は下記です。

package com.example.server.web.rest;

import com.example.server.service.TaskService;
import com.example.server.web.response.TaskResponse;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/api/tasks")
public class TaskRestController {

    private final TaskService todoService;

    public TaskRestController(TaskService todoService) {
        this.todoService = todoService;
    }

    @GetMapping
    @CrossOrigin(origins = {"http://localhost:4200"})
    public List<TaskResponse> findAll() {
        return this.todoService.findAll().stream().map(todo -> new TaskResponse(todo)).collect(Collectors.toList());
    }
}

文字列型の配列を受け取るようになっていますので、許容する Origin を複数設定することもできます。

他にも、細かな制限をかけることが可能なので、公式ドキュメント を参照し、要件に合わせた設定を行ってください。

Java Configでアプリケーション全体で設定する

先ほどコントローラーメソッドに設定した @CrossOriginアノテーションは削除しましょう。

下記の Java Config を Spring Boot のコンポーネントスキャンの有効なパッケージに配置してください。

package com.example.server.web.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
                .allowedOrigins("http://localhost:4200");
    }
}

WebMvcConfigurer は Web関連の Spring の設定をカスタマイズするためのインタフェースです。
@Configuration アノテーションを付加して Java Config として設定して、メソッドをオーバライドして設定をカスタマイズします。

CORS は addCorsMappings というメソッドで引数の CorsRegistry に設定していきます。

上記の例では、 Origin が http://localhost:4200 を許容している設定です。

より細かな設定が可能ですので、公式ドキュメント を参照し、要件に合わせた設定を行ってください。

サーバホスト名解決

CORS を扱う場合、開発環境では

Angular Spring Boot
http://localhost:4200 http://localhost:8080

で動作するのに対し、本番環境では例えば

Angular Spring Boot
https://angular.example.com https://boot.example.com

で動作するといった環境での違いを考慮する必要があります。
つまり、開発環境の Angular アプリは、http://localhost:8080 に、
本番環境のAngular アプリは、https://boot.example.com に非同期通信を行うように切り替えなくてはいけません。

Angularには、環境固有の設定値を持たせることができるので、それを使います。

Angular アプリケーションにはデフォルトで、src/environments 配下に環境ごとの設定ファイルを持ちます。

キャプチャ.PNG

  • environment.ts :開発環境用
  • environment.prod.ts :本番環境用

の設定ファイルになります。
デフォルトでは、

environment.ts
export const environment = {
  production: false
};
environment.prod.ts
export const environment = {
  production: true
};

だけが設定されています。
これを下記の様に編集します。

environment.ts
export const environment = {
  production: false,
  apiUrl: 'http://localhost:8080'
};
environment.prod.ts
export const environment = {
  production: true,
  apiUrl: 'http://boot.example.com'
};

apiUrl という設定を追加します。
production はAngularアプリケーションを開発モードで立ち上げるかの判定に利用されるので、残しておきましょう。
次に、非同期通信のリクエストを行うサービスクラスを編集します。

import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';
import {Task} from './task';
import {environment} from "../environments/environment";

@Injectable({
  providedIn: 'root'
})
export class TaskDataService {

  constructor(private http: HttpClient) {
  }

  findAll(): Observable<Task[]> {
    return this.http.get<Task[]>(environment.apiUrl + '/api/tasks');
  }
}

非同期通信のリクエストURLを環境設定ファイルから参照するようにします。
Angular CLI の ng serve で実行しているときやオプションなしの ng build でビルドした場合は、
開発環境用の environment.ts が適用されますが、
ng build --prod と本番環境用のオプションを付けてビルドした場合は、
本番環境用の environment.prod.ts が適用されます。

より詳細な情報は、リファレンス を参照してください。

補足

  • Angular CLI :Angularアプリの開発をサポートするコマンドライン
  • ng serve :Angular CLIに含まれるAngularアプリの検証サーバ
  • ng build :Angular CLIに含まれるAngularアプリのビルド機能
m_kikuchi
技術部・ラーニングサービス技術課に所属し、Spring系のコースを担当します。
casareal
システム開発/評価・検証支援/品質改善支援サービスと現場に即した実践的なIT研修サービスを提供しています。
https://www.casareal.co.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away