Spring Cloudでマイクロサービス

Spring cloud を使用してシンプルなマイクロサービスを構成します。ログインにはOAuth2を使用してGoogleアカウントでログインできるようにします。ソースは https://github.com/jun-1/spring-cloud-microservices

# 全体構成

ブラウザ等のクライアントとの対話やログイン処理はweb-serviceが行いbackend-serviceに各種機能を要求します。この際discovery-serviceにサービスを登録することで各サービスがお互いを見つけられるようにします。

![image](https://qiita-image-store.s3.amazonaws.com/0/46832/e4e6c80d-17d0-599d-6cac-072f576f3348.png)


# discovery-seriviceの実装

`@EnableEurekaServer`アノテーションを付けたspring-bootアプリケーションを作成するだけでサービスレジストリとしてEurekaサーバーが実装できます。
また http://localhost:8761 からステータスを確認できます。

```java
package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer;

@EnableEurekaServer
@SpringBootApplication
public class DeiscoveryServiceApplication {

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

```

ポート番号と自分自身をレジストリに登録しないため設定をします。

```yaml
eureka:
  client:
    register-with-eureka: false
    fetch-registry: false
server:
  port: 8761
```

# web-serviceの実装

はじめにOAuthで使用するクライアントIDとクライアントシークレットを取得します。

## Googleの認証情報作成
https://console.developers.google.com で認証情報を作成します。

![image](https://qiita-image-store.s3.amazonaws.com/0/46832/7ad590f6-a88d-3e29-47b9-2134cd44b5e1.png)

[認証情報]から[プロジェクトを作成]をクリックしプロジェクトを作成します。


![image](https://qiita-image-store.s3.amazonaws.com/0/46832/56673f25-aa5b-0cad-9405-adcc848199bf.png)

OAuthクライアントIDを選択し認証情報を作成します。（OAuth同意画面を作成していない場合は作成しておきます）

![image](https://qiita-image-store.s3.amazonaws.com/0/46832/61dcf2c9-ac95-e47a-ab0d-e97e218563b0.png)

localhostで動かす場合は[その他]を選択して[作成]ボタンをクリックすると、OAuthクライアントのクライアントIDとクライアントシークレットが取得できます。

## アプリケーションの実装

web-serviceの役割はクライアントとの対話とログイン、それからbackend-serviceに対するリバースプロキシです。

`@EnableZuulProxy`をつけることでZuulを使用してCORSと認証の懸念事項を個別に管理することなくリバースプロキシとして動作させることができます。
`@EnableOAuth2Sso`をつけることでOAuth2に基づくシングルサインオンが実現できます。

```java
package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.oauth2.client.EnableOAuth2Sso;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.netflix.zuul.EnableZuulProxy;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableEurekaClient
@EnableZuulProxy
@EnableOAuth2Sso
@SpringBootApplication
public class WebServiceApplication extends WebSecurityConfigurerAdapter{

	public static void main(String[] args) {
		SpringApplication.run(WebServiceApplication.class, args);
	}
	
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http.authorizeRequests()
			.antMatchers("/", "index.html").permitAll()
			.anyRequest().authenticated();
	}
}

```

configure()ではindex.html以外のURLへのアクセスには認証が必要と設定しています。
次はweb-serviceの設定です。

```yaml
spring:
  application:
    name: web-service
eureka:
  client:
    service-url: 
      defaultZone: http://localhost:8761/eureka
zuul:
  ignored-services: '*'
  routes:
    backend-service: 'api/**'
security:
  oauth2:
    resource:
      user-info-uri: https://www.googleapis.com/oauth2/v1/userinfo
    client:
      access-token-uri: https://accounts.google.com/o/oauth2/token
      user-authorization-uri: https://accounts.google.com/o/oauth2/auth
      client-id: ${clientId}
      client-secret: ${clientSecret}
      grant-type: code
      scope: profile
```

`spring.application.name`で設定した名前でEurekaサーバーにサービスが登録されます。
登録先のサーバーは`eureka.client.service.url.defaultZone`で指定します。

`zuul`の設定でapi/**へのリクエストをbackend-serviceに転送するようにしています。
ここでは転送先にEurekaサーバーに登録したサービス名を使用できます。

`security`の設定はOAuth2クライアントの設定です。ここで先ほど取得したクライアントIDとクライアントシークレットを使用します。

# backend-service

backend-serviceはOAuth2のクライアントでありリソースサーバーとして実装しています。

```java
package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableOAuth2Client;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;

@EnableEurekaClient
@EnableOAuth2Client
@EnableResourceServer
@SpringBootApplication
public class BackendServiceApplication {

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

```

以下は'Hello'の文字列を返すだけのコントローラーですが、OAuth2の認証がなければアクセスできないようになります。web-service経由でアクセスした場合はZuul Proxyが認証トークンをリレーしてくれるためweb-serviceでログインしていれば/api/helloで呼び出すことが可能です。

```java
package demo.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BackendController {
	
	@RequestMapping("/hello")
	public String hello(){
		return "Hello";
	}
}
```

また、以下のようにOAuth2RestTemplateを使用すると、他のサービスとRest通信を簡単に行うことができます。

```java
    @Bean
    @LoadBalanced
    public OAuth2RestTemplate oAuth2RestTemplate(OAuth2ProtectedResourceDetails resource, OAuth2ClientContext context){
        return new OAuth2RestTemplate(resource, context);
    }
```