OAuth2とJWTを使用してZuulでセキュリティを処理する

1. 前書き

簡単に言えば、マイクロサービスアーキテクチャにより、システムとAPIを独立したサービスのセットに分割し、完全に独立して展開できます。
これは継続的な展開と管理の観点からすれば素晴らしいことですが、APIのユーザビリティに関してはすぐに複雑になります。 さまざまなエンドポイントを管理するには、依存するアプリケーションでCORS(クロスオリジンリソースシェアリング)とエンドポイントの多様なセットを管理する必要があります。
Zuulは、着信HTTP要求を複数のバックエンドマイクロサービスにルーティングできるようにするエッジサービスです。 一つには、これはバックエンドリソースの消費者に統一されたAPIを提供するために重要です。
基本的に、Zuulでは、サービスの前に座ってプロキシとして機能することにより、すべてのサービスを統合できます。 すべてのリクエストを受信し、それらを正しいサービスにルーティングします。 外部アプリケーションにとって、APIは統一されたAPIの表面領域として表示されます。
このチュートリアルでは、link:/spring-security-oauth-jwt[OAuth 2.0 and JWTs]と組み合わせて、この正確な目的のためにどのように使用できるかについて説明します。 Webサービスを保護するための最前線。 具体的には、https://oauth.net/2/grant-types/password/[Password Grant]フローを使用して、保護されたリソースへのアクセストークンを取得します。
簡単ですが重要な注意点は、単純なシナリオを調べるためにパスワード付与フローのみを使用していることです。ほとんどのクライアントは、実稼働シナリオでAuthorization Grantフローを使用する可能性が高くなります。

2. Zuul Maven依存関係の追加

Zuulをhttps://github.com/eugenp/spring-security-oauth[our project]に追加することから始めましょう。 https://search.maven.org/search?q=g:org.springframework.cloud%20AND%20a:spring-cloud-starter-netflix-zuul[_spring-cloud-starter-netflix- zuul_]アーティファクト:
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-netflix-zuul</artifactId>
    <version>2.0.2.RELEASE</version>
</dependency>

3. Zuulを有効にする

Zuulを経由してルーティングするアプリケーションには、アクセストークンを許可するOAuth 2.0承認サーバーと、それらを受け入れるリソースサーバーが含まれています。 これらのサービスは、2つの別々のエンドポイントに存在します。
これらのサービスのすべての外部クライアントに単一のエンドポイントを持ち、異なる物理エンドポイントに分岐する異なるパスが必要です。 そのために、Zuulをエッジサービスとして導入します。
これを行うには、_GatewayApplication_という新しいSpring Bootアプリケーションを作成します。 次に、このアプリケーションクラスを_ @ EnableZuulProxy_アノテーションで装飾します。これにより、Zuulインスタンスが生成されます。
@EnableZuulProxy
@SpringBootApplication
public class GatewayApplication {
    public static void main(String[] args) {
    SpringApplication.run(GatewayApplication.class, args);
    }
}

4. Zuulルートの構成

さらに先に進む前に、いくつかのZuulプロパティを構成する必要があります。 最初に設定するのは、Zuulが着信接続をリッスンするポートです。 それは_ / src / main / resources / application.yml_ファイルに入れる必要があります:
server:
    port: 8080
楽しいことのために、Zuulが転送する実際のルートを構成します。 そのためには、次のサービス、そのパス、およびそれらがリッスンするポートに注意する必要があります。
**承認サーバーは次の場所にデプロイされます:** http:// localhost:8081 / spring-security-oauth-server / oauth
**リソースサーバーは次の場所にデプロイされます:** http:// localhost:8082 / spring-security-oauth-resource
承認サーバーはOAuth IDプロバイダーです。 リソースサーバーに認証トークンを提供し、保護されたエンドポイントを提供するために存在します。
認可サーバーはクライアントにアクセストークンを提供し、クライアントはリソースオーナーに代わって、トークンを使用してリソースサーバーに対してリクエストを実行します。 https://oauth2.thephpleague.com/terminology/[OAuth terminology]をざっと目を通すと、これらの概念を把握するのに役立ちます。
次に、これらの各サービスにいくつかのルートをマッピングしましょう。
zuul:
  routes:
    spring-security-oauth-resource:
      path: /spring-security-oauth-resource/**
      url: http://localhost:8082/spring-security-oauth-resource
    oauth:
      path: /oauth/**
      url: http://localhost:8081/spring-security-oauth-server/oauth
この時点で、_localhost:8080 / oauth / ** _のZuulに到達する要求は、ポート8081で実行されている承認サービスにルーティングされます。 _localhost:8080 / spring-security-oauth-resource / ** _へのリクエストは、8082で実行されているリソースサーバーにルーティングされます。

5. Zuul外部トラフィックパスの保護

Zuulエッジサービスは現在、リクエストを正しくルーティングしていますが、認証チェックなしでルーティングしています。 _ / oauth / * _の後ろにある認証サーバーは、認証が成功するたびにJWTを作成します。 当然、匿名でアクセスできます。
一方、_ / spring-security-oauth-resource / ** _にあるリソースサーバーには、JWTを使用して常にアクセスし、許可されたクライアントが保護されたリソースにアクセスしていることを確認する必要があります。
最初に、JWTを通過してその背後にあるサービスに渡すようにZuulを構成します。 ここでは、これらのサービス自体がトークンを検証する必要があります。
これを行うには、機密性の高いヘッダーを追加します:Cookie、Set-Cookie。
これで、Zuulの構成が完了しました。
server:
  port: 8080
zuul:
  sensitiveHeaders: Cookie,Set-Cookie
  routes:
    spring-security-oauth-resource:
      path: /spring-security-oauth-resource/**
      url: http://localhost:8082/spring-security-oauth-resource
    oauth:
      path: /oauth/**
      url: http://localhost:8081/spring-security-oauth-server/oauth
邪魔にならないようにした後、承認をエッジで処理する必要があります。 現在、ZuulはJWTを検証してからダウンストリームサービスに渡すことはありません。 これらのサービスはJWT自体を検証しますが、理想的には、エッジサービスにそれを最初に実行させ、不正なリクエストを拒否してから、アーキテクチャに深く伝播させたいと考えています。
  • Spring Securityをセットアップして、Zuulで認証がチェックされるようにします。*

    まず、Spring Securityの依存関係をプロジェクトに取り込む必要があります。 https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2[spring-security-oauth2]およびhttps://mvnrepository.com/artifact/org.springframework.security/springが必要です。 -security-jwt [spring-security-jwt:]
<dependency>
    <groupId>org.springframework.security.oauth</groupId>
    <artifactId>spring-security-oauth2</artifactId>
    <version>2.3.3.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-jwt</artifactId>
    <version>1.0.9.RELEASE</version>
</dependency>
_ResourceServerConfigurerAdapter:_を拡張して、保護するルートの構成を作成しましょう。
@Configuration
@Configuration
@EnableResourceServer
public class GatewayConfiguration extends ResourceServerConfigurerAdapter {
    @Override
    public void configure(final HttpSecurity http) throws Exception {
    http.authorizeRequests()
          .antMatchers("/oauth/**")
          .permitAll()
          .antMatchers("/**")
      .authenticated();
    }
}
_GatewayConfiguration_クラスは、Spring SecurityがZuulを介して着信HTTP要求を処理する方法を定義します。 _configure_メソッド内では、最初に_antMatchers_を使用して最も制限の厳しいパスを照合し、次に_permitAll_を介して匿名アクセスを許可しました。
つまり、_ / oauth / ** _に着信するすべての要求は、認証トークンを確認せずに通過を許可する必要があります。 これは、許可トークンが生成されるパスであるため、理にかなっています。
次に、他のすべてのパスを/ ** _と照合し、_authenticated_の呼び出しを通じて、他のすべての呼び出しにアクセストークンを含めるように主張しました。

6. JWT検証に使用されるキーの構成

これで設定が完了したので、_ / oauth / ** _パスにルーティングされたすべての要求は匿名で許可され、他のすべての要求には認証が必要になります。
ただし、ここで不足していることが1つあります。これは、JWTが有効であることを確認するために必要な実際の秘密です。 そのためには、JWTの署名に使用するキー(この場合は対称)を提供する必要があります。 構成コードを手動で記述するのではなく、https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure [_spring-security-oauth2-autoconfigure_]を使用できます。
プロジェクトにアーティファクトを追加することから始めましょう。
<dependency>
    <groupId>org.springframework.security.oauth.boot</groupId>
    <artifactId>spring-security-oauth2-autoconfigure</artifactId>
    <version>2.1.2.RELEASE</version>
</dependency>
次に、application.ymlファイルに数行の設定を追加して、JWTの署名に使用するキーを定義する必要があります。
security:
  oauth2:
    resource:
      jwt:
        key-value: 123
行_key-value:123_は、JWTに署名するために承認サーバーが使用する対称キーを設定します。 このキーは、トークン解析を構成するために_spring-security-oauth2-autoconfigure_によって使用されます。
実稼働システムでは、**アプリケーションのソースコードで指定されている対称キーを使用しないでください。 **当然、外部で設定する必要があります。

7. エッジサービスのテスト

7.1. アクセストークンの取得

次に、いくつかのcurlコマンドを使用して、Zuulエッジサービスの動作をテストします。
最初に、https://oauth.net/2/grant-types/password/[password grant]を使用して、承認サーバーから新しいJWTを取得する方法を確認します。
ここでは、*ユーザー名とパスワードをアクセストークン*と交換します。 この場合、ユーザー名として「_john_」、パスワードとして「_123_」を使用します。
curl -X POST \
  http://localhost:8080/oauth/token \
  -H 'Authorization: Basic Zm9vQ2xpZW50SWRQYXNzd29yZDpzZWNyZXQ=' \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=password&password=123&username=john'
この呼び出しによりJWTトークンが生成され、これをリソースサーバーに対する認証済みリクエストに使用できます。
_「Authorization:Basic…」_ヘッダーフィールドに注意してください。 これは、どのクライアントがそれに接続しているのかを承認サーバーに伝えるために存在します。
ユーザーに対するユーザー名とパスワードは、クライアント(この場合はcURLリクエスト)に対するものです。
{
    "access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX...",
    "token_type":"bearer",
    "refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpX...",
    "expires_in":3599,
    "scope":"foo read write",
    "organization":"johnwKfc",
    "jti":"8e2c56d3-3e2e-4140-b120-832783b7374b"
}

7.2. リソースサーバーリクエストのテスト

次に、承認サーバーから取得したJWTを使用して、リソースサーバーに対してクエリを実行できます。
curl -X GET \
curl -X GET \
  http:/localhost:8080/spring-security-oauth-resource/users/extra \
  -H 'Accept: application/json, text/plain, */*' \
  -H 'Accept-Encoding: gzip, deflate' \
  -H 'Accept-Language: en-US,en;q=0.9' \
  -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXV...' \
  -H 'Cache-Control: no-cache' \
Zuulエッジサービスは、リソースサーバーにルーティングする前にJWTを検証します。
次に、JWTからキーフィールドを抽出し、リクエストに応答する前に、より詳細な承認をチェックします。
{
    "user_name":"john",
    "scope":["foo","read","write"],
    "organization":"johnwKfc",
    "exp":1544584758,
    "authorities":["ROLE_USER"],
    "jti":"8e2c56d3-3e2e-4140-b120-832783b7374b",
    "client_id":"fooClientIdPassword"
}

8. レイヤー全体のセキュリティ

JWTはリソースサーバーに渡される前にZuulエッジサービスによって検証されていることに注意することが重要です。 JWTが無効な場合、要求はエッジサービスの境界で拒否されます。
一方、JWTが実際に有効な場合、要求はダウンストリームに渡されます。 次に、リソースサーバーはJWTを再度検証し、ユーザースコープ、組織(この場合はカスタムフィールド)、権限などの重要なフィールドを抽出します。 これらのフィールドを使用して、ユーザーができることとできないことを決定します。
明確にするために、多くのアーキテクチャでは、実際にJWTを2回検証する必要はありません。これは、トラフィックパターンに基づいて行う必要がある決定です。
たとえば、一部の本番プロジェクトでは、個々のリソースサーバーに直接アクセスすることも、プロキシを介してアクセスすることもあります。両方の場所でトークンを確認することもできます。 他のプロジェクトでは、トラフィックがプロキシを介してのみ着信している場合があります。この場合、トークンの検証で十分です。

9. 概要

これまで見てきたように、Zuulはサービスのルートを抽象化および定義するための簡単で構成可能な方法を提供します。 Spring Securityと共に、サービスの境界でリクエストを承認できます。
最後に、いつものように、コードはhttps://github.com/Baeldung/spring-security-oauth[Github上]で入手できます。