TL;DR 本書では、OAuth 2.0 を使って jQuery SPA および Spring Boot API を作り、セキュアに保護する方法を学んでいきます。新しい Spring Boot プロジェクトをスキャフォールディングして始めます。それから、それにエンドポイントを追加します。その後、Spring Security を使って全体を安全に保護します。最後に、API を使用するために( jQuery で) SPAを作ります。必要であれば、この GitHub レポジトリにある記事で作成した参照コードをご覧ください。

「OAuth 2.0 で Spring Boot API および SPA をセキュアに保護する方法を学びましょう。」

前提条件

本書に従うには、コンピューターに次のソフトウェアをインストールする必要があります。

OAuth 2.0 とは何か?

OAuth 2.0 が何かについて学ぶときは、公式仕様に限ります。

OAuth 2.0 は認証フレームワークで、ご自分のアクセスを得るためにリソース所有者(通常ユーザー)の代わりに、または、サード パーティ アプリケーションを許可して、サード パーティ アプリケーションが HTTP サービスへの制限付きアクセスの取得を許可します。- OAuth 2.0 __認証フレームワーク

つまり、このプロトコルはユーザーがご自分の資格情報を公開しないで、あるアプリ(Web アプリケーション、モバイル アプリなど)上のデータへの制限付きアクセスを別のアプリに付与することを許可します。OAuth 2.0 __についてご存じでない方や詳細について学びたい方はこのリソースをご確認ください.

OAuth 2.0 およびシングルページアプリ: Implicit Grant を使用する

今回は OAuth 2.0 でセキュアに保護した Spring Boot API からリソースを使うシングルページアプリ (SPA) から成るデモアプリケーションを作るので、OAuth 2.0 Implicit Grant として知られるものを実装しなければなりません。Implicit Grant は特に API を使用するパブリック SPA クライアント用に適合された OAuth 2.0 フローです。異なる種類のクライアント(モバイルアプリなど)を作成するのであれば、別のフローを選ばなければなりません。

異なるフローの詳細を学びたい方、互いに実装する方法を学びたい方は、このリソースをご覧ください

Securing Spring Boot APIs with OAuth 2.0

本章では、ゼロから新しい Spring Boot API を作り、OAuth 2.0 でそれをセキュアに保護し、API を使う SPA を作ります。コードを深く掘り下げる前に、 OAuth 2.0 プロトコルを実装する ID プロバイダーが必要です。このデモアプリケーションでは、Auth0 を使用し、そのために 無料 Auth0 アカウントはこちらから登録する必要があります。

Auth0 アカウントを登録したら、バックエンド API を表しリクエストを認証するために構成できるように、Auth0 上に API を作る必要があります。このためには、Auth0 ダッシュボード上の APIAPI **の作成 ボタンをクリックします。その後、ダッシュボードにフォームが表示されますので次を入力します。

  • API の名前(これは "Spring Boot Implicit Flow" などです)
  • 識別子(この場合 http://localhost:8080/api または有効 URL と同様のものです)
  • 署名アルゴリズム(このフィールドでは必ず RS256 を選択してください)

それから、作成 ボタンをクリックして Auth0 API を作成します。

このボタンをクリックすると、ダッシュボードはバックエンドを構成する方法について指示するセクションにリダイレクトします。本書では構成に関するすべてを対処しますから、本章は無視して、スコープ セクションに移動してください。そこでは、OAuth スコープ を登録します。

  • 名前:read:messages
  • 説明:「メッセージを読む」

上記の値をフォームに挿入したら、追加 ボタンを押してこの新しいスコープを Auth0 API に保存します。保存が終わったら、構成は終わり、バックエンド API に取りかかることができます。

Spring Boot API をスキャフォールディングする

本章では、API としての機能を果たす新規 Spring Boot アプリケーションを作ります。この API はパブリック エンドポイントとプライベート エンドポイントを公開します。初心者の方は Spring Initializr __のページに行き、次のようなフォームにご記入ください。

  • 生成Gradle Project
  • 使用言語Java
  • Spring Boot2.0.x
  • グループcom.example
  • 成果物spring-boot-oauth2

それから 依存関係 セクションで、検索ボックスを使ってWebSecurity の2つのライブラリを含めます。

Generating a new Spring Boot project with Spring Initializr

フォームに記入したら、プロジェクトの生成 ボタンをクリックして新規アプリケーションをダウンロードします。ブラウザーのダウンロードが終わったら、ダウンロードしたファイルのコンテンツを抽出し、そのプロジェクトを優先 IDE にインポートします。

IDE を通してご覧のように、現時点ではプロジェクトには SpringBootOauthApplication と呼ばれるひとつのクラスのみが含まれています。build.gradle ファイルを開くと、このプロジェクトは spring-boot-starter-securityspring-boot-starter-webspring-boot-starter-testspring-security-test の4つの依存関係を定義していることが分かります。Java 10 をご利用の場合、Java から XML へのマーシャリング:を処理するライブラリを含むためにこのファイルをアップデートする必要があります。

// ... その他はそのままにします ...

dependencies {
  // ... その他依存関係 ...
  compile('org.glassfish.jaxb:jaxb-runtime:2.3.1')
}

Spring Boot でエンドポイントを定義する

Spring Boot プロジェクトをスキャフォールディングしたら、最初のエンドポイントを作ることに集中します。そのためには、次のエンドポイントを定義する新規クラスを作ります。

  • /api/public:このエンドポイントは公的にアクセス可能で、簡単なテキストメッセージを戻します。
  • /api/private:このエンドポイントは認証されたユーザーのみがアクセス可能です。
  • /api/private-scoped:このエンドポイントは認証され、特定のスコープが与えられたユーザーのみがアクセス可能です。
  • /config:このエンドポイントは公的にアクセス可能で、SPA がユーザーを認証するために使用する構成プロパティの一部を戻します。

ですから、これらエンドポイントを定義するには、com.example.springbootoauth2 パッケージ内に AppController と呼ばれるクラスを作り、次のコードをそれに挿入します。

package com.example.springbootoauth2;

import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class AppController {

  @Value("${security.oauth2.resource.id}")
  private String resourceId;

  @Value("${auth0.domain}")
  private String domain;

  @Value("${auth0.clientId}")
  private String clientId;

  @RequestMapping(value = "/api/public", method = RequestMethod.GET, produces = "application/json")
  @ResponseBody
  public String publicEndpoint() {
    return new JSONObject()
      .put("message", "Hello from a public endpoint! You don\'t need to be authenticated to see this.")
      .toString();
  }

  @RequestMapping(value = "/api/private", method = RequestMethod.GET, produces = "application/json")
  @ResponseBody
  public String privateEndpoint() {
    return new JSONObject()
      .put("message", "Hello from a private endpoint! You need to be authenticated to see this.")
      .toString();
  }

  @RequestMapping(value = "/api/private-scoped", method = RequestMethod.GET, produces = "application/json")
  @ResponseBody
  public String privateScopedEndpoint() {
    return new JSONObject()
      .put("message", "Hello from a private endpoint! You need to be authenticated and have a scope of read:messages to see this.")
      .toString();
  }

  @RequestMapping(value = "/config", method = RequestMethod.GET, produces = "application/json")
  @ResponseBody
  public String getAppConfigs() {
    return new JSONObject()
      .put("domain", domain)
      .put("clientID", clientId)
      .put("audience", resourceId)
      .toString();
  }
}

このコードを挿入したら、IDE はおそらく org.json.JSONObject クラスがどこにあるのか分からないので、叫び始めるでしょう。この問題を解決するには、次の依存関係を build.gradle ファイルに追加します。

// ... その他はそのままにします ...

dependencies {
  // ... その他依存関係 ...
  compile('org.json:json:20180813')
}

これが終てから、次にしなければならないことは AppController クラスが使用する環境変数を定義します。つまり、このクラスをよく見ると、@Value の注釈が付いた3つの String フィールドがあることが分かります。

  • resourceId:これは Auth0 API 識別子(例:http://localhost:8080/api)を参照します。
  • domain:これは Auth0 ドメイン(例:blog-samples.auth0.com)を参照します。
  • clientId:これはこれからまだ作らなければならない Auth0 アプリケーションの クライアント** ID を参照します。

これら環境変数を定義するには、application.properties ファイルを開き、次のコンテンツをそれに追加します。

auth0.domain=<DOMAIN>
auth0.clientId=<CLIENT-ID>

security.oauth2.resource.id=<AUTH0-API-IDENTIFIER>

必ず、<DOMAIN><AUTH0-API-IDENTIFIER> を独自の Auth0 値と置き換えてください。現時点では <CLIENT-ID> プレースホルダーは後で置き換えますので、気にしないでください。

OAuth 2.0 で Spring Boot API をセキュアに保護する

これでエンドポイントの定義が終わりましたので、次に OAuth 2.0 を使って API をセキュアに保護する必要があります。そのためには、Spring が提供する別のライブラリをインポートしていきます。これによって、すべての構成が促進されます。では、gradle.build フィルを開き、次の依存関係をそれに追加します。

// ... その他はそのままにします ...

dependencies {
  // ... その他依存関係 ...
  compile('org.springframework.security.oauth.boot:spring-security-oauth2-autoconfigure:2.0.6.RELEASE')
}

それから、API をセキュアにするために、com.example.springbootoauth2 パッケージ内に SecurityConfig と呼ばれる新しいクラスを作り、次のコードをそれに追加します。

package com.example.springbootoauth2;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;

@Configuration
@EnableResourceServer
public class SecurityConfig extends ResourceServerConfigurerAdapter {
  @Value("${security.oauth2.resource.id}")
  private String resourceId;

  @Override
  public void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
      .mvcMatchers("/api/public").permitAll()
      .antMatchers("/api/private-scoped").access("#oauth2.hasScope('read:messages')")
      .mvcMatchers("/api/**").authenticated()
      .anyRequest().permitAll();
  }

  @Override
  public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
    resources.resourceId(resourceId);
  }
}

ご覧のように、このクラスには @EnableResourceServer の注釈が付いています。この注釈は着信する OAuth 2.0 トークンを経由してリクエストを認証する Spring Security フィルターを自動的に可能にするので OAuth 2.0 でセキュアに保護されたリソース サーバーに便利な機能です。この注釈のほかに、このクラスは次のメソッドを含みます。

  • configure(ResourceServerSecurityConfigurer resources)、API の識別子を設定するために使用する(例:http://localhost:8080/api
  • configure(HttpSecurity http)、どの API エンドポイントをセキュアにするかを指定するために使用する(この場合 mvcMatchers``("/api/**").authenticated())、セキュアに保護され特定スコープを必要とする(例:antMatchers("/api/private-scoped").access("#oauth2.hasScope('read:messages')"))、エンドポイントがパブリックである(例:mvcMatchers``("/api/public").permitAll())。

ここでの唯一の問題は SecurityConfig クラスはアクセストークンを検証する方法を知りません。つまり、ユーザーがセキュリティで保護されたエンドポイントにリクエストを送るといつでも、秘密キーで署名されたアクセストークンを含みます。このクラスがこれらアクセストークンを信頼できるかどうかを知るために、Spring はトークンを署名するために使用したこの秘密キーとペアになる公開キーが必要になります。この公開キーを取得するには、バックエンド API は JWKS __エンドポイントとして知られるエンドポイントにリクエストを発行する必要があります。これがどのように機能するかという詳細を理解する必要はありませんが、詳細を知りたい方は こちら公式仕様をご覧ください。

Spring Boot API がアクセストークンを検証するために使用する公開キーのコピーを得るには、application.propertiesファイルを開き、次のプロパティをそれに加えます。

# ... その他のプロパティをそのままにします ...
security.oauth2.resource.jwk.keySetUri=https://<DOMAIN>/.well-known/jwks.json

注: <DOMAIN> と独自の Auth0 ドメインを置き換えなければなりません。auth0.domain 環境変数を構成するときに行ったように実行します。

バックエンド API 観点から、エンドポイントを定義し、OAuth 2.0 でこれらをセキュアに保護するために必要なのはこれだけです。次の章では、この API を使用するクライアントアプリ(SPA) の作成と定義を中心に学んでいきます。基本的にこの SPA は次の3つの点を担当します。

  1. ユーザーの認証を可能にする
  2. アクセストークンを認証サーバー(Auth0)から取得する
  3. リクエストを新規 Spring Boot API に発行するこのアクセストークンをユーザーが使用できるようにする

「Spring Boot には OAuth 2.0 で API の保護を促進するユーティリティクラスが搭載されています。」

OAuth 2.0 Implicit Grant で SPA を作り、セキュアに保護する

本章では、Spring Boot API で対話する jQuery SPA を作っていきます。始める前に、SPA を表す Auth0 アプリケーションを作る必要があります。そのためには、Auth0 ダッシュボードのアプリケーション ページ に移動し、アプリケーションの作成 をクリックします。それをクリックすると、Auth0 はアプリの 名前 ("Spring Boot Client SPA" などと名付けます)を入力し、アプリケーションの種類を定義する場所のダイアログを表示します。これから SPA と同様のアプリを構築するので、シングルページ Web アプリケーション の種類を選択します。

Creating an Auth0 Application in Dashboard

Now, clicking on the Create button will make Auth0 redirect you to the Quick Start section of your new app. From there, click on the Settings tab and add http://localhost:8080 to the Allowed Callback URLs field. As a security measure, Auth0 will only redirect users (after the authentication process) back to the URLs listed on this field (i.e., whenever you move into production, make sure you have a configuration that only lists your real internet domain). Now, click on Save Changes.

ここで作成 ボタンをクリックすると、Auth0 は新規アプリのクイック スタート セクションにリダイレクトします。そこから設定 タブをクリックし、http://localhost:8080許可されたコールバック** URL フィールドに追加します。安全性のため、Auth0 は(認証プロセスの後)このフィールドに記載されている URL だけにユーザーをリダイレクトします(生産に移動したら、必ず本当のインターネットドメインだけに記載されている構成があることを確認してください)。ここで変更の保存 をクリックします。

それを保存したら、ドメイン フィールドに表示されている値をコピーし、application.properties フィールドの auth0.domain= の後にそれを挿入します(これをまだしていない場合)。それからクライアント** ID フィールドに表示の値をコピーし、それを同じフィールド内の <CLIENT-ID> と置き換えます。最後に、ファイルは次のようになります。

auth0.domain=blog-samples.auth0.com
auth0.clientId=4Oi9...Vlab8uB

security.oauth2.resource.id=http://localhost:8080/api
security.oauth2.resource.jwk.keySetUri=https://blog-samples.auth0.com/.well-known/jwks.json

これが終わったら、SPA を作成する準備ができました。初心者の方は、/src/main/resources/static の下に assets ディレクトリを作ります。ここに、クライアントアプリケーションに関係するすべてのファイルを保存します。

では、assets ディレクトリ内にstyle.css と呼ばれる新規ファイルを作成し、次の CSS ルールを記入します。

.btn-margin {
  margin-top: 7px
}

#profile-view,
#ping-view {
  display: none;
}

.profile-area img {
  max-width: 150px;
  margin-bottom: 20px;
}

.panel-body h3 {
  margin-top: 0;
}

これはカスケード スタイル シート (CSS) ファイルでクライアントアプリを美しくします。ここで SPA のページが必要ですから、static ディレクトリ内にindex.html ページを作成し、次のコンテンツを記入します。

<!doctype html>
<html>
<head>
  <meta charset="utf-8">
  <title>Calling an API</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
  <link rel="stylesheet" href="assets/style.css">
</head>
<body>

<div class="content">
  <nav class="navbar navbar-default">
    <div class="container-fluid">
      <div class="navbar-header">
        <a class="navbar-brand" href="#">Auth0 - jQuery</a>

        <button id="btn-home-view" class="btn btn-primary btn-margin">
          Home
        </button>

        <button id="btn-profile-view" class="btn btn-primary btn-margin">
          Profile
        </button>

        <button id="btn-ping-view" class="btn btn-primary btn-margin">
          Ping
        </button>

        <button id="btn-login" class="btn btn-primary btn-margin">
          Log In
        </button>

        <button id="btn-logout" class="btn btn-primary btn-margin">
          Log Out
        </button>

      </div>
    </div>
  </nav>

  <main class="container">
    <!-- ホーム ビュー -->
    <div id="home-view">
      <h4></h4>
    </div>

    <!-- プロファイル ビュー -->
    <div id="profile-view" class="panel panel-default profile-area">
      <div class="panel-heading"><h3>Profile</h3></div>
      <div class="panel-body">
        <img class="avatar" alt="avatar">
        <div>
          <label><i class="glyphicon glyphicon-user"></i> Nickname</label>
          <h3 class="nickname"></h3>
        </div>
        <pre class="full-profile"></pre>
      </div>
    </div>

    <!-- ping ビュー -->
    <div id="ping-view">
      <h1>Make a Call to the Server</h1>

      <p id="call-private-message">
        Log in to call a private (secured) server endpoint.
      </p>

      <button id="btn-ping-public" class="btn btn-primary">
        Call Public
      </button>

      <button id="btn-ping-private" class="btn btn-primary">
        Call Private
      </button>

      <button id="btn-ping-private-scoped" class="btn btn-primary">
        Call Private Scoped
      </button>

      <h2 id="ping-message"></h2>
    </div>
  </main>
</div>

<script src="https://cdn.auth0.com/js/auth0/9.5.1/auth0.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="assets/app.js"></script>
</body>
</html>

ここで注目すべき点は、このファイルには Bootstrap CSS__ライブラリ とカスタムスタイルシート (style.css``)head 要素に含まれています。また、body タグを閉じる前に、このファイルには Bootstrap JavaScript ライブラリ、Auth0 JavaScript ライブラリ、[jQuery])(https://jquery.com/)、app.js と呼ばれるローカル JavaScript ファイルが含まれています。app.js がなくてもすぐに作ることができますから心配しないでください。

HTML 定義をよくご覧になられると、ページには8つのボタンがあります。

  • ホーム (btn-home-view):このボタンは既定のビューを表示する
  • プロファイル (btn-profile-view):このボタンはログインユーザーのプロファイルでビューを表示する
  • Ping (btn-ping-view``):このボタンはユーザーが別の種類のリクエストを発行できるビューを表示する
  • ログイン (btn-login):このボタンは認証プロセスを開始する
  • ログアウト (btn-logout):このボタンは現在のユーザーをログアウトする
  • コール パブリック (btn-ping-public):このボタンはパブリック エンドポイント (/api/public) を呼び出し、返されたメッセージを表示する
  • コール プライベート (btn-ping-private):このボタンはプライベート エンドポイントのひとつ(この場合 /api/private )を呼び出し、返されたメッセージを表示する
  • コール プライベート スコープ (btn-ping-private-scoped):このボタンはその他のプライベートエンドポイント (/api/private-scoped) を呼び出し、返されたメッセージを表示する

この HTML ファイルで定義された要素の残りがあるので、ユーザーのプロファイルやサーバーが返したメッセージを表示できます。

ここで、最後にしなければならないことは assets ディレクトリ内に app.js ファイルを作成することです。それを作成したら、次のコードをこのファイルに追加します。

$('document').ready(function() {
  const apiUrl = 'http://localhost:8080/api';

  // 環境変数を読み込みます
  const envVar = $.parseJSON($.ajax({
    url:  '/config',
    dataType: 'json',
    async: false
  }).responseText);

  // Auth0 クライアントを作成します
  const webAuth = new auth0.WebAuth({
    domain: envVar.domain,
    clientID: envVar.clientID,
    redirectUri: location.href,
    audience: envVar.audience,
    responseType: 'token id_token',
    scope: 'openid profile read:messages',
    leeway: 60
  });

  // セクションおよびボタン
  const homeView = $('#home-view');
  const profileView = $('#profile-view');
  const pingView = $('#ping-view');
  const callPrivateMessage = $('#call-private-message');
  const pingMessage = $('#ping-message');

  // ボタン
  const loginBtn = $('#btn-login');
  const logoutBtn = $('#btn-logout');
  const homeViewBtn = $('#btn-home-view');
  const profileViewBtn = $('#btn-profile-view');
  const pingViewBtn = $('#btn-ping-view');
  const pingPublic = $('#btn-ping-public');
  const pingPrivate = $('#btn-ping-private');
  const pingPrivateScoped = $('#btn-ping-private-scoped');

  // リスナー
  pingPublic.click(() => callAPI('/public', false));
  pingPrivate.click(() => callAPI('/private', true));
  pingPrivateScoped.click(() => callAPI('/private-scoped', true));
  loginBtn.click(() => webAuth.authorize());
  logoutBtn.click(logout);
  homeViewBtn.click(displayHome);
  profileViewBtn.click(displayProfile);
  pingViewBtn.click(displayPingView);

  let accessToken = null;
  let userProfile = null;

  handleAuthentication();
  displayButtons();

  // 関数の定義
  function logout() {
    // トークンと有効期限をブラウザーから削除します
    accessToken = null;
    pingMessage.css('display', 'none');
    displayButtons();
  }

  function isAuthenticated() {
    return accessToken != null;
  }

  function handleAuthentication() {
    webAuth.parseHash(function(err, authResult) {
      if (authResult && authResult.accessToken) {
        window.location.hash = '';
        accessToken = authResult.accessToken;
        userProfile = authResult.idTokenPayload;
        loginBtn.css('display', 'none');
        homeView.css('display', 'inline-block');
      } else if (err) {
        homeView.css('display', 'inline-block');
        console.log(err);
        alert(
          'Error: ' + err.error + '. Check the console for further details.'
        );
      }
      displayButtons();
    });
  }

  function callAPI(endpoint, secured) {
    const url = apiUrl + endpoint;

    let headers;
    if (secured && accessToken) {
      headers = { Authorization: 'Bearer ' + accessToken };
    }

    $.ajax({
      url: url,
      headers: headers
    }).done(({message}) => $('#ping-view h2').text(message))
  .fail(({statusText}) => $('#ping-view h2').text('Request failed: ' + statusText));
  }

  function displayButtons() {
    const loginStatus = $('.container h4');
    if (isAuthenticated()) {
      loginBtn.css('display', 'none');
      logoutBtn.css('display', 'inline-block');
      profileViewBtn.css('display', 'inline-block');
      pingPrivate.css('display', 'inline-block');
      pingPrivateScoped.css('display', 'inline-block');
      callPrivateMessage.css('display', 'none');
      loginStatus.text(
        'You are logged in! You can now send authenticated requests to your server.'
      );
    } else {
      homeView.css('display', 'inline-block');
      loginBtn.css('display', 'inline-block');
      logoutBtn.css('display', 'none');
      profileViewBtn.css('display', 'none');
      profileView.css('display', 'none');
      pingView.css('display', 'none');
      pingPrivate.css('display', 'none');
      pingPrivateScoped.css('display', 'none');
      callPrivateMessage.css('display', 'block');
      loginStatus.text('You are not logged in! Please log in to continue.');
    }
  }

  function displayHome() {
    homeView.css('display', 'inline-block');
    profileView.css('display', 'none');
    pingView.css('display', 'none');
  }

  function displayProfile() {
    // 要素を表示します
    homeView.css('display', 'none');
    pingView.css('display', 'none');
    profileView.css('display', 'inline-block');

    // プロファイル データを表示します
    $('#profile-view .nickname').text(userProfile.nickname);
    $('#profile-view .full-profile').text(JSON.stringify(userProfile, null, 2));
    $('#profile-view img').attr('src', userProfile.picture);
  }

  function displayPingView() {
    homeView.css('display', 'none');
    profileView.css('display', 'none');
    pingView.css('display', 'inline-block');
  }
});

ご覧のように、このファイルはかなり大きいです(JavaScript のコード行約 150)が、その内容を理解するのは簡単です。まず、スクリプトはドキュメントが用意されるまで待ってから($('document').ready(...))次に移ります。ドキュメントが用意されたら、スクリプトは次をしてクライアントアプリを用意します。

  1. 環境変数を読み取るために http://localhost:8080/api/config への AJAX リクエストを発行する
  2. これら変数で Auth0 クライアント (var webAuth = new auth0.WebAuth(...)) を構成する
  3. HTML ファイルで定義されたボタンを定義してイベント リスナーに添付する

このスクリプトのもう一つの重要なことは、Auth0 を通して認証した後、ユーザーの詳細をフェッチする担当の関数を定義することです。ご覧いただくと、このスクリプト内に handleAuthentication と呼ばれる関数があります。この関数は Auth0 によって返される2つのものをフェッチするために webAuth.parseHash を呼び出します。

  • accessToken:Spring Boot API のセキュリティエンドポイントにリクエストを発行しているときに、スクリプトはこのトークンを使う
  • idTokenPayload:クライアントアプリはこの情報を使ってログインユーザーについての情報を表示する

これだけです!Spring Boot API を実行してこのシンプルな SPA を通してそれを使用します。そこで、端末を開き、次のコマンドを実行します。

# プロジェクト ルート に移動します
cd spring-boot-oauth2

# Gradle を使ってプロジェクトを始めます
gradle bootRun

注: IDE 内に正しくプロジェクトをインポートしたら、おそらく IDE のインターフェイスを通して実行できます。

アプリを実行した後に、http://localhost:8080 に移動したら、ログインできるスクリーンが表示されます。

Locally running the SPA that consumes the Spring Boot API

ログインの後、SPA にリダイレクトされ、同様のスクリーンが表示されますが、ボタンが数個あります。Ping ボタンをクリックすると、3つのオプションがあるセクションが表示されます。最初のオプションはパブリック エンドポイントにリクエストを発行するボタンです。2つめのオプションはセキュリティ保護され、スコープが必要でないエンドポイントにリクエストを発行するボタンです。最後のオプションはセキュリティ保護され、スコープが必要なエンドポイントにリクエストを発行するボタンです(この場合、read:messages)。これらボタンをクリックすると、Spring Boot API にリクエストを発行し、スクリーン上に結果を出力します。

Locally running the SPA - Secure call with OAuth 2.0 and consuming a Spring Boot API

「OAuth 2.0 で Spring Boot API および SPA をセキュアにする方法を学びました。」

まとめ

本書では、フレームワークで提供された公式 OAuth 2.0 ライブラリを使って、OAuth 2.0 で Spring Boot API を作りセキュアに保護する方法を学んできました。SPA を作り認証する方法をざっと見て、Spring Boot API を使用できるようにしました。OAuth 2.0 とその Spring Boot サポートについて学びたい方は次のリンクをご覧ください。

添付のレポジトリや本書を通して作成したコードについての詳細はこの GitHub レポジトリをご覧ください。ご意見やご質問がある方は、以下のコメント欄にご自由にご記入ください。皆様のお役に立てれば幸いです。