1. 概要

このチュートリアルでは、 SSO –シングルサインオン– SpringセキュリティOAuthとSpring Bootを使用し、Keycloakを認証サーバーとして使用する方法について説明します。

4つの別々のアプリケーションを使用します。

  • 承認サーバー–これは中央の認証メカニズムです
  • リソースサーバー– Fooのプロバイダー
  • 2つのクライアントアプリケーション–SSOを使用するアプリケーション

非常に簡単に言えば、ユーザーが1つのクライアントアプリを介してリソースにアクセスしようとすると、承認サーバーを介して最初に認証するようにリダイレクトされます。 Keycloakはユーザーにサインインし、最初のアプリにログインしたまま、同じブラウザーを使用して2番目のクライアントアプリにアクセスする場合、ユーザーは資格情報を再度入力する必要はありません。

認証の委任を駆動するために、OAuth2から認証コード付与タイプを使用します。

Springセキュリティ5でOAuthスタックを使用します。SpringセキュリティOAuthレガシースタックを使用する場合は、次の前の記事を参照してください:シンプルなシングルサインオンSpringセキュリティOAuth2(レガシースタック)

移行ガイドによると:

SpringSecurityはこの機能をOAuth2.0ログインと呼び、SpringSecurityOAuthはこれをSSOと呼びます。

さて、すぐに飛び込みましょう。

2. 承認サーバー

以前は、Spring Security OAuthスタックは、承認サーバーをSpringアプリケーションとしてセットアップする可能性を提供していました。

ただし、OAuthスタックはSpringによって非推奨になり、認証サーバーとしてKeycloakを使用するようになります。

今回は、Spring Bootアプリに組み込みKeycloakサーバーとして認証サーバーを設定します。

事前構成では、クライアントアプリケーションごとに1つずつ、ssoClient-1と ssoClient-2、の2つのクライアントを定義します。

3. リソースサーバー

次に、リソースサーバー、またはクライアントアプリが消費するFooを提供するRESTAPIが必要です。

これは基本的に、以前のAngularクライアントアプリで使用したものと同じです。

4. クライアントアプリケーション

次に、Thymeleafクライアントアプリケーションを見てみましょう。 もちろん、構成を最小限に抑えるためにSpring Bootを使用します。

シングルサインオン機能を示すには、これらのうち2つが必要になることに注意してください。

4.1. Mavenの依存関係

まず、pom.xmlに次の依存関係が必要になります。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-oauth2-client</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
    <groupId>org.thymeleaf.extras</groupId>
    <artifactId>thymeleaf-extras-springsecurity5</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webflux</artifactId>
</dependency>
<dependency>
    <groupId>io.projectreactor.netty</groupId>
    <artifactId>reactor-netty</artifactId>
</dependency>

セキュリティを含む、必要なすべてのクライアントサポートを含めるには、spring-boot-starter-oauth2-clientを追加するだけです。 また、古い RestTemplate は非推奨になるため、 WebClient を使用します。そのため、 spring-webfluxreactor-netty

4.2. セキュリティ構成

次に、最も重要な部分は、最初のクライアントアプリケーションのセキュリティ構成です。

@EnableWebSecurity
public class UiSecurityConfig extends WebSecurityConfigurerAdapter {
    
    @Override
    public void configure(HttpSecurity http) throws Exception {
        http.antMatcher("/**")
          .authorizeRequests()
          .antMatchers("/")
          .permitAll()
          .anyRequest()
          .authenticated()
          .and()
          .oauth2Login();
    }

    @Bean
    WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, 
      OAuth2AuthorizedClientRepository authorizedClientRepository) {
        ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2 = 
          new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, 
          authorizedClientRepository);
        oauth2.setDefaultOAuth2AuthorizedClient(true);
        return WebClient.builder().apply(oauth2.oauth2Configuration()).build();
    }
}

この構成の中核部分はoauth2Login()メソッドであり、SpringSecurityのOAuth2.0ログインサポートを有効にするために使用されます。デフォルトでシングルサインオンソリューションであるKeycloakを使用しているためWebアプリとRESTfulWebサービスの場合、SSOの構成を追加する必要はありません。

最後に、 WebClient Beanを定義して、リソースサーバーに送信される要求を処理する単純なHTTPクライアントとして機能します。

そして、これがapplication.ymlです。

spring:
  security:
    oauth2:
      client:
        registration:
          custom:
            client-id: ssoClient-1
            client-secret: ssoClientSecret-1
            scope: read,write
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8082/ui-one/login/oauth2/code/custom
        provider:
          custom:
            authorization-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/auth
            token-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/token
            user-info-uri: http://localhost:8083/auth/realms/baeldung/protocol/openid-connect/userinfo
            user-name-attribute: preferred_username
  thymeleaf:
    cache: false
    
server: 
  port: 8082
  servlet: 
    context-path: /ui-one

resourceserver:
  api:
    project:
      url: http://localhost:8081/sso-resource-server/api/foos/        

ここで、 spring.security.oauth2.client.registration は、クライアントを登録するためのルート名前空間です。 登録IDcustomでクライアントを定義しました。 次に、 client-id client-secret scope authentication-grant-type redirect-uriを定義しました。 、もちろん、これは認証サーバー用に定義されたものと同じである必要があります。

その後、同じIDで custom を使用して、サービスプロバイダーまたは承認サーバーを定義し、Springセキュリティで使用するさまざまなURIを一覧表示しました。 定義する必要があるのはこれだけです。フレームワークは、Keycloakへのリダイレクトを含むログインプロセス全体をシームレスに実行します

また、ここでの例では、承認サーバーをロールアウトしましたが、もちろん、FacebookやGitHubなどの他のサードパーティプロバイダーを使用することもできます。

4.3. コントローラー

次に、クライアントアプリにコントローラーを実装して、リソースサーバーからFooを要求します。

@Controller
public class FooClientController {

    @Value("${resourceserver.api.url}")
    private String fooApiUrl;

    @Autowired
    private WebClient webClient;

    @GetMapping("/foos")
    public String getFoos(Model model) {
        List<FooModel> foos = this.webClient.get()
            .uri(fooApiUrl)
            .retrieve()
            .bodyToMono(new ParameterizedTypeReference<List<FooModel>>() {
            })
            .block();
        model.addAttribute("foos", foos);
        return "foos";
    }
}

ご覧のとおり、ここではfoosテンプレートにリソースを提供するメソッドが1つだけあります。 ログイン用のコードを追加する必要はありませんでした。

4.4. フロントエンド

それでは、クライアントアプリケーションのフロントエンド構成を見てみましょう。 主にサイトですでに取り上げているため、ここではこれに焦点を当てません。

ここでのクライアントアプリケーションには、非常にシンプルなフロントエンドがあります。 これがindex.htmlです。

<a class="navbar-brand" th:href="@{/foos/}">Spring OAuth Client Thymeleaf - 1</a>
<label>Welcome !</label> <br /> <a th:href="@{/foos/}">Login</a>

そしてfoos.html

<a class="navbar-brand" th:href="@{/foos/}">Spring OAuth Client Thymeleaf -1</a>
Hi, <span sec:authentication="name">preferred_username</span>   
    
<h1>All Foos:</h1>
<table>
  <thead>
    <tr>
      <td>ID</td>
      <td>Name</td>                    
    </tr>
  </thead>
  <tbody>
    <tr th:if="${foos.empty}">
      <td colspan="4">No foos</td>
    </tr>
    <tr th:each="foo : ${foos}">
      <td><span th:text="${foo.id}"> ID </span></td>
      <td><span th:text="${foo.name}"> Name </span></td>                    
    </tr>
  </tbody>
</table>

foos.html ページでは、ユーザーを認証する必要があります。 認証されていないユーザーがfoos.htmlにアクセスしようとすると、最初にKeycloakのログインページにリダイレクトされます

4.5. 2番目のクライアントアプリケーション

別のclient_id ssoClient-2 を使用して、2番目のアプリケーションSpringOAuthクライアントThymeleaf-2を構成します。

これは、今説明した最初のアプリケーションとほとんど同じです。

application.yml は、spring.security.oauth2に異なるclient_id client_secret 、およびredirect_uriを含むように異なります。 client.registration:

spring:
  security:
    oauth2:
      client:
        registration:
          custom:
            client-id: ssoClient-2
            client-secret: ssoClientSecret-2
            scope: read,write
            authorization-grant-type: authorization_code
            redirect-uri: http://localhost:8084/ui-two/login/oauth2/code/custom

そしてもちろん、それらを並行して実行できるように、別のサーバーポートも必要です。

server: 
  port: 8084
  servlet: 
    context-path: /ui-two

最後に、フロントエンドのHTMLを微調整して、タイトルを –1ではなくSpringOAuth Client Thymeleaf – 2 にして、2つを区別できるようにします。

5. SSOの動作のテスト

SSOの動作をテストするために、アプリケーションを実行してみましょう。

このためには、4つのブートアプリすべて(認証サーバー、リソースサーバー、および両方のクライアントアプリケーション)が稼働している必要があります。

次に、Chromeなどのブラウザを開き、クレデンシャル[email protected]/123を使用してClient-1にログインします。 次に、別のウィンドウまたはタブで、Client-2のURLを押します。 ログインボタンをクリックすると、認証手順をバイパスして、すぐにFoosページにリダイレクトされます。

同様に、ユーザーが最初に Client-2 にログインする場合、Client-1のユーザー名/パスワードを入力する必要はありません。

6. 結論

このチュートリアルでは、SpringSecurityOAuth2を使用したシングルサインオンとKeycloakをIDプロバイダーとして使用したSpringBootの実装に焦点を当てました。

いつものように、完全なソースコードはGitHubにあります。