- 記事一覧 >
- ブログ記事
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 認証実現
4. CAS Server の Delegated Authentication を設定
5. Shibboleth → CAS Server → Azure AD の SAML 認証実現
今回、上記の流れのすべての手順を紹介していきます。
【 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 test
:test
タスクを除外します。つまり、テストは実行されません。
-x javadoc
:javadoc
タスクを除外します。つまり、JavaDoc は生成されません。
-x check
:check
タスクを除外します。これにより、静的コード解析などのチェックは実行されません。
--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
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
です。
OK!
Build Aliases
bc
と入力するだけで再ビルド&bootRun のコマンドエイリアス(ショートカット)を作成します。
cas
と入力すると、cd /opt/cas-server
の意味のエイリアスも含まれます。
bci
もありますが、bci
は、今回使いません。
# vi ~/.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
<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
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
// (注意:今回実施しない)
// 略
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
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 と被るため、ポートを変更します。
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
に設定しておきます。
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
{
"@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
にアクセスします。
User: casuser
Password: Mellon
でログインします。
SAML Services をクリックします。
右上の + ボタンをクリックします。
Basics タブで、
Service Type: SAML2 Service Provider
Entity Id: https://shibapp.example.com/shibboleth
Service Name: shibapp
と入力します。
Metadata タブで、
Metadata Location: https://shibapp.example.com/Shibboleth.sso/Metadata
と入力し、右上の Save ボタンをクリックします。
実際に確認してみます。
# find /etc/cas/services/
/etc/cas/services/
/etc/cas/services/shibapp-1707985312253.json
# cat /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 を使う権限がありません
/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
でログインします。
認証後、php にアクセスできて、アプリケーションのコンテンツが表示されます!
これで第一弾の普通の SAML 認証は完了です。
続いて、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
です。
ここで、
アプリのフェデレーション メタデータ URL と書かれている URL から XML ファイルをダウンロードします。
ここではそれを /home/admin/SAML2Client.xml として、配置したものとします。
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
がまだ存在しませんが、この後、自動生成しますので、とりあえず、パスだけ書きます。
# 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.
<?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>
<?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」って出てきました!
なお、この瞬間、/etc/cas/config/sp-metadata.xml が生成されます。
pac4j-webflow を有効にしないと、「SAML2CLIENT」は出てこず、sp-metadata.xml も生成されません。
IdP 選択
ユーザー名とパスワードを入力すると、今まで通り、IdP = CAS Server でログインになります。
IdP = Azure AD でログインしたいので、SAML2CLIENT ボタンを押します。
Azure AD 認証ヨシっ!
以上!
といきたいところですが、Azure AD 一択の場合、いちいち「SAML2CLIENT」ボタンを押すのがめんどくさいです。
これをなくす設定があります。
cas.authn.pac4j.saml[0].auto-redirect-type=CLIENT
もしくは、
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
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 設定
左上のハンバーガーメニューをクリックして、Run → Edit Configurations... をクリックします。
左上の + をクリックします。
Remote JVM Debug をクリックします。
Port: を 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
で起動します。
左上のハンバーガーメニューをクリックして、Run → Run をクリックします。
Unnamed → Debug をクリックします。
https://shibapp.example.com/info.php
にアクセスして、ログインする際に、ブレークポイントで止まります。
ヨシっ!
以上!
その他、宣伝、誹謗中傷等、当方が不適切と判断した書き込みは、理由の如何を問わず、投稿者に断りなく削除します。
書き込み内容について、一切の責任を負いません。
このコメント機能は、予告無く廃止する可能性があります。ご了承ください。
コメントの削除をご依頼の場合はTwitterのDM等でご連絡ください。