1. 序章

OAuth は、承認のプロセスを説明するオープンスタンダードです。 APIへのユーザーアクセスを承認するために使用できます。 たとえば、REST APIは、適切な役割を持つ登録済みユーザーにのみアクセスを制限できます。

OAuth承認サーバーは、ユーザーを認証し、ユーザーデータと適切なアクセスポリシーを含むアクセストークンを発行する責任があります。

このチュートリアルでは、 Spring Security OAuth AuthorizationServerプロジェクトを使用して簡単なOAuthアプリケーションを実装します。

その過程で、RESTAPIからBaeldung記事のリストをフェッチするクライアントサーバーアプリケーションを作成します。 クライアントサービスとサーバーサービスの両方で、OAuth認証が必要になります。

2. 承認サーバーの実装

まず、OAuth認証サーバーの構成を見てみましょう。 これは、記事リソースとクライアントサーバーの両方の認証ソースとして機能します。

2.1. 依存関係

まず、pom.xmlファイルにいくつかの依存関係を追加する必要があります。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.5.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.5.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.security</groupId>
    <artifactId>spring-security-oauth2-authorization-server</artifactId>
    <version>0.2.0</version>
</dependency>

2.2. 構成

次に、application.ymlファイルのserver.portプロパティを設定して、認証サーバーが実行されるポートを構成しましょう。

server:
  port: 9000

その後、SpringBean構成に移動できます。 まず、 @Configuration クラスが必要です。このクラスで、いくつかのOAuth固有のBeanを作成します。 最初のものは、クライアントサービスのリポジトリになります。 この例では、RegisteredClientビルダークラスを使用して作成された単一のクライアントがあります。

@Configuration
@Import(OAuth2AuthorizationServerConfiguration.class)
public class AuthorizationServerConfig {
    @Bean
    public RegisteredClientRepository registeredClientRepository() {
        RegisteredClient registeredClient = RegisteredClient.withId(UUID.randomUUID().toString())
          .clientId("articles-client")
          .clientSecret("{noop}secret")
          .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC)
          .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
          .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
          .redirectUri("http://127.0.0.1:8080/login/oauth2/code/articles-client-oidc")
          .redirectUri("http://127.0.0.1:8080/authorized")
          .scope(OidcScopes.OPENID)
          .scope("articles.read")
          .build();
        return new InMemoryRegisteredClientRepository(registeredClient);
    }
}

構成しているプロパティは次のとおりです。

  • クライアントID– Springはこれを使用して、リソースにアクセスしようとしているクライアントを識別します
  • クライアントシークレットコード–2つの間の信頼を提供するクライアントとサーバーに知られているシークレット
  • 認証方法–この場合、ユーザー名とパスワードだけの基本認証を使用します
  • 認証付与タイプ–クライアントが認証コードと更新トークンの両方を生成できるようにします
  • リダイレクトURI–クライアントはリダイレクトベースのフローでそれを使用します
  • スコープ–このパラメーターは、クライアントが持つ可能性のある権限を定義します。 この例では、必要な OidcScopes.OPENIDとカスタムの記事–記事を提供します。 読んだ

次に、デフォルトのOAuthセキュリティを適用し、デフォルトのフォームログインページを生成するようにBeanを設定しましょう。

@Bean
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityFilterChain authServerSecurityFilterChain(HttpSecurity http) throws Exception {
    OAuth2AuthorizationServerConfiguration.applyDefaultSecurity(http);
    return http.formLogin(Customizer.withDefaults()).build();
}

各認証サーバーには、セキュリティドメイン間の適切な境界を維持するために、トークンの署名キーが必要です。 2048バイトのRSAキーを生成してみましょう。

@Bean
public JWKSource<SecurityContext> jwkSource() {
    RSAKey rsaKey = generateRsa();
    JWKSet jwkSet = new JWKSet(rsaKey);
    return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
}

private static RSAKey generateRsa() {
    KeyPair keyPair = generateRsaKey();
    RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
    RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
    return new RSAKey.Builder(publicKey)
      .privateKey(privateKey)
      .keyID(UUID.randomUUID().toString())
      .build();
}

private static KeyPair generateRsaKey() {
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
    keyPairGenerator.initialize(2048);
    return keyPairGenerator.generateKeyPair();
}

署名キーを除いて、各認証サーバーには一意の発行者URLも必要です。 ProviderSettings Beanを作成して、ポート 9000http://auth-serverのローカルホストエイリアスとして設定します。

@Bean
public ProviderSettings providerSettings() {
    return ProviderSettings.builder()
      .issuer("http://auth-server:9000")
      .build();
}

さらに、 / etc /hostsファイルに「127.0.0.1auth-server」エントリを追加します。 これにより、ローカルマシンでクライアントと認証サーバーを実行でき、2つの間のセッションCookieの上書きに関する問題を回避できます。

最後に、@EnableWebSecurityアノテーション付き構成クラスを使用してSpringWebセキュリティモジュールを有効にします。

@EnableWebSecurity
public class DefaultSecurityConfig {

    @Bean
    SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http.authorizeRequests(authorizeRequests ->
          authorizeRequests.anyRequest().authenticated()
        )
          .formLogin(withDefaults());
        return http.build();
    }

    // ...
}

ここでは、 authorizeRequests.anyRequest()。authenticated()を呼び出して、すべてのリクエストの認証を要求し、 formLogin(defaults())を呼び出すことでフォームベースの認証を提供しています。 メソッド。

さらに、テストに使用する一連のサンプルユーザーを定義します。 この例のために、1人の管理者ユーザーだけでリポジトリを作成しましょう。

@Bean
UserDetailsService users() {
    UserDetails user = User.withDefaultPasswordEncoder()
      .username("admin")
      .password("password")
      .build();
    return new InMemoryUserDetailsManager(user);
}

3. リソースサーバー

次に、GETエンドポイントから記事のリストを返すリソースサーバーを作成します。 エンドポイントは、OAuthサーバーに対して認証されたリクエストのみを許可する必要があります。

3.1. 依存関係

まず、必要な依存関係を含めましょう。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.5.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.5.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
    <version>2.5.4</version>
</dependency>

3.2. 構成

実装コードを開始する前に、application.ymlファイルでいくつかのプロパティを構成する必要があります。 最初のものはサーバーポートです:

server:
  port: 8090

次に、セキュリティ構成の時間です。 以前にProviderSettings Beanで構成したホストとポートを使用して、認証サーバーの適切なURLを設定する必要があります。

spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: http://auth-server:9000

これで、Webセキュリティ構成をセットアップできます。 繰り返しになりますが、記事リソースへのすべてのリクエストは承認され、適切なarticles.read権限を持っている必要があることを明示的に言いたいと思います。

@EnableWebSecurity
public class ResourceServerConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.mvcMatcher("/articles/**")
          .authorizeRequests()
          .mvcMatchers("/articles/**")
          .access("hasAuthority('SCOPE_articles.read')")
          .and()
          .oauth2ResourceServer()
          .jwt();
        return http.build();
    }
}

ここに示すように、 oauth2ResourceServer()メソッドも呼び出しています。このメソッドは、application.yml構成に基づいてOAuthサーバー接続を構成します。

3.3. 記事コントローラー

最後に、 GET /articlesエンドポイントの下に記事のリストを返すRESTコントローラーを作成します。

@RestController
public class ArticlesController {

    @GetMapping("/articles")
    public String[] getArticles() {
        return new String[] { "Article 1", "Article 2", "Article 3" };
    }
}

4. APIクライアント

最後の部分では、リソースサーバーから記事のリストをフェッチするRESTAPIクライアントを作成します。

4.1. 依存関係

まず、必要な依存関係を含めましょう。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.5.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
    <version>2.5.4</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
    <version>2.5.4</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webflux</artifactId>
    <version>5.3.9</version>
</dependency>
<dependency>
    <groupId>io.projectreactor.netty</groupId>
    <artifactId>reactor-netty</artifactId>
    <version>1.0.9</version>
</dependency>

4.2. 構成

前に行ったように、認証の目的でいくつかの構成プロパティを定義します。

server:
  port: 8080

spring:
  security:
    oauth2:
      client:
        registration:
          articles-client-oidc:
            provider: spring
            client-id: articles-client
            client-secret: secret
            authorization-grant-type: authorization_code
            redirect-uri: "http://127.0.0.1:8080/login/oauth2/code/{registrationId}"
            scope: openid
            client-name: articles-client-oidc
          articles-client-authorization-code:
            provider: spring
            client-id: articles-client
            client-secret: secret
            authorization-grant-type: authorization_code
            redirect-uri: "http://127.0.0.1:8080/authorized"
            scope: articles.read
            client-name: articles-client-authorization-code
        provider:
          spring:
            issuer-uri: http://auth-server:9000

次に、 WebClient インスタンスを作成して、リソースサーバーへのHTTPリクエストを実行しましょう。 OAuth承認フィルターを1つ追加するだけで、標準の実装を使用します。

@Bean
WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) {
    ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client =
      new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager);
    return WebClient.builder()
      .apply(oauth2Client.oauth2Configuration())
      .build();
}

WebClient には、依存関係としてOAuth2AuthorizedClientManagerが必要です。 デフォルトの実装を作成しましょう:

@Bean
OAuth2AuthorizedClientManager authorizedClientManager(
        ClientRegistrationRepository clientRegistrationRepository,
        OAuth2AuthorizedClientRepository authorizedClientRepository) {

    OAuth2AuthorizedClientProvider authorizedClientProvider =
      OAuth2AuthorizedClientProviderBuilder.builder()
        .authorizationCode()
        .refreshToken()
        .build();
    DefaultOAuth2AuthorizedClientManager authorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
      clientRegistrationRepository, authorizedClientRepository);
    authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider);

    return authorizedClientManager;
}

最後に、Webセキュリティを構成します。

@EnableWebSecurity
public class SecurityConfig {

    @Bean
    SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
          .authorizeRequests(authorizeRequests ->
            authorizeRequests.anyRequest().authenticated()
          )
          .oauth2Login(oauth2Login ->
            oauth2Login.loginPage("/oauth2/authorization/articles-client-oidc"))
          .oauth2Client(withDefaults());
        return http.build();
    }
}

ここでは、他のサーバーと同様に、すべてのリクエストを認証する必要があります。 さらに、ログインページのURL( .yml 構成で定義)とOAuthクライアントを構成する必要があります。

4.3. 記事クライアントコントローラー

最後に、データアクセスコントローラーを作成できます。 以前に構成したWebClientを使用して、リソースサーバーにHTTPリクエストを送信します。

@RestController
public class ArticlesController {

    private WebClient webClient;

    @GetMapping(value = "/articles")
    public String[] getArticles(
      @RegisteredOAuth2AuthorizedClient("articles-client-authorization-code") OAuth2AuthorizedClient authorizedClient
    ) {
        return this.webClient
          .get()
          .uri("http://127.0.0.1:8090/articles")
          .attributes(oauth2AuthorizedClient(authorizedClient))
          .retrieve()
          .bodyToMono(String[].class)
          .block();
    }
}

上記の例では、OAuth2AuthorizedClientクラスの形式でリクエストからOAuth認証トークンを取得しています。 適切なIDを持つ@RegisteredOAuth2AuthorizedClient アノテーションを使用して、Springによって自動的にバインドされます。 この例では、.ymlファイルで以前に構成したarticle-client-authorizaiton-codeから取得されます。

この認証トークンはさらにHTTPリクエストに渡されます。

4.4. 記事リストへのアクセス

これで、ブラウザにアクセスして http://127.0.0.1:8080 / articles ページにアクセスしようとすると、httpの下のOAuthサーバーログインページに自動的にリダイレクトされます。 // auth-server:9000 / login URL:

適切なユーザー名とパスワードを入力すると、認証サーバーは要求されたURL(記事のリスト)にリダイレクトします。

アクセストークンはCookieに保存されるため、Articlesエンドポイントへのそれ以降のリクエストにはログインする必要はありません。

5. 結論

この記事では、Spring Security OAuth Authorization Serverをセットアップ、構成、および使用する方法を学習しました。

いつものように、すべてのソースコードはGitHub利用できます。