ポータル画面に個人向け通知サービスを表示する(前編)
はじめに
皆さんは、お勤めの会社で、経費精算はAシステム、ワークフローはBシステム、発注承認はCシステム…
それぞれメールで承認依頼は来るけど、他のメールに埋もれてしまってつい忘れてしまう といった事故の経験はありませか?
それぞれのシステムはSAML認証でシングルサインオンを実現しているのですが、各システムのダッシュボードを開けてみないとやるべきことがどれくらい残っているのかわからない。
そこで、『通知サービス』の登場です。
社内のSAML認証に対応したポータル画面の一部に『通知サービス』を表示するiframe
を埋め込んでもらって、そこにAシステム・Bシステム・Cシステムの未承認件数を表示されれば、とてもハッピーではないでしょうか?
ということで、その『通知サービス』を実現するための技術検証を行いましたので、自分の備忘録として記事にします。
技術検証の前提
- SAML認証には
keycloak
を認証サーバーにする - 通知サービスの前段に
Apache
を構えてリバースプロキシ兼認証サービスプロバイダとする - 通知サービスは
Next.js
で実装し、前編ではSAML認証されたユーザー情報を表示するのみとする - ポータルに関しては検証環境ではSAML認証に対応しないものとする
(そこもSAML認証を実装するパワーがありません) - 本記事ではSAML認証については言及しない
SAML認証については下記の記事がわかりやすかったです。
➡️ SAML認証を勉強せずに理解したい私から勉強せずに理解したい私へ
検証環境概要
検証環境について上図のような環境を構築していきます。
前編ではサービスプロバイダにアクセスすると、未認証の場合は認証サービスの認証画面が表示れされ、ユーザー名・パスワードを入力すると通知サービスにリダイレクトされるところまで構築します。通知サービスは、前編では外部のAPIとの連携はせず、認証サービスから取得したユーザーIDが表示されるところまで作ります。
構築手順概要
検証環境は次の手順で少しづつ構築していきます。
-
keycloak
をdocker-compose
で起動できるようにする-
keycloak
を起動し検証用realmsample
を作成 -
docker-compose
で起動時の環境設定ファイルを作成 -
keycloak
起動時に設定ファイルから起動できるようにする -
sample realm
のメタファイルを取得する
-
- 通知サービス初版を起動できるようにする
-
create-next-app
で雛形を作成 - ヘッダにユーザー情報があればページに表示するようにする
- 通知サービスを
docker-compose
で起動できるようにする
-
-
Apache
でリバースプロキシできるようにする- リバースプロキシ設定をした設定ファイルを準備
- プロキシサービスを
docker-compose
で起動できるようにする
-
Apache
でSAML認証できるよう設定する- SAML認証モジュールと関連ファイルのインストール
- サービスプロバイダとして鍵・証明書・メタファイルを生成
- SAML認証の設定を設定ファイルに追記
-
keycloak
にサービスプロバイダを追加- サービスプロバイダのメタファイルを登録
- サービスプロバイダへ渡す情報(ユーザーID)の設定
- 起動時の環境設定ファイルを再作成
keycloak
をdocker-compose
で起動できるようにする
1. 参考記事
➡️ DockerでKeycloakをサクッと立ててSAML検証環境を構築する
➡️ KeycloakでSAMLを使ってみる(WordPress編)
➡️ Keycloakの設定をファイルから読み込む
keycloak
を起動する
まずは作業ディレクトリにidp/setup
というサブディレクトリを作成します。
mkdir -p ./idp/setup
作業ディレクトリ直下にdocker-compose.yml
を作成します。
version: "3"
services:
keyclock:
image: jboss/keycloak
container_name: keycloak
environment:
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
ports:
- 18080:8080
volumes:
- './idp/setup:/setup'
M1 Macエラー対応方法
M1 Macで上記 jboss/keycloak
をそのまま使えません。
githubのリポジトリからファイルをcloneして、imageをbuildします。
作業ディレクトリに、keycloak_build.sh
を作ります。
#/bin/zsh
VERSION=14.0.0 # set version here
cd /tmp
git clone https://github.com/keycloak/keycloak-containers.git
cd keycloak-containers/server
git checkout $VERSION
docker build -t "jboss/keycloak:${VERSION}" .
パーミションを変更します。
chmod +x keycloak_build.sh
イメージをビルドします。
./keycloak_build.sh
docker-compose.ymlも作成したイメージのタグ(jboss/keycloak:14.0.0)に書き換えておきます。
作業ディレクトリでコマンドラインより docker-compose up -d
を入力してコンテナを起動します。
コマンドプロンプトが戻ってくると、keycloak
の起動が完了するのを待つために、次のコマンドを入力します。
docker logs -f keycloak
下記のようにポートのリッスン状態になれば起動は完了しています。
10:06:16,719 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 14.0.0 (WildFly Core 15.0.1.Final) started in 10881ms - Started 692 of 977 services (686 services are lazy, passive or on-demand)
10:06:16,720 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:9990/management
10:06:16,720 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:9990
起動を確認後、Ctrl+C
でログ表示を終了します。
起動できているか、以下のURLにブラウザでアクセスします。
http://localhost:18080/auth/admin/
docker-compose.yml で設定したユーザー名とパスワードでログインします。
sample
を作成する
検証用realm 画面左上の「Master」メニュー部分をマウスオーバーすると「Add realm」ボタンが表示されるので、クリックする。
- Name: sample
- Enabled: ON
「create」ボタンをクリックする
docker-compose
で起動時の環境設定ファイルを作成
下記のコマンドをコマンドラインから実行します。
docker exec -it keycloak /opt/jboss/keycloak/bin/standalone.sh \
-Djboss.socket.binding.port-offset=100 \
-Dkeycloak.migration.action=export \
-Dkeycloak.migration.provider=singleFile \
-Dkeycloak.migration.realmName=sample \
-Dkeycloak.migration.usersExportStrategy=REALM_FILE \
-Dkeycloak.migration.file=/setup/sample_realm.json
/setup/sample_realm.json
が環境設定ファイルになります。
下記のようなログが出て止まれば、Ctrl+Cで止めます。
11:34:40,637 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0025: Keycloak 14.0.0 (WildFly Core 15.0.1.Final) started in 6246ms - Started 594 of 872 services (584 services are lazy, passive or on-demand)
11:34:40,639 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0060: Http management interface listening on http://127.0.0.1:10090/management
11:34:40,639 INFO [org.jboss.as] (Controller Boot Thread) WFLYSRV0051: Admin console listening on http://127.0.0.1:10090
下記ディレクトリにsample_realm.json
が生成されていることを確認します。
./idp/setup
keycloak
起動時に設定ファイルから起動できるようにする
一度、keycloak
を止めます。
docker-compose down
docker-compose.yml
を編集して、設定ファイルを読み込んで起動するように変更します。
version: "3"
services:
keyclock:
image: jboss/keycloak:14.0.0
container_name: keycloak
environment:
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
+ KEYCLOAK_IMPORT: /setup/sample_realm.json
ports:
- 18080:8080
volumes:
- './idp/setup:/setup'
再度起動し、sampleレルムが登録されているか確認します。
docker-compose up -d
ログイン後、下記のような画面が表示されます。
sample realm
のメタファイルを取得する
下記エンドポイントにアクセスすることで、keycloak
のメタ情報のXMLが取得できます。
http://localhost:18080/auth/realms/sample/protocol/saml/descriptor
メタファイルを保存する場所を作成します。
mkdir -p ./sp/saml
エンドポイントのリンクを名前をつけて保存する で./sp/saml
に保存します。
この時、ファイル名をidp-metadata.xml
としておきます。
認証サービスを停止する
ここで一旦認証サービスを停止しておきます。
docker-compose down
2. 通知サービス初版を起動できるようにする
今回の通知サービスは、リバースプロキシサーバーでヘッダに追加されたユーザー情報を取得する必要があります。
これを取得するために、SSRモードでgetServerSideProps
を使用します。
というわけで、Next.js
を利用して通知サービスを作っていきます。
create-next-app
で雛形を作成
npx create-next-app notify --typescript
起動確認します。
cd notify
yarn dev
上記URLにアクセスして下図のような画面が出力されればOKです。
ヘッダにユーザー情報があればページに表示するようにする
ページ表示の前に、リクエストヘッダからユーザーID(x-notify-user
)を取り出し、Welcome to の前に表示するようにindex.tsx
を書き換えます。
import type { NextPage } from 'next'
import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
+type Props = {
+ userid?: string
+}
+
+const Home: NextPage<Props> = ({ userid }: Props) => {
-const Home: NextPage = () => {
return (
<div className={styles.container}>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
+ {userid !== 'NO ONE' && <div>Hi, {userid} !</div>}
<h1 className={styles.title}>
Welcome to <a href="https://nextjs.org">Next.js!</a>
</h1>
{/* 中略 */}
</div>
</main>
<footer className={styles.footer}>
{/* 中略 */}
</footer>
</div>
)
}
+export async function getServerSideProps({ req }: any) {
+ const userid: string = req.headers['x-notify-user'] || 'NO ONE'
+ return {
+ props: {
+ userid,
+ },
+ }
+}
+
export default Home
通常のwebブラウザでは確認できませんが、Postman
があれば、リクエストヘッダにX-Notify-User
を設定してGETすれば、セットしたユーザーIDが表示されていることが確認できます。
Ctrl+C
でサービスを停止します。
docker-compose
で起動できるようにする
通知サービスをsrc
ディレクトリを作成し pages
,styles
をその配下に移動する
./notify/src
|--pages
| |--_app.tsx
| |--api
| | |--hello.ts
| |--index.tsx
|--styles
| |--Home.module.css
| |--globals.css
./notify/Dockerfile
を作る
FROM node:16.13.1
WORKDIR /app
COPY package* yarn.lock ./
RUN yarn install
COPY public ./public
COPY src ./src
COPY tsconfig.json .eslint* next* ./
RUN yarn build
ENV HOST 0.0.0.0
EXPOSE 3000
CMD ["yarn", "start"]
docker-compose.yml
に通知サービスを起動する部分を追記する。
version: "3"
services:
keyclock:
image: jboss/keycloak:14.0.0
container_name: keycloak
environment:
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
KEYCLOAK_IMPORT: /setup/sample_realm.json
ports:
- 18080:8080
volumes:
- './idp/setup:/setup'
+
+ notify:
+ image: saml/app:latest
+ build:
+ context: ./notify/
+ dockerfile: Dockerfile
+ container_name: notify
+ expose:
+ - 3000
+ ports:
+ - 3000:3000
+
作業ディレクトリで通知アプリのイメージをビルドします。
cd ..
docker-compose build notify
通知アプリを起動して、動作を確認します。
docker-compose up -d
上記URLにアクセスして動作を確認します。
動作に問題ないことを確認し、コンテナを停止します。
docker-compose down
Apache
でリバースプロキシできるようにする
3. 参考記事
リバースプロキシ設定をした設定ファイルを準備
リバースプロキシに必要な設定を行った設定ファイルを作成します。
./sp/httpd.conf
Listen 80
# 必須モジュール
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authz_core_module modules/mod_authz_core.so
# root で実行しないようにする
LoadModule unixd_module modules/mod_unixd.so
User daemon
Group daemon
# エラーログを stderr に流す
ErrorLog /proc/self/fd/2
# アクセスログを stdout に流す
LoadModule log_config_module modules/mod_log_config.so
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog /proc/self/fd/1 combined
# リバースプロキシの設定
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
ProxyPass ${PROXY_PATH} ${PROXY_URL}
ProxyPassReverse ${PROXY_PATH} {$PROXY_URL}
# リクエストヘッダを追加する
LoadModule headers_module modules/mod_headers.so
RequestHeader add X-Notify-User "test_user"
docker-compose
で起動できるようにする
プロキシサービスをプロキシサービスを./docker-compose.yml
に追加します。
version: "3"
services:
keyclock:
image: jboss/keycloak:14.0.0
container_name: keycloak
environment:
KEYCLOAK_USER: admin
KEYCLOAK_PASSWORD: admin
KEYCLOAK_IMPORT: /setup/sample_realm.json
ports:
- 18080:8080
volumes:
- './idp/setup:/setup'
notify:
image: saml/app:latest
build:
context: ./notify/
dockerfile: Dockerfile
container_name: notify
expose:
- 3000
- ports:
- - 3000:3000
+
+ proxy:
+ image: httpd:2.4
+ container_name: proxy
+ ports:
+ - 9000:80
+ volumes:
+ - ./sp/httpd.conf:/usr/local/apache2/conf/httpd.conf
+ environment:
+ PROXY_PATH: /
+ PROXY_URL: http://notify:3000/
+
ここで一度プロキシが動作するか確認します。
コンテナを起動します。
docker-compose up -d
下記URLにアクセスして、Nextの初期ページでユーザー名が表示されるとOKです。
うまく表示されない場合は、キャッシュをクリアして再読み込みしてみて下さい。
プロキシサーバーで設定したユーザーID(test_user)が通知サービスで取得できていることがわかります。
コンテナを停止します。
docker-compose down
Apache
でSAML認証できるよう設定する
4. ApacheでSAML認証ができるように設定していきます。
参考記事
➡️ mod_auth_mellon を使ってみた
➡️ (SAML第3回) mod_auth_mellonを使った簡易SPの作成
SAML認証モジュールと関連ファイルのインストール
ApacheにSAML認証に必要なモジュールを追加するために、apache:2.4
のイメージをベースに追加していきます。
./sp/Dockerfile
を新規に作成します。
FROM httpd:2.4
RUN apt-get update
RUN apt-get -y install libapache2-mod-auth-mellon openssl
RUN cp /usr/lib/apache2/modules/mod_auth_mellon.so /usr/local/apache2/modules/
./sp/http.conf
にも必要な設定を追記していきます。
SAML認証の設定も必要ですが、まず鍵・証明書・メタファイルを生成してから後で追記することとします。
Listen 80
# 必須モジュール
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authz_core_module modules/mod_authz_core.so
+LoadModule authn_file_module modules/mod_authn_file.so
+LoadModule authn_core_module modules/mod_authn_core.so
+LoadModule authz_host_module modules/mod_authz_host.so
+LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
+LoadModule authz_user_module modules/mod_authz_user.so
+LoadModule auth_basic_module modules/mod_auth_basic.so
# root で実行しないようにする
LoadModule unixd_module modules/mod_unixd.so
User daemon
Group daemon
# エラーログを stderr に流す
ErrorLog /proc/self/fd/2
# アクセスログを stdout に流す
LoadModule log_config_module modules/mod_log_config.so
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog /proc/self/fd/1 combined
# リバースプロキシの設定
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
ProxyPass ${PROXY_PATH} ${PROXY_URL}
ProxyPassReverse ${PROXY_PATH} {$PROXY_URL}
+
+# SAML認証モジュールの設定
+LoadModule auth_mellon_module modules/mod_auth_mellon.so
# リクエストヘッダを追加する
LoadModule headers_module modules/mod_headers.so
RequestHeader add X-Notify-User "test_user"
docker-compose.yml
に、プロキシサーバー内でSAML認証用のファイルを保存する場所をローカルの./sp/saml
と共有するよう設定を追加する。
また、SAML認証モジュールをインストールしたコンテナをビルドするよう変更する。
proxy:
+ image: saml/sp:latest
+ build:
+ context: ./sp/
+ dockerfile: Dockerfile
- image: httpd:2.4
container_name: proxy
ports:
- 9000:80
volumes:
- ./sp/httpd.conf:/usr/local/apache2/conf/httpd.conf
+ - ./sp/saml:/usr/local/apache2/saml
environment:
PROXY_PATH: /
PROXY_URL: http://notify:3000/
プロキシのコンテナイメージを再作成してから、コンテナを起動する。
docker-compose build proxy
docker-compose up -d
サービスプロバイダとして鍵・証明書・メタファイルを生成
プロキシサーバーをSAML認証のSP(Service Provider)にするため下記の項目を決定します。
本記事は検証目的のため、正式なFQDNを使用するのではなく、localhostを使用します。
項目 | 値 | 備考 |
---|---|---|
EntryID | http://localhost:9000 | SAML SPの識別子 |
mellon動作パス | /mellon | SAMLのレスポンスを受け取る |
保護対象パス | / | リバースプロキシを行うパスを設定する |
SPメタデータを作成します。
プロキシサーバーのコンテナに入り、メタデータ生成コマンドを実行します。
docker exec -it proxy bash
コンテナ内で次のコマンドを実行します。
cd /usr/local/apache2/saml
mellon_create_metadata http://localhost:9000 http://localhost:9000/mellon
以下の出力があればOKです。
Output files:
Private key: http_localhost_9000.key
Certificate: http_localhost_9000.cert
Metadata: http_localhost_9000.xml
Host: localhost
Endpoints:
SingleLogoutService: http://localhost:9000/mellon/logout
AssertionConsumerService: http://localhost:9000/mellon/postResponse
コンテナから抜けます。
exit
設定ファイル書き換えのためにコンテナを停止します。
docker-compose down
SAML認証の設定を設定ファイルに追記
先ほど作成した鍵・証明書・認証サービス(IdP)のメタファイルの設定を./sp/http.conf
に対して追記します。
Listen 80
# 必須モジュール
LoadModule mpm_event_module modules/mod_mpm_event.so
LoadModule authz_core_module modules/mod_authz_core.so
LoadModule authn_file_module modules/mod_authn_file.so
LoadModule authn_core_module modules/mod_authn_core.so
LoadModule authz_host_module modules/mod_authz_host.so
LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
LoadModule authz_user_module modules/mod_authz_user.so
LoadModule auth_basic_module modules/mod_auth_basic.so
# root で実行しないようにする
LoadModule unixd_module modules/mod_unixd.so
User daemon
Group daemon
# エラーログを stderr に流す
ErrorLog /proc/self/fd/2
# アクセスログを stdout に流す
LoadModule log_config_module modules/mod_log_config.so
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog /proc/self/fd/1 combined
# リバースプロキシの設定
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so
ProxyPass ${PROXY_PATH} ${PROXY_URL}
ProxyPassReverse ${PROXY_PATH} {$PROXY_URL}
# SAML認証モジュールの設定
LoadModule auth_mellon_module modules/mod_auth_mellon.so
+<Location />
+ MellonEndpointPath "/mellon"
+ MellonIdPMetadataFile /usr/local/apache2/saml/idp-metadata.xml
+ MellonSPPrivateKeyFile /usr/local/apache2/saml/http_localhost_9000.key
+ MellonSPCertFile /usr/local/apache2/saml/http_localhost_9000.cert
+ MellonSPMetadataFile /usr/local/apache2/saml/http_localhost_9000.xml
+
+ AuthType "Mellon"
+ Require valid-user
+ MellonEnable "auth"
+</Location>
+
# リクエストヘッダを追加する
LoadModule headers_module modules/mod_headers.so
+RequestHeader add X-Notify-User %{MELLON_username}e env=MELLON_username
-RequestHeader add X-Notify-User "test_user"
MELLON_以降がKeycloakで設定する属性名になる。
keycloak
にサービスプロバイダを追加
5. keycloakkにサービスプロバイダのメタ情報を登録し、ユーザー登録を行います。
サービスプロバイダのメタファイルを登録
コンテナを起動します。
docker-compose up -d
管理コンソールを起動します。
http://localhost:18080/auth/admin
起動後、左サイドメニューから「Clients」を選択します。
「Clients」画面の右にある「Create」ボタンをクリックして、サービスプロバイダを登録する画面を表示します。
「Select file」ボタンをクリックするとファイル選択のダイアログが表示されるので、4. で生成したサービスプロバイダぐ側のメタファイル(http_localhost_9000.xml
)を指定します。
「Save」ボタンをクリックしてサービスプロバイダを登録します。
「Mapper」タブを選択し、「Create」ボタンをクリックして新しくマッピングを追加します。
- Name : username
- Mapper Type : User Property
- Property : username
- SAML Attribute : username (Apache環境変数のMELLON_以降と一致するように設定する)
- SAML Attribute NameFormat : Basic
「Save」ボタンをクリックして設定を保存します。
サービスプロバイダへ渡す情報(ユーザーID)の設定
管理画面TOPに戻り、左サイドメニューから「Users」を選択し、表示される「Users」画面右の「Add user」ボタンをクリックします。
- Username: ユーザー名(今回はGitHubに接続するのでそのIDを設定)
- Email: 任意で設定します。(今回の検証では使用しません)
- First Name: 任意で設定します。(今回の検証では使用しません)
- Last Name: 任意で設定します。(今回の検証では使用しません)
- User Enabled: ON(初期値です)
「Save」ボタンをクリックして設定を保存します。
「Credentials」タブを選択すると上記表示になるので、パスワードをセットし、
起動時の環境設定ファイルを再作成
docker-composeでコンテナ起動された時に読み込む設定ファイルを更新します。
下記のコマンドをコマンドラインから実行します。
docker exec -it keycloak /opt/jboss/keycloak/bin/standalone.sh \
-Djboss.socket.binding.port-offset=100 \
-Dkeycloak.migration.action=export \
-Dkeycloak.migration.provider=singleFile \
-Dkeycloak.migration.realmName=sample \
-Dkeycloak.migration.usersExportStrategy=REALM_FILE \
-Dkeycloak.migration.file=/setup/sample_realm.json
..... WFLYSRV0051: Admin console listening on http://127.0.0.1:10090
上記表示で出力が止まれば、設定ファイルは作成されているので、Ctrl+Cで止めます。
コンテナを停止〜起動し、設定内容が反映されていることを確認します。
docker-compose down
docker-compose up -d
ketcloakが起動しているか確認します。
docker logs -f keycloak
..... WFLYSRV0051: Admin console listening on http://127.0.0.1:10090
上記表示で出力が止まれば、起動完了していますので、Ctrl+Cで止めます。
管理コンソールを起動します。
http://localhost:18080/auth/admin
Clients メニューから、「http://localhost:9000」が登録されていることを確認します。
「http://localhost:9000」のリンクをクリックして「Mapper」タブを選択し、「username」が登録されていることを確認します。
「username」のリンクをクリックしてユーザー属性情報が登録されていることを確認します。
「Users」メニューから「View all users」ボタンをクリックして、ユーザー登録されているか確認します。
通知サービスのSAML認証確認
最後に、通知サービスでSAML認証の動作確認を行います。
次のような動作になればOKです。
-
http://localhost:9000にアクセス
➡️ keycloakのsampleレルムのログイン画面が表示される
- ユーザー名/パスワードを入力しSign Inボタンをクリックする
➡️ 通知サービスTOPが表示され、ログインしたユーザー名が表示されている
- 同じブラウぜで別タブを開きhttp://localhost:9000にアクセス
➡️ ログイン画面を経由せずに通知サービス画面が表示される
最後に
長い記事に最後までお付き合いいただきましてありがとうございました。
これで、一通りSAML認証が実装された通知サービスの環境構築ができました。
後編では以下のような内容を考えています。
- 通知サービス内でGitHubのAPIにアクセスしてスター数を取得しアイコンにスター数をバッジ表示する
- ポータルサイトのiframeから通知サービスをコールしてバッチ付きのアイコンを表示する
Discussion