Spring SecurityのCAS SSO

Spring Securityを使用したCAS SSO

1. 概要

この記事では、integrating the Central Authentication Service (CAS) with Spring Securityについて説明します。 CASはシングルサインオン(SSO)サービスです。

ユーザー認証を必要とするアプリケーションがあるとしましょう。 最も一般的な方法は、各アプリケーションにセキュリティメカニズムを実装することです。 ただし、すべてのアプリのユーザー認証を1か所で実装することをお勧めします。

これはまさにCAS SSOシステムが行うことです。 このarticleは、アーキテクチャの詳細を示します。 プロトコル図はhereにあります。

2. プロジェクトのセットアップとインストール

中央認証サービスの設定には、少なくとも2つのコンポーネントが関係しています。 1つのコンポーネントは、cas-serverと呼ばれるSpringベースのサーバーです。 他のコンポーネントは、1つ以上のクライアントで構成されます。

クライアントは、認証にサーバーを使用している任意のWebアプリケーションにすることができます。

2.1. CASサーバーのセットアップ

サーバーは、Maven(Gradle)War Overlayスタイルを使用して、セットアップと展開を容易にします。 複製して使用できるクイックスタートテンプレートがあります。

クローンを作成しましょう:

git clone https://github.com/apereo/cas-overlay-template.git cas-server

このコマンドは、cas-overlay-templateをローカルマシンのcas-serverディレクトリに複製します。

次に、ルートpom.xmlに依存関係を追加しましょう。 これらの依存関係により、JSON構成を介したサービス登録が可能になります。

また、データベースへの接続を容易にします。


    org.apereo.cas
    cas-server-support-json-service-registry
    ${cas.version}


    org.apereo.cas
    cas-server-support-jdbc
    ${cas.version}


    org.apereo.cas
    cas-server-support-jdbc-drivers
    ${cas.version}

cas-server-support-json-service-registrycas-server-support-jdbc、およびcas-server-support-jdbc-driversの依存関係の最新バージョンは、MavenCentralにあります。 親%(t​​3)sがアーティファクトのバージョンを自動的に管理することに注意してください。

次に、フォルダcas-server/src/main/resourcesを作成し、フォルダcas-server/etcをコピーしましょう。 それに。 また、アプリケーションのポートとSSLキーストアのパスも変更します。

cas-server/src/main/resources/application.propertiesの関連エントリを編集して、これらを構成します。

server.port=6443
server.ssl.key-store=classpath:/etc/cas/thekeystore
standalone.config=classpath:/etc/cas/config

構成フォルダーのパスもclasspath:/etc/cas/configに設定されました。 cas-server/src/main/resources/etc/cas/configを指します。

次のステップは、ローカルSSLキーストアを生成することです。 キーストアは、HTTPS接続の確立に使用されます。 この手順は重要であり、スキップしないでください。

ターミナルから、ディレクトリをcas-server/src/main/resources/etc/casに変更します。 その後、次のコマンドを実行します。

keytool -genkey -keyalg RSA -alias thekeystore -keystore thekeystore
-storepass changeit -validity 360 -keysize 2048

It’s important to use localhost when prompted for a first and last name、組織名、さらには組織単位。 これを行わないと、SSLハンドシェイク中にエラーが発生する場合があります。 市、州、国などの他のフィールドは、必要に応じて設定できます。

上記のコマンドは、名前がthekeystore、パスワードがchangeit.のキーストアを生成します。これは現在のディレクトリに保存されます。

次に、生成されたキーストアを.crt形式にエクスポートして、クライアントアプリケーションで使用できるようにする必要があります。 したがって、同じディレクトリで、次のコマンドを実行して、生成されたthekeystoreファイルをthekeystore.crt.にエクスポートします。パスワードは変更されません。

keytool -export -alias thekeystore -file thekeystore.crt
-keystore thekeystore

それでは、エクスポートされたthekeystore.crtをJavacacertsキーストアにインポートしましょう。 ターミナルプロンプトは、ディレクトリcas-server/src/main/resources/etc/casディレクトリにあります。

そこから、コマンドを実行します:

keytool -import -alias thekeystore -storepass changeit -file thekeystore.crt
 -keystore "C:\Program Files\Java\jdk1.8.0_152\jre\lib\security\cacerts"

念のため、JDKインストールの外部にあるJREに証明書をインポートすることもできます。

keytool -import -alias thekeystore -storepass changeit -file thekeystore.crt
-keystore "C:\Program Files\Java\jre1.8.0_152\lib\security\cacerts"

-keystoreフラグは、ローカルマシン上のJavaキーストアの場所を指していることに注意してください。 この場所は、手元のJavaインストールによって異なる場合があります。

さらに、ensure that the JRE that is referenced as the location of the key store is the same as the one that is used for the client application.

thekeystore.crtをJavaキーストアに正常に追加した後、システムを再起動する必要があります。 同様に、ローカルマシンで実行されているJVMのすべてのインスタンスを強制終了できます。

次に、ルートプロジェクトディレクトリから、cas-server,がターミナルからコマンドbuild packageおよびbuild runを呼び出します。 サーバーの起動には時間がかかる場合があります。 準備ができると、コンソールにREADYと出力されます。

この時点で、ブラウザでhttps://localhost:6443/casにアクセスすると、ログインフォームが表示されます。 デフォルトのユーザー名はcasuserで、パスワードはMellonです。

2.2. CASクライアントのセットアップ

Spring Initializrを使用して、Web、セキュリティ、Freemarker、およびオプションでDevToolsの依存関係を持つプロジェクトを生成しましょう。

Spring Initializrによって生成された依存関係に加えて、Spring SecurityCASモジュールの依存関係を追加しましょう。


    org.springframework.security
    spring-security-cas

依存関係の最新バージョンはMaven Centralにあります。 また、application.properties:に次のエントリを追加して、ポート9000でリッスンするようにサーバーのポートを構成しましょう。

server.port=9000

3. CASサーバーへのサービス/クライアントの登録

サーバーは、認証のためにすべてのクライアントがサーバーにアクセスすることを許可しません。 clients/services must be registered in the CAS server services registry

サーバーにサービスを登録するには、いくつかの方法があります。 これらには、YAML、JSON、Mongo、LDAP、およびothersが含まれます。

メソッドに応じて、pom.xmlファイルに含まれる依存関係があります。 この記事では、JSON Service Registryメソッドを使用します。 依存関係は、前のセクションのpom.xmlファイルにすでに含まれています。

クライアントアプリケーションの定義を含むJSONファイルを作成しましょう。 cas-server/src/main/resourcesフォルダー内に、さらに別のフォルダーを作成しましょう–services.このservicesフォルダーにJSONファイルが含まれています。

次に、cas-server/src/main/resources/servicesディレクトリにcasSecuredApp-19991.jsonという名前のJSONファイルを次の内容で作成します。

{
    "@class" : "org.apereo.cas.services.RegexRegisteredService",
    "serviceId" : "^http://localhost:9000/login/cas",
    "name" : "CAS Spring Secured App",
    "description": "This is a Spring App that usses the CAS Server for it's authentication",
    "id" : 19991,
    "evaluationOrder" : 1
}

serviceId属性は、認証にサーバーを使用することを目的としたクライアントアプリケーションの正規表現URLパターンを定義します。 この場合、パターンはローカルホストで実行され、ポート9000でリッスンしているアプリケーションと一致します。

id属性は、競合や誤って構成をオーバーライドしないように一意である必要があります。 サービス構成ファイル名は、規則serviceName-id.jsonに従います。 themeproxyPolicylogoprivacyUrlなどの他の構成可能な属性はhereにあります。

今のところ、JSONサービスレジストリをオンにするために、さらに2つの構成アイテムを追加しましょう。 1つは、サービス構成ファイルが置かれているディレクトリでサーバーに通知することです。 もう1つは、JSON構成ファイルからサービスレジストリの初期化を有効にすることです。

これらの構成アイテムは両方とも、cas.properties.という名前の別のファイルに配置されます。このファイルをcas-server /src/main/resourcesディレクトリに作成します。

cas.serviceRegistry.initFromJson=true
cas.serviceRegistry.config.location=classpath:/services

build runコマンドを再度実行し、コンソールの「Loaded [3] service(s) from [JsonServiceRegistryDao]」などの行に注意してみましょう。

4. Spring Securityの構成

4.1. シングルサインオンの構成

これで、Spring Boot ApplicationがCASサーバーにサービスとして登録されました。 ユーザー認証のためにconfigure Spring Security to work in concert with the serverをしましょう。 Spring Securityとサーバー間の相互作用の完全なシーケンスはhereにあります。

まず、SpringSecurityのCASモジュールに関連するBeanを構成しましょう。 これにより、Spring Securityは中央認証サービスと連携できます。

この点で、Spring BootアプリケーションへのエントリポイントであるCasSecuredAppApplicationクラスに構成Beanを追加する必要があります。

@Bean
public ServiceProperties serviceProperties() {
    ServiceProperties serviceProperties = new ServiceProperties();
    serviceProperties.setService("http://localhost:9000/login/cas");
    serviceProperties.setSendRenew(false);
    return serviceProperties;
}

@Bean
@Primary
public AuthenticationEntryPoint authenticationEntryPoint(
  ServiceProperties sP) {

    CasAuthenticationEntryPoint entryPoint
      = new CasAuthenticationEntryPoint();
    entryPoint.setLoginUrl("https://localhost:6443/cas/login");
    entryPoint.setServiceProperties(sP);
    return entryPoint;
}

@Bean
public TicketValidator ticketValidator() {
    return new Cas30ServiceTicketValidator(
      "https://localhost:6443/cas");
}

@Bean
public CasAuthenticationProvider casAuthenticationProvider() {

    CasAuthenticationProvider provider = new CasAuthenticationProvider();
    provider.setServiceProperties(serviceProperties());
    provider.setTicketValidator(ticketValidator());
    provider.setUserDetailsService(
      s -> new User("casuser", "Mellon", true, true, true, true,
        AuthorityUtils.createAuthorityList("ROLE_ADMIN")));
    provider.setKey("CAS_PROVIDER_LOCALHOST_9000");
    return provider;
}

CasAuthenticationFilterが内部的にマップされるデフォルトのサービスログインURLを使用してServicePropertiesBeanを構成します。 ServicePropertiessendRenewプロパティはfalseに設定されます。 結果として、ユーザーはログイン資格情報をサーバーに一度提示するだけで済みます。

後続の認証は自動的に行われます。つまり、ユーザーにユーザー名とパスワードを再度要求することはありません。 この動作は、認証に同じサーバーを使用する複数のサービスにアクセスできる単一のユーザーを意味します。

後で説明するように、ユーザーがサーバーから完全にログアウトすると、チケットは無効になります。 その結果、ユーザーはサーバーに接続されているすべてのアプリケーションから同時にログアウトされます。 これはシングルログアウトと呼ばれます。

サーバーのデフォルトのログインURLを使用してAuthenticationEntryPointBeanを構成します。 このURLはサービスログインURLとは異なることに注意してください。 このサーバーのログインURLは、ユーザーが認証のためにリダイレクトされる場所です。

TicketValidatorは、サーバーでの認証が成功したときにユーザーに付与されたサービスチケットを検証するためにサービスアプリが使用するBeanです。

フローは次のとおりです。

  1. ユーザーが保護されたページにアクセスしようとした

  2. AuthenticationEntryPointがトリガーされ、ユーザーをサーバーに移動します。 サーバーのログインアドレスがAuthenticationEntryPointで指定されています

  3. サーバーでの認証に成功すると、指定されたサービスURLにリクエストをリダイレクトし、クエリパラメーターとしてサービスチケットを追加します

  4. CasAuthenticationFilterは、パターンに一致するURLにマップされ、次に、内部でチケット検証をトリガーします。

  5. チケットが有効な場合、ユーザーは最初にリクエストされたURLにリダイレクトされます

次に、一部のルートを保護し、CasAuthenticationEntryPointBeanを使用するようにSpringSecurityを構成する必要があります。

WebSecurityConfigurerAdapterを拡張してconfig()をオーバーライドするSecurityConfig.javaを作成しましょう。

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
      http
        .authorizeRequests()
        .regexMatchers("/secured.*", "/login")
        .authenticated()
        .and()
        .authorizeRequests()
        .regexMatchers("/")
        .permitAll()
        .and()
        .httpBasic()
        .authenticationEntryPoint(authenticationEntryPoint);
    }
    // ...
}

また、SecurityConfigクラスでは、次のメソッドをオーバーライドし、同時にCasAuthenticationFilterBeanを作成します。

@EnableWebSecurity
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private AuthenticationProvider authenticationProvider;
    private AuthenticationEntryPoint authenticationEntryPoint;
    private SingleSignOutFilter singleSignOutFilter;
    private LogoutFilter logoutFilter;

    @Autowired
    public SecurityConfig(CasAuthenticationProvider casAuthenticationProvider, AuthenticationEntryPoint eP,
                          LogoutFilter lF
                          , SingleSignOutFilter ssF
    ) {
        this.authenticationProvider = casAuthenticationProvider;
        this.authenticationEntryPoint = eP;

        this.logoutFilter = lF;
        this.singleSignOutFilter = ssF;

    }

    // ...

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
      auth.authenticationProvider(authenticationProvider);
    }

    @Override
    protected AuthenticationManager authenticationManager() throws Exception {
      return new ProviderManager(Arrays.asList(authenticationProvider));
    }

    @Bean
    public CasAuthenticationFilter casAuthenticationFilter(ServiceProperties sP) throws Exception {
      CasAuthenticationFilter filter = new CasAuthenticationFilter();
      filter.setServiceProperties(sP);
      filter.setAuthenticationManager(authenticationManager());
      return filter;
    }
}

/secured/login、およびホームページにも送信されるリクエストを処理するコントローラーを作成しましょう。

ホームページは、メソッドindex().を持つIndexControllerにマップされます。このメソッドは、インデックスビューを返すだけです。

@GetMapping("/")
public String index() {
    return "index";
}

/loginパスは、AuthControllerクラスからlogin()メソッドにマップされます。 デフォルトのログイン成功ページにリダイレクトするだけです。

上記のHttpSecurityを構成する際に、認証が必要になるように/loginパスを構成したことに注意してください。 このようにして、認証のためにユーザーをCASサーバーにリダイレクトします。

このメカニズムは、/loginパスが保護されたルートではなく、ログインフォームを返す通常の構成とは少し異なります。

@GetMapping("/login")
public String login() {
    return "redirect:/secured";
}

/securedパスは、SecuredPageControllerクラスからindex()メソッドにマップされます。 認証されたユーザーのユーザー名を取得し、ウェルカムメッセージの一部として表示します。

@GetMapping
public String index(ModelMap modelMap) {
  Authentication auth = SecurityContextHolder.getContext()
    .getAuthentication();
  if(auth != null
    && auth.getPrincipal() != null
    && auth.getPrincipal() instanceof UserDetails) {
      modelMap.put("username", ((UserDetails) auth.getPrincipal()).getUsername());
  }
  return "secure/index";
}

すべてのビューは、cas-secured-appresourcesフォルダーで使用できることに注意してください。 この時点で、cas-secured-appはサーバーを認証に使用できるはずです。

最後に、ターミナルからbuild runを実行し、同時にSpringブートアプリも起動します。 SSLはこのプロセス全体の鍵であるため、上記のSSL生成手順はスキップしないでください。

4.2. シングルログアウトの構成

システムからのthe authentication process by logging out a userに進みましょう。 ユーザーがログアウトできる場所は、クライアントアプリとサーバーの2つです。

クライアントアプリ/サービスからユーザーをログアウトすることは、最初にすることです。 これは、同じサーバーに接続されている他のアプリケーションのユーザーの認証状態には影響しません。 もちろん、サーバーからユーザーをログアウトすると、他のすべての登録済みサービス/クライアントからもユーザーがログアウトされます。

CasSecuredAppApplicaitonクラスでいくつかのBean構成を定義することから始めましょう。

@Bean
public SecurityContextLogoutHandler securityContextLogoutHandler() {
    return new SecurityContextLogoutHandler();
}

@Bean
public LogoutFilter logoutFilter() {
    LogoutFilter logoutFilter = new LogoutFilter(
      "https://localhost:6443/cas/logout",
      securityContextLogoutHandler());
    logoutFilter.setFilterProcessesUrl("/logout/cas");
    return logoutFilter;
}

@Bean
public SingleSignOutFilter singleSignOutFilter() {
    SingleSignOutFilter singleSignOutFilter = new SingleSignOutFilter();
    singleSignOutFilter.setCasServerUrlPrefix("https://localhost:6443/cas");
    singleSignOutFilter.setIgnoreInitConfiguration(true);
    return singleSignOutFilter;
}

@EventListener
public SingleSignOutHttpSessionListener singleSignOutHttpSessionListener(
  HttpSessionEvent event) {
    return new SingleSignOutHttpSessionListener();
}

URLパターン/logout/casをインターセプトし、システム全体のログアウトのためにアプリケーションをサーバーにリダイレクトするようにlogoutFilterを構成します。 サーバーは、関係するすべてのサービスに単一のログアウト要求を送信します。 このような要求は、HTTPセッションを無効にするSingleSignOutFilter,によって処理されます。

SecurityConfigクラスのconfig()HttpSecurity構成を変更してみましょう。 以前に構成されたCasAuthenticationFilterLogoutFilterも、チェーンに追加されるようになりました。

http
  .authorizeRequests()
  .regexMatchers("/secured.*", "/login")
  .authenticated()
  .and()
  .authorizeRequests()
  .regexMatchers("/")
  .permitAll()
  .and()
  .httpBasic()
  .authenticationEntryPoint(authenticationEntryPoint)
  .and()
  .logout().logoutSuccessUrl("/logout")
  .and()
  .addFilterBefore(singleSignOutFilter, CasAuthenticationFilter.class)
  .addFilterBefore(logoutFilter, LogoutFilter.class);

ログアウトを正しく機能させるには、最初にユーザーをシステムからローカルでログアウトし、サーバーに接続されている他のすべてのサービスからユーザーをオプションでログアウトするためのリンクを含むページを表示するlogout()メソッドを実装する必要があります。

リンクは、上記で構成したLogoutFilterのフィルタープロセスURLとして設定されたものと同じです。

@GetMapping("/logout")
public String logout(
  HttpServletRequest request,
  HttpServletResponse response,
  SecurityContextLogoutHandler logoutHandler) {
    Authentication auth = SecurityContextHolder
      .getContext().getAuthentication();
    logoutHandler.logout(request, response, auth );
    new CookieClearingLogoutHandler(
      AbstractRememberMeServices.SPRING_SECURITY_REMEMBER_ME_COOKIE_KEY)
      .logout(request, response, auth);
    return "auth/logout";
}

ログアウトビュー:



    Cas Secured App - Logout


You have logged out of Cas Secured Spring Boot App Successfully


Log out of all other Services

5. CASサーバーをデータベースに接続する

認証には静的なユーザー認証情報を使用しています。 ただし、実稼働環境では、ユーザー資格情報はほとんどの場合データベースに保存されます。 したがって、次に、we show how to connect our server to a MySQL databasedatabase name: test)がローカルで実行されます。

これを行うには、cas-server/src/main/resourcesディレクトリのapplication.propertiesファイルに次のデータを追加します。

cas.authn.accept.users=
cas.authn.accept.name=

cas.authn.jdbc.query[0].sql=SELECT * FROM users WHERE email = ?
cas.authn.jdbc.query[0].url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false&serverTimezone=UTC
cas.authn.jdbc.query[0].dialect=org.hibernate.dialect.MySQLDialect
cas.authn.jdbc.query[0].user=root
cas.authn.jdbc.query[0].password=root
cas.authn.jdbc.query[0].ddlAuto=none
cas.authn.jdbc.query[0].driverClass=com.mysql.cj.jdbc.Driver
cas.authn.jdbc.query[0].fieldPassword=password
cas.authn.jdbc.query[0].passwordEncoder.type=NONE

application.propertiesの完全な内容は、ソースコードにあることに注意してください。 Leaving the value of cas.authn.accept.users blank deactivates the use of static user repositories by the server

さらに、データベースからユーザーを取得するSQLステートメントを定義します。 SQL自体を構成する機能により、データベース内のユーザーのストレージは非常に柔軟になります。

上記のSQLによると、ユーザーのレコードはusersテーブルに格納されます。 email列は、ユーザーのプリンシパル(ユーザー名)を表すものです。 構成のさらに下で、パスワードフィールドの名前cas.authn.jdbc.query[0].fieldPassword.を設定します。柔軟性をさらに高めるために、値passwordに設定します。

構成したその他の属性は、データベースユーザー(root)とパスワード(空白)、方言とJDBC接続String.です。サポートされているデータベース、使用可能なドライバー、および方言のリストはhereにあります。 。

Another essential attribute is the encryption type used for storing the password.この場合、NONEに設定されます。

ただし、サーバーはBcryptなどのより多くの暗号化メカニズムをサポートします。 hereで見つけることができるこれらの暗号化メカニズムと、他の構成可能なプロパティ。

サーバーを実行すると(build run))、構成されたデータベースに存在する資格情報を持つユーザーの認証が有効になります。 Note again that the principal in the database that the server uses must be the same as that of the client applications

この場合、Spring Bootアプリのプリンシパル(username))は、サーバーに接続されているデータベースの値(username)]と同じである必要があります。

次に、Spring BootアプリケーションのCasSecuredAppApplicationクラスで構成されたCasAuthenticationProvider Beanに接続されているUserDetailsを変更してみましょう。

@Bean
public CasAuthenticationProvider casAuthenticationProvider() {
    CasAuthenticationProvider provider = new CasAuthenticationProvider();
    provider.setServiceProperties(serviceProperties());
    provider.setTicketValidator(ticketValidator());
    provider.setUserDetailsService((s) -> new User(
      "[email protected]", "testU",
      true, true, true, true,
    AuthorityUtils.createAuthorityList("ROLE_ADMIN")));
    provider.setKey("CAS_PROVIDER_LOCALHOST_9000");
    return provider;
}

もう1つの注意点は、UserDetailsにはパスワードが与えられていますが、使用されていないことです。 ただし、ユーザー名がサーバーのユーザー名と異なる場合、認証は失敗します。

データベースに保存された資格情報でアプリケーションを正常に認証するには、127.0.0.1およびポート3306で実行されているMySQLサーバーを、ユーザー名rootおよびパスワードrootで起動します。

次に、source codeの一部であるSQLファイルcas-server\src\main esources\create_test_db_and_users_tbl.sql,を使用して、データベースtestにテーブルusersを作成します。

デフォルトでは、電子メール[email protected]とパスワードMellon.が含まれています。application.properties.のデータベース接続設定はいつでも変更できます。

build run,でCASサーバーをもう一度起動し、https://localhost:6443/casに移動して、認証にそれらの資格情報を使用します。 同じ資格情報は、casで保護されたSpring Bootアプリでも機能します。

6. 結論

SpringSecurityでCASServer SSOを使用する方法と、関連する多くの構成ファイルについて詳しく見てきました。

サーバーには、テーマやプロトコルの種類から認証ポリシーに至るまで構成できる他の多くの側面があります。 これらはすべて、ドキュメントのhereにあります。

この記事のサーバーとその構成ファイルのソースコードはhereにあり、Spring Bootアプリケーションのソースコードはhereにあります。