1. 記事一覧 >
  2. ブログ記事
category logo

Apereo CASでDelegated Authentication with SAML2によるAzure AD SSO環境構築

(更新) (公開)

はじめに

Apereo CAS Server で Delegated Authentication with SAML2 による Azure AD(Azure Active Directory/Microsoft Entra ID) SSO 環境を構築しました。
...紐解くと以下の話になります。
普通の SAML 認証と CAS を介して Azure AD 認証の2段階実施したということです。


1. SAML 認証対応アプリケーション(PHP)&Shibboleth(SAML SP) 作成
2. CAS Server(SAML IdP) 作成
3. 1と2で SAML 認証実現
普通の SAML 認証 Shibboleth(SAML SP) 図


4. CAS Server の Delegated Authentication を設定
5. Shibboleth → CAS Server → Azure AD の SAML 認証実現
CAS を介して Azure AD 認証 図


今回、上記の流れのすべての手順を紹介していきます。

【 CAS 】

CAS(Central Authentication Service)サーバーは、複数の Web アプリケーションにシングルサインオン(SSO)ソリューションを提供する集中認証サーバーです。

主に Java で実装されているオープンソースです。(https://github.com/apereo/cas

OpenID Connect にも対応しますが、今回は、SAML で利用します。

(2024 年 2 月時点)

RC(Release Candidate)以外の最新は、v7.0.1 ですが、まだ新しすぎるため、今回の記事では、6.6.x 系最新の Apereo CAS Server 6.6.15 で実施しています。

また、今回、Docker で立ち上げません。ビルドして立ち上げます。

何も設定しない場合の CAS Server URL は、

https://cas.example.org:8443/cas/

ですが、

https://cas.example.com:8443/cas/

でいきます。(.org → .com)

※こういうことをして、どハマりするのは、あるあるなのですが、あえて踏み込みます。

【 Shibboleth 】

Shibboleth は、組織内および組織を超えて Web 上でフェデレーション・シングルサイオン(SSO)を実現する、標準的なオープンソースソフトウェアのパッケージです。

サービスプロバイダ(Service Provider/SP)、アイデンティティプロバイダ(Identitiy Provider/IdP)両方の機能を持ちますが、今回は、SP として利用します。

また、OpenID Connect にも対応しますが、今回は、SAML で利用します。

Azure AD(Azure Active Directory)は、Microsoft Entra ID に名称が変わりましたが、この記事では、Azure AD 表記のままでいきます。

【検証環境】

●CAS Server

・Ubuntu 22.04.3 LTS

 ・Apereo CAS Server 6.6.15

●Apache & Shibboleth & PHP

・Ubuntu 22.04.3 LTS(上記環境に同居)

 ・Apache 2.4.52

 ・shibboleth 3.3.0

 ・PHP 8.1.2

●IntelliJ IDEA Community 2023.3.3

● クラウド:Azure AD(Azure Active Directory/Microsoft Entra ID)


CAS Server ビルド

基本的には、公式ドキュメント(https://apereo.github.io/cas/6.6.x/developer/Build-Process.html)を参考にして実行しています。

root 権限で作業しています。

CAS Server 6.6.15 を git clone で入手します。

# apt update -y
# apt install git -y
# cd /opt
# git clone --depth=1 --single-branch --branch=v6.6.15 https://github.com/apereo/cas.git cas-server

OpenJDK 11 をインストールします。

# apt install openjdk-11-jdk -y

11 である理由は、公式ドキュメント(https://apereo.github.io/cas/6.6.x/planning/Installation-Requirements.html)に書いてあるからです。


プロジェクトトップで、gradlew を使ってビルドします。

注意:環境により異なりますが、時間がかかります。30分以上かかるかもしれません。

# cd /opt/cas-server
# ./gradlew build --parallel -x test -x javadoc -x check --build-cache --configure-on-demand

./gradlew build:Gradle ラッパーを使用して build タスクを実行します。これにより、プロジェクトがコンパイルされ、テストが実行され、JAR が生成されます。

--parallel:可能な場合、プロジェクトのサブプロジェクトを並行して実行します。これにより、ビルド時間が短縮されます。

-x testtest タスクを除外します。つまり、テストは実行されません。

-x javadocjavadoc タスクを除外します。つまり、JavaDoc は生成されません。

-x checkcheck タスクを除外します。これにより、静的コード解析などのチェックは実行されません。

--build-cache:ビルドキャッシュを有効にします。これにより、以前のビルドから再利用可能なタスク出力がキャッシュされ、ビルド時間が短縮されます。

--configure-on-demand:設定をオンデマンドにします。これにより、現在実行中のタスクに関連しないプロジェクトの設定フェーズがスキップされ、ビルド時間が短縮されます。


Web アプリビルド

CAS Server サブプロジェクトのアプリケーションサーバーをビルドします。
いきなりビルドしてもうまくいかないため、その前に準備します。


https:// で動作するため、公開鍵/秘密鍵のペアを生成し、キーストアに保存します。

# mkdir /etc/cas
# keytool -genkey -alias cas -keyalg RSA -validity 999 \
    -keystore /etc/cas/thekeystore -ext san=dns:cas.example.com

このとき、パスフレーズの入力を求められます。

今回は、xxxxxx とします。

keytool -genkey:新しい公開鍵/秘密鍵のペアを生成します。

-alias cas:生成されたキーのエイリアスを cas とします。エイリアスは、キーストア内のキーを一意に識別するための名前です。

-keyalg RSA:キーのアルゴリズムを RSA とします。RSA は公開鍵暗号の一種で、広く使用されています。

-validity 999:生成されたキーの有効期間を 999 日とします。

-keystore /etc/cas/thekeystore:生成されたキーを /etc/cas/thekeystore というパスのキーストアに保存します。

-ext san=dns:cas.example.com:Subject Alternative Name (SAN) 拡張を使用して、証明書が cas.example.com という DNS 名を持つことを示します。

各質問の回答は以下とします。(テストサーバーなので、かなり適当な回答です。公式ドキュメントがこうなっていました。)

cas.example.com

Test

Test

Test

Test

US

はい

最後の「はい」は日本語 Ubuntu の場合、「Yes」ではなくて、日本語入力で「はい」を入力する必要がありました。


"The certificate exported out of your keystore needs to also be imported into the Java platform’s global keystore:"
キーストアからエクスポートされた証明書は、Java プラットフォームのグローバル キーストアにもインポートする必要があります。
(公式ドキュメント)
ということですので、グローバル キーストアにもインポートします。


まずは、JAVA_HOME 環境変数を設定します。
いろいろな方法があると思いますが、ここでは、全ユーザー有効とします。

# echo 'export JAVA_HOME=$(dirname $(dirname $(readlink -f $(which java))))' >> /etc/profile
# source /etc/profile
# echo $JAVA_HOME
/usr/lib/jvm/java-11-openjdk-amd64

証明書をファイル(/etc/cas/config/cas.crt)にエクスポートします。

# mkdir /etc/cas/config
# keytool -export -file /etc/cas/config/cas.crt -keystore /etc/cas/thekeystore -alias cas

エクスポートした証明書をグローバル キーストアにインポートします。

# keytool -import -file /etc/cas/config/cas.crt -alias cas -keystore $JAVA_HOME/lib/security/cacerts

このとき、パスフレーズの入力を求められますが、先ほど設定した xxxxxx ではありません。

グローバル キーストアのパスフレーズです。

環境によって異なると思いますが、今回の場合、設定していないため、デフォルトの changeit でした。


ビルド後起動してきたアプリケーションサーバーがキーストア /etc/cas/thekeystore を参照して、これのパスワードが xxxxxx のため、これをあらかじめ設定しておきます。

# vi /etc/cas/config/cas.properties
/etc/cas/config/cas.properties
server.ssl.key-store-password=xxxxxx
server.ssl.key-password=xxxxxx

設定しない場合、デフォルトの changeit で動作しようとするため、以下のエラーになり、起動しません。
Caused by: java.lang.IllegalArgumentException: keystore password was incorrect
Caused by: java.io.IOException: keystore password was incorrect
Caused by: java.security.UnrecoverableKeyException: failed to decrypt safe contents entry: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.
> Task :webapp:cas-server-webapp-tomcat:bootRun FAILED


準備が完了したため、ビルドします。

# cd /opt/cas-server
# cd webapp/cas-server-webapp-tomcat
# ../../gradlew build bootRun --parallel --configure-on-demand --build-cache --stacktrace

bootRun タスクは Spring Boot アプリケーションを起動します。

--stacktrace:ビルドが失敗した場合にスタックトレースを出力します。

公式ドキュメントの場合、--offline オプションがありますが、以下のエラーで止まるため、--offline オプション無しです。

> Task :webapp:cas-server-webapp-tomcat:checkstyleTest FAILED

FAILURE: Build failed with an exception.

* What went wrong:

Execution failed for task ':webapp:cas-server-webapp-tomcat:checkstyleTest'.

> Could not resolve all files for configuration ':webapp:cas-server-webapp-tomcat:checkstyle'.

> Could not resolve com.puppycrawl.tools:checkstyle:10.3.1.

Required by:

project :webapp:cas-server-webapp-tomcat

> No cached version of com.puppycrawl.tools:checkstyle:10.3.1 available for offline mode.


以下のような出力になったら、ビルド&起動成功です。

  ____  _____    _    ______   __
 |  _ \| ____|  / \  |  _ \ \ / /
 | |_) |  _|   / _ \ | | | \ V /
 |  _ <| |___ / ___ \| |_| || |
 |_| \_\_____/_/   \_\____/ |_|

>
2024-02-10 17:59:53,009 INFO [org.apereo.cas.web.CasWebApplicationReady] - <>
2024-02-10 17:59:53,009 INFO [org.apereo.cas.web.CasWebApplicationReady] - <Ready to process requests @ [2024-02-10T08:59:53.002Z]>
2024-02-10 18:00:22,995 INFO [org.apereo.cas.ticket.registry.DefaultTicketRegistryCleaner] - <[0] expired tickets removed.>
<============-> 99% EXECUTING [2m 59s]
> IDLE
> IDLE
> IDLE
> :webapp:cas-server-webapp-tomcat:bootRun

とりあえず、アクセスしてみます。


URL: https://cas.example.com:8443/cas/
User: casuser
Password: Mellon
です。

cas.example.com:8443/cas エントリー画面


cas.example.com:8443/cas ログイン後


OK!


Build Aliases

bc と入力するだけで再ビルド&bootRun のコマンドエイリアス(ショートカット)を作成します。

cas と入力すると、cd /opt/cas-server の意味のエイリアスも含まれます。

bci もありますが、bci は、今回使いません。

# vi ~/.bashrc
~/.bashrc
# Adjust the cas alias to the location of cas project folder
alias cas='cd /opt/cas-server'

# Run CAS with module selections
# $> bc oidc,gauth
function bc() {
  clear
  cas
  cd webapp/cas-server-webapp-tomcat
  casmodules="$1"
  if [ ! -z "$casmodules" ] ; then
    echo "Loading CAS Modules: ${casmodules}"
  fi

  # Could also use: gm -b ./build.gradle
  ../../gradlew build bootRun \
    --configure-on-demand --build-cache \
    --parallel -x test -x javadoc -x check -DenableRemoteDebugging=true \
    --stacktrace -DskipNestedConfigMetadataGen=true \
    -DremoteDebuggingSuspend=false \
    -DcasModules=${casmodules}
}

# Install JARs/WARs for use with a CAS overlay project
alias bci='clear; cas; \
    ./gradlew clean build publishToMavenLocal \
    --configure-on-demand \
    --build-cache --parallel \
    -x test -x javadoc -x check --stacktrace \
    -DskipNestedConfigMetadataGen=true \
    -DskipBootifulArtifact=true'
# source ~/.bashrc

-DenableRemoteDebugging=true:リモートデバッグを有効にします。

-DskipNestedConfigMetadataGen=true:ネストした設定メタデータの生成をスキップします。

-DremoteDebuggingSuspend=false:リモートデバッグが有効になっている場合でも、JVM の起動を一時停止しません。

-DcasModules=${casmodules}:casModules プロパティを ${casmodules} 環境変数の値に設定します。これは、ビルドする CAS モジュールを指定するためのものです。この後の作業で重要な意味を持ちます。


Shibboleth SP 構築

とりあえず、CAS Server の件は一旦置いといて、認証をかけたいアプリケーション側 Apache & PHP & Shibboleth SP の環境を構築します。
ここは、主題ではないため、細かい説明は端折ります。

アプリケーションの URL は、

https://shibapp.example.com/info.php

とします。

# apt -y update
# apt -y install apache2
# apt -y install libapache2-mod-shib
# a2enmod shib
Module shib already enabled
# a2enmod ssl
# a2ensite default-ssl
# openssl genrsa -aes128 2048 > server.key
#  openssl req -new -key server.key > server.csr
Enter pass phrase for server.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:JP
State or Province Name (full name) [Some-State]:Aichi
Locality Name (eg, city) []:Toyota
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:shibapp.example.com
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:
# openssl x509 -in server.csr -days 365 -req -signkey server.key > server.crt
# openssl rsa -in server.key -out server.key
# cp -p server.crt /etc/ssl/certs/shib.crt
# cp -p server.key /etc/ssl/private/shib.key
# chmod 400 /etc/ssl/private/shib.key
# vi /etc/apache2/sites-available/default-ssl.conf
    SSLCertificateFile      /etc/ssl/certs/ssl-cert-snakeoil.pem
    SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key
を以下に変更
    SSLCertificateFile      /etc/ssl/certs/shib.crt
    SSLCertificateKeyFile /etc/ssl/private/shib.key
# apt -y install php-common libapache2-mod-php php-cli
# vi /var/www/html/info.php
<?php
  phpinfo();
# vi /etc/hosts
127.0.0.1	shibapp.example.com

/info.php と php にアクセスに来たら、認証がかかるように設定します。

# vi /etc/apache2/sites-available/default-ssl.conf
/etc/apache2/sites-available/default-ssl.conf
<IfModule mod_ssl.c>
	<VirtualHost *:443>
		ServerAdmin webadmin@shibapp.example.com
		ServerName shibapp.example.com
		DocumentRoot /var/www/html

		SSLEngine on
		SSLCertificateKeyFile /etc/ssl/private/shib.key
		SSLCertificateFile /etc/ssl/certs/shib.crt
		<Files *.php>
			Authtype shibboleth
			ShibRequireSession On
			require valid-user
		</Files>
	</VirtualHost>
	<VirtualHost _default_:443>
...(既存設定)

Shibboleth SP の設定をします。
ここでは、entityID(SP - IdP 間のお互いの識別子)と IdP メタデータの最低限必要な設定だけします。

# cp -p /etc/shibboleth/shibboleth2.xml /etc/shibboleth/shibboleth2.xml.org
# vi /etc/shibboleth/shibboleth2.xml
13     <ApplicationDefaults entityID="https://sp.example.org/shibboleth"
13     <ApplicationDefaults entityID="https://shibapp.example.com/shibboleth"

34             <SSO entityID="https://idp.example.org/idp/shibboleth"
34             <SSO entityID="https://cas.example.com:8443/saml/idp"

68         <!--
69         <MetadataProvider type="XML" validate="true" path="partner-metadata.xml"/>
70         -->
68
69         <MetadataProvider type="XML" validate="true" path="/etc/cas/saml/idp-metadata.xml"/>
70

SSO entityID="https://cas.example.com:8443/saml/idp" は、CAS の SAML IdP エンティティ ID です。この後 CAS 側に設定します。

/etc/cas/saml/idp-metadata.xml は、IdP(CAS Server)側のメタデータ(XML)です。とりあえず、設定だけして、この後置きます。


# systemctl restart shibd
# systemctl restart apache2

CAS Server SAML 設定

CAS Server の設定を変更して、準備します。

# vi /etc/cas/config/cas.properties

/etc/cas/config/cas.properties
server.ssl.key-store-password=xxxxxx
server.ssl.key-password=xxxxxx
# サービスレジストリ(SPの設定)を JSON ファイルから初期化するかどうかを指定
# true:cas.service-registry.json.location で指定された JSON ファイルからサービス定義を読み込み
# false:従来の XML ファイルからサービス定義を読み込み
cas.service-registry.core.init-from-json=true
# cas.service-registry.json.location で指定された JSON ファイルに変更があった場合に、自動的にサービス定義を更新するかどうかを指定
# true:ファイル変更を監視し、サービス定義を更新
# false:ファイル変更を監視せず、サービス定義は手動で更新
cas.service-registry.json.watcher-enabled=true
# JSON 形式でサービス定義を記述したファイルの場所を指定
cas.service-registry.json.location=file:/etc/cas/services
# CAS の SAML IdP エンティティ ID を指定
cas.authn.saml-idp.core.entity-id=https://cas.example.com:8443/saml/idp
# CAS Server の完全な URL を指定
cas.server.name=https://cas.example.com:8443
# CAS Server の URL プレフィックスを指定
cas.server.prefix=${cas.server.name}/cas
# CAS Server のスコープ(ドメイン名)を指定
cas.server.scope=example.com

CAS Server と CAS Modules の一つ saml-idp をビルドして起動します。
このとき、bc ではなく、bc saml-idp で起動します。
これは何を意味しているかというと、
org.apereo.cas:cas-server-support-saml-idp
をローカルのソースコードからビルドして組み込むという意味です。
指定するのは、
org.apereo.cas:cas-server-support-[サブプロジェクト名プレフィックス部分]
の [サブプロジェクト名プレフィックス部分] で、
例えば、cas-server-support-ldap
もビルドしたい場合、
bc saml-idp,ldap です。


なお、公式ドキュメントの手順では、以下のように build.gradle を書き換えて、bc ですが、この場合、ローカルソースコードが使われずにビルドされます。そのため、一生懸命 LOGGER でログ出力を増やしたりしても反映されません。
今回、下記は実施しません。

# cd /opt/cas-server
# vi build.gradle
/opt/cas-server/build.gradle
// (注意:今回実施しない)
// 略
buildscript {
    repositories {
        mavenCentral()
        gradlePluginPortal()
        maven {
            url "https://repo.spring.io/milestone"
            mavenContent { releasesOnly() }
        }
// ここから
        maven {
            mavenContent { releasesOnly() }
            url "https://build.shibboleth.net/maven/releases/"
        }
// ここまで追加
    }
// 略
    dependencies {
// ここから
        implementation "org.apereo.cas:cas-server-support-saml-idp:${project.'version'}"
// ここまで追加

        implementation libraries.aspectj
// 略

(CTRL + C で停止済みとします。)

# bc saml-idp

ここで、
2024-02-11 12:15:19,131 INFO [org.apereo.cas.support.saml.idp.metadata.generator.BaseSamlIdPMetadataGenerator] - <Creating SAML2 metadata for identity provider...>
という出力があった後、
/etc/cas/saml/idp-metadata.xml
が作成されます。

本来、この後、idp-metadata.xml を SP 側に送り込まないといけないと思いますが、今回、SP(Shibboleth)も同居しているため、Shibboleth でそのまま /etc/cas/saml/idp-metadata.xml を読み込むように設定しています。


/etc/cas/saml/idp-metadata.xml
が作成されて、
READY 表示があったら、成功ですが、
SP の情報を CAS Server に登録していないため、一旦、CTRL + C で止めます。


cas-management

SP の情報を CAS Server に登録します。
/etc/cas/services/*.json に書くのですが、何をどう書けば良いか分かりませんので、
cas-management(https://apereo.github.io/cas-management/6.6.x/index.html)を利用し、json を生成します。

CAS(Central Authentication Service)と CAS Management Web Application は別々の独立したアプリケーションです。

CAS Management は CAS のサービスを管理するための管理用インターフェースであり、CAS サーバーの運用能力は CAS Management のデプロイ状態に依存しません。

つまり、管理画面をオフラインにしたり、完全に削除したりしても、CAS サーバー自体の運用には影響がないですが、サービスレジストリの管理や操作を行うには、別の方法を検討する必要があります。

# cd /opt
# git clone --depth=1 --single-branch --branch=v6.6.4 https://github.com/apereo/cas-management.git cas-management
# cd /opt/cas-management
# ./install.sh

バージョンを指定しないと、CAS 7.x.x 用のものがダウンロードされて、install.sh が存在しません。


ビルドに成功したら、CAS Server のときと同じように、キーストアのパスワードを設定しておきます。

# vi /etc/cas/config/management.properties
/etc/cas/config/management.properties
server.ssl.key-store-password=xxxxxx
server.ssl.key-password=xxxxxx

これを行わないと、以下のエラーになり、cas-management が起動しません。
Caused by: org.apache.catalina.LifecycleException: Protocol handler start failed
Caused by: java.lang.IllegalArgumentException: keystore password was incorrect
Caused by: java.io.IOException: keystore password was incorrect
Caused by: java.security.UnrecoverableKeyException: failed to decrypt safe contents entry: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption.


さらに、そのまま起動すると、CAS Server のポート 8443 と被るため、ポートを変更します。

/etc/cas/config/management.properties
mgmt.server-name=https://localhost:9443
server.port=9443

CAS Management Web App にアクセスしたとき最初に CAS Server を利用した認証があるため、CAS Server にリダイレクトされるのですが、
https://cas.example.org:8443/cas/login(.com ではなく、デフォルトの.org)
にリダイレクトされるため、CAS Server についての設定も追加します。
このとき、サービスレジストリ(SP についての *.json を置く場所のこと)も /etc/cas/services に設定しておきます。

/etc/cas/config/management.properties
cas.server.name=https://cas.example.com:8443
cas.server.prefix=${cas.server.name}/cas
cas.service-registry.json.location=file:/etc/cas/services

CAS Management Web App についての認証設定を作成します。

cas.service-registry.json.location=file:/etc/cas/services

の設定が影響して、内部デフォルトから *.json 読み取りに変更されるため、自分で設定しておかないといけないです。

# mkdir /etc/cas/services
# vi /etc/cas/services/casmanagement-001.json
/etc/cas/services/casmanagement-001.json
{
  "@class": "org.apereo.cas.services.CasRegisteredService",
  "serviceId": "https://localhost:9443/cas-management/.*",
  "name": "CAS Management Web App",
  "id": 10000001
}

CAS Server を起動します。

# bc saml-idp

続いて、CAS Management Web ダッシュボードを build & bootRun します。

# cd /opt/cas-management
# cd webapp/cas-mgmt-webapp-tomcat
# ../../gradlew build && ../../gradlew bootRun --configure-on-demand --build-cache --parallel --scan --stacktrace -x check -x test -x javadoc -DskipErrorProneCompiler=true -DbuildDev=true -DskipClientBuild=true

READY が表示されたら、https://localhost:9443/cas-management にアクセスします。

cas-management ログイン

User: casuser
Password: Mellon
でログインします。


cas-management ログイン後


SAML Services をクリックします。
右上の ボタンをクリックします。

SAML Services→+ボタンクリック


+ New Service をクリックします。 + New Serviceクリック


Basics タブで、
Service Type: SAML2 Service Provider
Entity Id: https://shibapp.example.com/shibboleth
Service Name: shibapp
と入力します。

Basicsタブ設定


Metadata タブで、
Metadata Location: https://shibapp.example.com/Shibboleth.sso/Metadata
と入力し、右上の Save ボタンをクリックします。

Metadataタブ設定


Save ボタンをクリック直後


実際に確認してみます。

# find /etc/cas/services/
/etc/cas/services/
/etc/cas/services/shibapp-1707985312253.json
# cat /etc/cas/services/shibapp-1707985312253.json
/etc/cas/services/shibapp-1707985312253.json
{
  @class: org.apereo.cas.support.saml.services.SamlRegisteredService
  serviceId: https://shibapp.example.com/shibboleth
  name: shibapp
  id: 1707985312253
  metadataLocation: https://shibapp.example.com/Shibboleth.sso/Metadata
  skipGeneratingSubjectConfirmationNameId: false
  signingCredentialType: BASIC
}

追加されました!

この .json は手動で作成しても同じことです。

ファイル名は、[英数字]-[数字].json が推奨されているようです。


Shibboleth SP SAML 認証確認

さて、ここで、SP, IdP ともに準備完了なので、
https://shibapp.example.com/info.php
にアクセスしたいところですが、まだやることがあります!


https://shibapp.example.com/info.php が自己署名証明書のため、
CAS Server → https://shibapp.example.com/Shibboleth.sso/Metadata
のアクセスに失敗します。
以下のエラーになります。
2024-02-10 16:31:34,340 ERROR [org.apereo.cas.util.HttpUtils] - <SSL error accessing: [https://shibapp.example.com/Shibboleth.sso/Metadata]
Alert.java:createSSLException:131
TransportContext.java:fatal:360
TransportContext.java:fatal:303


エラー時の画面表示は以下です。
アプリケーションは CAS を使う権限がありません

アプリケーションは CAS を使う権限がありません


/etc/ssl/certs/shib.crt にある証明書を Java のデフォルトのキーストアにインポートして、Java から見て信頼済みのサイトとします。

# keytool -import -trustcacerts -cacerts -storepass changeit -noprompt -alias shibboleth -file /etc/ssl/certs/shib.crt

https://shibapp.example.com/info.php にアクセスします。
このとき、まず、CAS Server の認証画面にリダイレクトされるため、
User: casuser
Password: Mellon
でログインします。

CAS Server の認証画面にリダイレクト


認証後、php にアクセスできて、アプリケーションのコンテンツが表示されます!

アプリケーションのコンテンツ表示


これで第一弾の普通の SAML 認証は完了です。

普通の SAML 認証 Shibboleth(SAML SP) 図


続いて、CAS を介して Azure AD 認証を実現する手順に入ります。
完成後のイメージは以下です。
CAS を介して Azure AD 認証 図


Azure AD 連携

先ほど、CAS Server のログイン情報でアプリケーションにログインしましたが、Azure AD で認証できるようにします。
これが、https://apereo.github.io/cas/6.6.x/integration/Delegate-Authentication-SAML.html で、
"Delegated Authentication w/ SAML2"(w/ は、with の省略)と言っているやつです。


Azure AD 設定

CAS Server の設定変更が必要ですが、まず、Azure AD の SAML エンタープライズ アプリケーション登録を行います。


別の SAML の記事「Ubuntu 22 に Keycloak 22 をインストールして、Identity providers=Azure AD で SAML - IdP:Azure AD」と同じ手順のため、省略します。


異なるのは、
識別子 (エンティティ ID)応答 URL (Assertion Consumer Service URL) 部分で、
識別子 (エンティティ ID)https://cas.example.com:8443
応答 URL (Assertion Consumer Service URL)https://cas.example.com:8443/cas/login?client_name=SAML2Client
です。

Azure AD SAML エンタープライズ アプリケーション登録


ここで、
アプリのフェデレーション メタデータ URL と書かれている URL から XML ファイルをダウンロードします。
ここではそれを /home/admin/SAML2Client.xml として、配置したものとします。

アプリのフェデレーション メタデータ URL


Azure AD のメタデータ配置

IdP(Azure AD)のメタデータ(XML)を配置します。

# cp /home/admin/SAML2Client.xml /etc/cas/SAML2Client.xml

CAS 設定

以下の設定をします。

# vi /etc/cas/config/cas.properties

/etc/cas/config/sp-metadata.xml

がまだ存在しませんが、この後、自動生成しますので、とりあえず、パスだけ書きます。

/etc/cas/config/cas.properties
# SAML認証に使用するキーストアのパスワード
cas.authn.pac4j.saml[0].keystore-password=pac4j-demo-passwd
# プライベートキーのパスワード
cas.authn.pac4j.saml[0].private-key-password=pac4j-demo-passwd
# サービスプロバイダのエンティティID(今回の場合は、CAS Server。Azure ADから見たサービスプロバイダ。一般的にはURLを記載)
cas.authn.pac4j.saml[0].service-provider-entity-id=https://cas.example.com:8443
# キーストアへのパス
cas.authn.pac4j.saml[0].keystore-path=/etc/cas/config/samlKeystore.jks
# サービスプロバイダのメタデータへのパス
cas.authn.pac4j.saml[0].service-provider-metadata-path=/etc/cas/config/sp-metadata.xml
# アイデンティティプロバイダのメタデータへのパス
cas.authn.pac4j.saml[0].identity-provider-metadata-path=/etc/cas/SAML2Client.xml
# クライアント名(IdP選択ボタンに表示される名前)
cas.authn.pac4j.saml[0].client-name=SAML2Client
# SAMLRequestにNameQualifierを含めるかどうか(false: 含めない)
cas.authn.pac4j.saml[0].use-name-qualifier=false

ここで、
cas.authn.pac4j.saml[0].use-name-qualifier=false
に関しては、重要な意味を持ちます。
なぜか、Azure AD に NameQualifier を含めた SAMLRequest を送信すると、以下のエラーになります。
AADSTS7500525: There was an XML error in the SAML message at line 2, position 709. Verify that the XML content of the SAML messages conforms to the SAML protocol specifications.

AADSTS7500525 エラー


エラーのSAMLRequest
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest AssertionConsumerServiceURL="https://cas.example.com:8443/cas/login?client_name=SAML2Client" AttributeConsumingServiceIndex="0" Destination="https://login.microsoftonline.com/********-****-****-****-************/saml2" ForceAuthn="false" ID="_***************************************" IsPassive="false" IssueInstant="2024-02-16T13:54:23.324Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"
	xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
	<saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity" NameQualifier="https://cas.example.com:8443"
		xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://cas.example.com:8443
	</saml2:Issuer>
</saml2p:AuthnRequest>
正しいSAMLRequest
<?xml version="1.0" encoding="UTF-8"?>
<saml2p:AuthnRequest AssertionConsumerServiceURL="https://cas.example.com:8443/cas/login?client_name=SAML2Client" AttributeConsumingServiceIndex="0" Destination="https://login.microsoftonline.com/********-****-****-****-************/saml2" ForceAuthn="false" ID="_***************************************" IsPassive="false" IssueInstant="2024-02-16T13:54:23.324Z" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Version="2.0"
	xmlns:saml2p="urn:oasis:names:tc:SAML:2.0:protocol">
	<saml2:Issuer Format="urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
		xmlns:saml2="urn:oasis:names:tc:SAML:2.0:assertion">https://cas.example.com:8443
	</saml2:Issuer>
</saml2p:AuthnRequest>

CAS 起動

bc saml-idp,pac4j-webflow で起動します。
すなわち、org.apereo.cas:cas-server-support-pac4j-webflow を有効にして、ローカルソースコードをビルドします。

# bc saml-idp,pac4j-webflow

アプリケーション https://shibapp.example.com/info.php にアクセスします。

SAML2CLIENT表示


なんか、「SAML2CLIENT」って出てきました!


なお、この瞬間、/etc/cas/config/sp-metadata.xml が生成されます。

pac4j-webflow を有効にしないと、「SAML2CLIENT」は出てこず、sp-metadata.xml も生成されません。


IdP 選択

ユーザー名とパスワードを入力すると、今まで通り、IdP = CAS Server でログインになります。
IdP = Azure AD でログインしたいので、SAML2CLIENT ボタンを押します。

SAML2CLIENTボタンクリック


Azure AD 認証開始


アプリケーションのコンテンツ表示


Azure AD 認証ヨシっ!


以上!


といきたいところですが、Azure AD 一択の場合、いちいち「SAML2CLIENT」ボタンを押すのがめんどくさいです。


これをなくす設定があります。


/etc/cas/config/cas.properties
cas.authn.pac4j.saml[0].auto-redirect-type=CLIENT

もしくは、


/etc/cas/config/cas.properties
cas.authn.pac4j.saml[0].auto-redirect-type=SERVER

です。


どちらも、「SAML2CLIENT」ボタンを押す必要がなく、Azure AD 認証に遷移します。
どう違うかというと、「お待ちください」的な画面が一瞬表示されるか、全く画面が表示されないかの違いです。


全く画面が表示されないのは、
cas.authn.pac4j.saml[0].auto-redirect-type=SERVER
です。
CAS Server → https://login.microsoftonline.com/ の直接通信ができない場合、
cas.authn.pac4j.saml[0].auto-redirect-type=CLIENT
を設定することになると思います。


SERVER

auto-redirect-type=SERVER 図


CLIENT

auto-redirect-type=CLIENT 図

JavaScript の onLoad で SAML2CLIENT ボタンを押したことにしているから自動で遷移します。


おまけ(IntelliJ IDEA でのデバッグ)

目的は達成しましたが、ソースコードをビルドしてデバッグポートを開いているのだから、IntelliJ IDEA Community でのデバッグを行ってみます。


IntelliJ インストール

IntelliJ IDEA Community をインストールします。

Ubuntu 22 の場合、デスクトップ(リモートデスクトップではない。)にログインして、Terminal アプリで作業を行う必要があります。

# tar -xzf /home/admin/ideaIC-2023.3.3.tar.gz -C /opt
# cd /opt/idea-IC-233.14015.106/bin
# ./idea.sh

プロジェクトを開くたびに
認証が必要です
コンピューターへのログインに使用するパスワードが、もはやログインキーリングのパスワードと一致しなくなっています。
と表示されるのがうざいため、表示されなくしておきます。

認証が必要です

この行為の意味が分からない場合は、実施しないでください。認証が必要です は、キャンセル クリックで、先に進みます。

# rm /home/admin/.local/share/keyrings/login.keyring

IntelliJ IDEA Community 起動後、
新しいキーリングのパスワード指定
と表示されるため、空白のまま 続行 をクリックします。

新しいキーリングのパスワード指定


続行クリック


Debug 設定

左上のハンバーガーメニューをクリックして、RunEdit Configurations... をクリックします。

Edit Configurations


左上の をクリックします。

+クリック


Remote JVM Debug をクリックします。

Remote JVM Debug


Port:5000 に変更し、ApplyOK をクリックします。

5000→Apply→OK


ブレークポイント設置

ファイル: /opt/cas-server/api/cas-server-core-api-authentication/src/main/java/org/apereo/cas/authentication/AuthenticationManager.java
対象コード: Authentication authenticate(AuthenticationTransaction authenticationTransaction) throws AuthenticationException;
にブレークポイントを張ります。

ブレークポイント設置


Debug

CAS Server を bc saml-idp,pac4j-webflow で起動します。


左上のハンバーガーメニューをクリックして、RunRun をクリックします。

Run→Run


UnnamedDebug をクリックします。

Unnamed→Debug


https://shibapp.example.com/info.php にアクセスして、ログインする際に、ブレークポイントで止まります。

アクセスして、ログイン


ブレークポイントで停止


ヨシっ!
以上!

loading...