認証(Google Workspace の SAML SSO / SMTP の OAuth)
公式マニュアルでは Microsoft 365 向けの手順が中心の、Google Workspace(以下 GWS)との認証連携をまとめます。
- SAML SSO: GWS で「カスタム SAML アプリ」を作り、取得した IdP 情報を
Authentication.jsonに設定します(1.4 系以降が対象)。 - SMTP の OAuth: 1.5.1.0 から SMTP 認証に OAuth 2.0 が使えます。GWS ではサービスアカウントと「ドメイン全体の委任」を組み合わせ、
Mail.jsonを設定します。トークンの取得・キャッシュの仕組みと、Microsoft 365 での設定例も載せています。
INFO
以下の URL・ID・メールアドレスはすべてダミー値です。pleasanter.example.com などは実際の環境の値に置き換えてください。
SAML SSO 1.4 以降
仕組みと設定の全体像
プリザンターが SP(Service Provider)、GWS が IdP(Identity Provider)として動作します。
図を読み込み中…
設定ファイルは Implem.Pleasanter/App_Data/Parameters/Authentication.json です。大きく 4 つのブロックで構成されます。
| ブロック | 設定内容 |
|---|---|
| ルート設定 | Provider を "SAML" にして SAML 認証を有効化 |
SPOptions | プリザンター(SP)自身のエンティティ ID やコールバック URL |
IdentityProviders | GWS(IdP)のエンティティ ID、SSO URL、証明書情報 |
Attributes | SAML 属性とプリザンターのユーザー項目のマッピング |
作業の順番は次のとおりです。
- GWS 管理コンソールでカスタム SAML アプリを作成し、IdP 情報(エンティティ ID・SSO URL・証明書)を取得する
- 取得した IdP 情報を
Authentication.jsonに設定する - プリザンターに設定を反映する(パラメータリロードまたは再起動)
- 動作確認する
GWS 管理コンソールでの設定
1. カスタム SAML アプリの追加
- Google 管理コンソール にログインする
- 「アプリ」>「ウェブアプリとモバイルアプリ」を選択する
- 「アプリを追加」>「カスタム SAML アプリの追加」をクリックする
- アプリ名に「Pleasanter」と入力して「続行」
2. IdP 情報の取得
「Google IdP 情報」画面で次の値を控えます。
| 項目 | 例 | 用途 |
|---|---|---|
| SSO の URL | https://accounts.google.com/o/saml2/idp?idpid=xxxxx | Authentication.json の SignOnUrl |
| エンティティ ID | https://accounts.google.com/o/saml2?idpid=xxxxx | Authentication.json の EntityId(IdP) |
| 証明書 | X.509 証明書(PEM 形式) | プリザンターサーバーの証明書ストアにインポート |
WARNING
証明書は「ダウンロード」ボタンで .pem ファイルとして保存しておきます。プリザンター側の設定で必要になります。
3. サービスプロバイダ(SP)情報の入力
| 項目 | 設定値 | 説明 |
|---|---|---|
| ACS の URL | https://pleasanter.example.com/Saml2/Acs | IdP が SAML レスポンスを POST する URL |
| エンティティ ID | https://pleasanter.example.com/Saml2 | プリザンター(SP)のエンティティ ID |
| 名前 ID の形式 | EMAIL | メールアドレスを NameID として使用 |
ACS の URL は /Saml2/Acs です。プリザンターは Sustainsys.Saml2 の AddSaml2 で SAML を組み込んでおり(Startup.cs)、SetSPOptions では ModulePath を変えていません(Saml.cs)。そのため SAML レスポンスを受け取るのは Sustainsys.Saml2 の既定のモジュールパス /Saml2 配下の /Saml2/Acs です。/Users/SamlLogin は、ACS で認証が済んだ後にリダイレクトされるプリザンターのアクション(UsersController.cs)で、SAML レスポンスを処理しません。ここを ACS の URL に登録すると、ログインが完了しません。
4. 属性マッピング
GWS の標準ディレクトリ属性には「部門名(Department)」はありますが「部門コード」はありません。部門コードなどは カスタム属性 を作って管理します。
カスタム属性は「ディレクトリ」>「ディレクトリ設定」>「カスタム属性」>「カスタム属性を追加」(またはユーザー編集画面の「その他のユーザー情報の追加…」)から作成します。Pleasanter のようなカテゴリにまとめておくと管理しやすくなります。
| カスタム フィールド名 | 情報の種類 | 用途 |
|---|---|---|
DeptCode | テキスト | プリザンターの部門コード |
UserCode | テキスト | プリザンターのユーザーコード |
TenantManager | テキスト | テナント管理者フラグ(true/false) |
いずれも「ユーザーや管理者に表示: はい」「値の数: 1 つの値」で作成します。各ユーザーの値は管理コンソールのユーザー編集画面か、Google Directory API で一括設定できます。
カスタム SAML アプリの属性マッピングには次をすべて設定します。
| Google のディレクトリ属性 | アプリの属性 |
|---|---|
| Primary email | MailAddress |
| First name | FirstName |
| Last name | LastName |
| Department | Dept |
| Pleasanter - DeptCode | DeptCode |
| Pleasanter - UserCode | UserCode |
| Pleasanter - TenantManager | TenantManager |
INFO
カスタム属性は「カテゴリ名 - フィールド名」の形式で表示されます(上の表はカテゴリ名を Pleasanter にした場合)。
5. ユーザーアクセスの有効化
作成したアプリは既定で オフ です。アプリ画面の「ユーザー アクセス」で「オン(すべてのユーザー)」、または「オン(一部の組織部門)」で対象の組織部門を選んで保存します。
証明書のインポート
GWS からダウンロードした .pem をプリザンターのサーバーにインポートし、拇印(Thumbprint)を控えます。
Windows
.pemをダブルクリックして「証明書のインポートウィザード」を開く- 保存場所は
Authentication.jsonのStoreLocationに合わせるCurrentUser: 現在のユーザーLocalMachine: ローカル コンピューター(IIS で動かす場合はこちらを推奨)
- 証明書ストアは「個人」(
My)を選ぶ certlm.mscまたはcertmgr.mscでインポートした証明書の拇印をコピーする
Linux
.NET の X509Store が証明書を検索できる場所にインポートします。CA トラストストアへの追加だけでは不十分なケースがあるため、certutil -user -addstore My でプリザンターの実行ユーザーの個人ストアにもインポートしておくのが確実です。
# Debian / Ubuntu 系
sudo cp GoogleIDPCertificate.pem /usr/local/share/ca-certificates/google-idp.crt
sudo update-ca-certificates
# プリザンターの実行ユーザーで実行
certutil -user -addstore My /usr/local/share/ca-certificates/google-idp.crt# RHEL / CentOS / AlmaLinux / Rocky Linux 系
sudo cp GoogleIDPCertificate.pem /etc/pki/ca-trust/source/anchors/google-idp.pem
sudo update-ca-trust
# プリザンターの実行ユーザーで実行
certutil -user -addstore My /etc/pki/ca-trust/source/anchors/google-idp.pem| 項目 | Debian / Ubuntu | RHEL / CentOS / AlmaLinux |
|---|---|---|
| 証明書の配置先 | /usr/local/share/ca-certificates/ | /etc/pki/ca-trust/source/anchors/ |
| 拡張子 | .crt | .pem |
| 更新コマンド | update-ca-certificates | update-ca-trust |
certutil を含むパッケージ | libnss3-tools | nss-tools |
WARNING
certutil -user -addstore My は Windows の certutil の書式で、Linux の NSS の certutil の書式とは異なります。また .NET の Linux 版の X509Store が NSS のデータベースを参照するかは、プリザンターのソースからは確認できません。プリザンターの実行ユーザーで .NET の X509Store に直接追加する方法を 証明書のインポート にまとめています。
拇印はディストリビューションに関係なく次で確認できます。コロンを除いた文字列を FindValue に設定します。
openssl x509 -in GoogleIDPCertificate.pem -fingerprint -sha1 -noout | sed 's/SHA1 Fingerprint=//;s/://g'Azure App Service
- 対象の App Service の「証明書」メニューで「公開キー証明書 (.cer) の追加」を選び、
.pemをアップロードする - 一覧に表示された拇印をコピーする
- 「構成」→「アプリケーション設定」で
WEBSITE_LOAD_CERTIFICATESにその拇印(すべて読み込む場合は*)を設定する - 保存して App Service を再起動する
WARNING
WEBSITE_LOAD_CERTIFICATES を設定しないと、アップロードした証明書をアプリのプロセスから参照できません。
App Service(Windows)では、読み込まれた証明書は CurrentUser の My ストアに配置されるため、StoreLocation は CurrentUser にします。App Service(Linux)では /var/ssl/certs/<拇印>.der にファイルとして配置されますが、X509Store から使うには同様に WEBSITE_LOAD_CERTIFICATES の設定が必要です。
Authentication.json
{
"Provider": "SAML",
"SamlParameters": {
"Attributes": {
"FirstName": "FirstName",
"LastName": "LastName",
"DeptCode": "DeptCode",
"Dept": "Dept",
"UserCode": "UserCode",
"TenantManager": "TenantManager",
"MailAddress": "{NameId}"
},
"SamlTenantId": 1,
"DisableOverwriteName": false,
"SPOptions": {
"EntityId": "https://pleasanter.example.com/Saml2",
"ReturnUrl": "https://pleasanter.example.com/Users/SamlLogin",
"AuthenticateRequestSigningBehavior": "IfIdpWantAuthnRequestsSigned",
"OutboundSigningAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
"MinIncomingSigningAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
},
"IdentityProviders": [
{
"EntityId": "https://accounts.google.com/o/saml2?idpid=xxxxx",
"SignOnUrl": "https://accounts.google.com/o/saml2/idp?idpid=xxxxx",
"LogoutUrl": null,
"AllowUnsolicitedAuthnResponse": true,
"Binding": "HttpPost",
"WantAuthnRequestsSigned": false,
"DisableOutboundLogoutRequests": true,
"LoadMetadata": false,
"SigningCertificate": {
"StoreName": "My",
"StoreLocation": "LocalMachine",
"X509FindType": "FindByThumbprint",
"FindValue": "A1B2C3D4E5F6..."
}
}
]
}
}Provider
| 設定値 | 説明 |
|---|---|
null | 独自認証(既定) |
"SAML" | SAML 認証を有効化(シングルテナント) |
"SAML-MultiTenant" | SAML 認証を有効化(マルチテナント) |
SPOptions / IdentityProviders
| 項目 | 設定値 |
|---|---|
SPOptions.EntityId | GWS の SP 情報で入力したエンティティ ID(https://<ドメイン>/Saml2)と一致させる |
SPOptions.ReturnUrl | https://<ドメイン>/Users/SamlLogin。ACS(/Saml2/Acs)で認証した後の戻り先で、IdP 起点のログインなど戻り先の指定が無いときに使われる。GWS の ACS の URL とは別の値にする |
IdentityProviders.EntityId | GWS の「エンティティ ID」 |
IdentityProviders.SignOnUrl | GWS の「SSO の URL」 |
FindValue | インポートした証明書の拇印 |
StoreLocation | 証明書をインポートした場所(CurrentUser / LocalMachine) |
OutboundSigningAlgorithm と MinIncomingSigningAlgorithm には、XML 署名のアルゴリズム識別子 http://www.w3.org/2001/04/xmldsig-more#rsa-sha256(http)を書きます。アクセス先の URL ではなく識別子なので https に変えません。プリザンター同梱の Authentication.json の既定値も http です(Authentication.json)。
属性(Attributes)の仕様
指定できる属性
| 属性キー | マッピング先 | 値の型 | 説明 |
|---|---|---|---|
MailAddress | MailAddresses テーブル | 文字列 | メールアドレス。{NameId} で NameID を使用可 |
Name | Users.Name | 文字列 | 表示名(フルネーム)。FirstName/LastName より優先 |
FirstName | Users.FirstName | 文字列 | 名。Name 未設定時に使用 |
LastName | Users.LastName | 文字列 | 姓。Name 未設定時に使用 |
FirstAndLastNameOrder | Users.FirstAndLastNameOrder | 数値 | 姓名の結合順。1 = 名→姓、2 = 姓→名 |
UserCode | Users.UserCode | 文字列 | ユーザーコード |
Birthday | Users.Birthday | 日付 | 誕生日(パース可能な日付文字列) |
Gender | Users.Gender | 文字列 | 性別 |
Language | Users.Language | 文字列 | 言語コード(ja、en など) |
TimeZone | Users.TimeZone | 文字列 | タイムゾーン(Asia/Tokyo など) |
TenantManager | Users.TenantManager | 真偽値 | テナント管理者フラグ。true/false(大文字小文字不問) |
DeptCode | Depts.DeptCode | 文字列 | 部門コード。Dept とセットで指定 |
Dept | Depts.DeptName | 文字列 | 部門名。DeptCode とセットで指定 |
Body | Users.Body | 文字列 | 備考 |
MailAddress の指定方法は 3 通りです。
| 指定方法 | 例 | 動作 |
|---|---|---|
{NameId} | "MailAddress": "{NameId}" | SAML の NameID をメールアドレスとして使用 |
| クレーム名 | "MailAddress": "mail" | 指定クレームの値を使用 |
| フォールバック | "MailAddress": "mail|{NameId}" | 指定クレームを探し、なければ NameID を使用 |
GWS で名前 ID の形式を EMAIL にしていれば、{NameId} にはユーザーのメールアドレスが入ります。
表示名の決まり方
| 優先度 | 条件 | 表示名 |
|---|---|---|
| 1 | Name が設定されている | Name の値 |
| 2 | FirstName と LastName の両方 | FirstAndLastNameOrder に従って結合 |
| 3 | LastName のみ | LastName の値 |
| 4 | FirstName のみ | FirstName の値 |
FirstAndLastNameOrder の既定は姓→名です。DisableOverwriteName を true にすると、既存ユーザーの名前は SAML ログイン時に上書きされず、新規作成時のみ反映されます。
IdP が属性を送らなかったとき
| 属性の状態 | 既存ユーザー | 新規ユーザー |
|---|---|---|
| IdP から値が送信された | IdP の値で上書き | IdP の値で設定 |
| IdP から値が送信されなかった | 現在の値を保持 | DB の既定値 |
Attributes に定義していない | 変更なし | DB の既定値 |
属性に既定値を持たせたいとき
Attributes の値は IdP のクレーム名として解釈されるため、"Language": "ja" と書いても「ja というクレーム」を探すだけで、固定値にはなりません。
| 方法 | 内容 | 向いているケース |
|---|---|---|
| IdP 側で設定 | GWS のカスタム属性(Language、TimeZone など)に全ユーザー分の値を入れ、属性マッピングに追加する | 全ユーザーに同じ値を強制したい(IdP が正) |
Attributes から除外 | 定義自体を削除する。SAML ログイン時に更新されず、プリザンター側の値がそのまま使われる | プリザンター側でユーザーごとに管理したい |
属性ごとに組み合わせることもできます。GWS 側の一括設定は Admin SDK の Directory API などを使います。
# gcloud CLI でカスタム属性を設定する例
gcloud identity users update user@example.com \
--custom-schemas='Pleasanter={Language:ja,TimeZone:Asia/Tokyo}'Service.json の既定値は使われない
Service.json の DefaultLanguage・TimeZoneDefault やテナント設定の言語・タイムゾーンは、SAML によるユーザー新規作成時には参照されません。SAML のユーザー作成処理は通常のユーザー作成パス(Controller → Model)を通らず、Saml.cs 内で直接 SQL を発行しているためです。言語やタイムゾーンを確実に設定するには、IdP 側から属性として送信します。
IdP から送られなかった列には DB の既定値が入ります。1.5.8.1 の列定義では Users.Language の既定値が ja、Users.TimeZone の既定値が Asia/Tokyo です(Users_Language.json、Users_TimeZone.json)。日本語・日本時間の環境ならそのままで困りません。
別の値にそろえたい場合は、SAML ユーザー作成後に SQL で更新します(手動実行、または定期ジョブ化が必要です)。新規作成直後のユーザーは空文字ではなく既定値(ja / Asia/Tokyo)を持つので、"Language" = '' のような条件では該当しません。次は既定値のままのユーザーを英語・UTC にする例です。
-- 既定値のままのユーザーの言語・タイムゾーンを一括設定
UPDATE [Users]
SET
[Language] = 'en',
[TimeZone] = 'UTC'
WHERE [TenantId] = 1
AND [Language] = 'ja'
AND [TimeZone] = 'Asia/Tokyo'
AND [TenantManager] = 0;-- 既定値のままのユーザーの言語・タイムゾーンを一括設定
UPDATE "Users"
SET
"Language" = 'en',
"TimeZone" = 'UTC'
WHERE "TenantId" = 1
AND "Language" = 'ja'
AND "TimeZone" = 'Asia/Tokyo'
AND "TenantManager" = false;-- 既定値のままのユーザーの言語・タイムゾーンを一括設定
UPDATE `Users`
SET
`Language` = 'en',
`TimeZone` = 'UTC'
WHERE `TenantId` = 1
AND `Language` = 'ja'
AND `TimeZone` = 'Asia/Tokyo'
AND `TenantManager` = 0;部門・グループ・同期タイミング
部門(Depts)の自動管理
SAML ログイン時に DeptCode と Dept の 両方 が送られてくると、次の処理が自動で行われます。
DeptCodeに一致する部門がなければ新規作成する- あれば部門名を
Deptの値で上書きする - ユーザーをその部門に所属させる
図を読み込み中…
Dept だけで DeptCode を省略した場合、部門の自動管理は行われません。
WARNING
DeptCode を空文字列で送ると、ユーザーの部門所属が 解除 されます(DeptId = 0)。GWS 側でカスタム属性を空にした場合にこの動作になります。
グループは自動管理されない
SAML 認証では グループ(Groups)の自動管理には対応していません(LDAP 連携ではグループ同期がサポートされています)。管理画面で手動管理する、API でスクリプトから一括管理する、または権限管理の単位を部門にまとめて DeptCode で自動管理する、のいずれかで対応します。
同期はログイン時のみ
| 認証方式 | 同期タイミング | 同期対象 | 設定 |
|---|---|---|---|
| LDAP | 定期実行(BackgroundService) | ディレクトリ内の全ユーザー | BackgroundService.json の SyncByLdap: true |
| SAML | ログイン時のみ | ログインした個別ユーザー | なし(自動) |
GWS 側で部門や名前を変更しても、そのユーザーが次にログインするまでプリザンターには反映されません。
退職者の扱い
GWS 側でユーザーを無効化すれば SAML ログインはできなくなりますが、プリザンター側のユーザーは有効なまま残ります。プリザンター側でもユーザーを無効化する運用(管理画面・API による手動またはスクリプト対応)が必要です。
Secure LDAP の併用
認証は SAML、ユーザー同期は GWS の Secure LDAP(LDAPS)という構成も選べます。
図を読み込み中…
| メリット | デメリット |
|---|---|
SyncByLdap で全ユーザーの属性を定期的に同期できる | GWS で Secure LDAP を有効化し、クライアント証明書を発行する必要がある |
| GWS で無効化したユーザーを LDAP 同期で無効化できる | Secure LDAP は Business Plus / Enterprise / Education Fundamentals 以上のエディションのみ |
| ユーザーがログインしなくても属性変更が反映される | Authentication.json と BackgroundService.json の両方を管理する必要がある |
| SSO・多要素認証は SAML 側でそのまま使える | SAML と LDAP で属性マッピングを二重管理する。サーバーから ldap.google.com:636 への通信が必要 |
{
"SyncByLdap": true,
"SyncByLdapTime": [ "02:00", "14:00" ]
}{
"Provider": "SAML",
"LdapParameters": [
{
"LdapSearchRoot": "LDAP://ldap.google.com:636/dc=example,dc=com",
"LdapSearchProperty": "mail",
"LdapTenantId": 1,
"LdapMailAddress": "mail",
"LdapFirstName": "givenName",
"LdapLastName": "sn"
}
],
"SamlParameters": {
...
}
}WARNING
併用する場合も Provider は "SAML" のままにします。LdapParameters は SyncByLdap によるバックグラウンド同期専用で、認証方式には影響しません。
設定の反映と動作確認
SAML の設定は、プリザンターを再起動(IIS はアプリケーションプールのリサイクル、Linux は sudo systemctl restart pleasanter など)して反映します。
/admins/reloadparameters(パラメータ)では反映されません。確認したソースでは、Provider が SAML かどうかで認証スキームを登録するかを Startup.ConfigureServices で決め、SPOptions と IdentityProviders もそのとき Sustainsys.Saml2 の設定に読み込みます(Startup.cs)。パラメータのリロードは JSON を読み直すだけで、この登録はやり直さないためです。
反映後、ログイン画面に通常のログインフォームに加えて 「SSO ログイン」ボタン が表示されます。ボタンから Google のログイン画面に遷移し、ログインするとトップページに戻ります。初回ログイン時は Attributes のマッピングに従ってユーザーが自動作成されます。
INFO
SAML を有効にしても IdP への自動リダイレクトは行われません。ID / パスワード欄は残り、ローカル認証も引き続き使えます。
SAML ユーザーとローカルユーザーの混在
Provider が "SAML" でも、SSO ログイン(IdP で認証)とローカルログイン(プリザンター DB のパスワードハッシュで認証)は併用できます。どちらでログインしても操作・権限は同じです。段階的な SSO 移行、社内は SSO・外部パートナーはローカル認証、IdP 障害時の管理者ログインといった使い方ができます。
移行完了後にローカルログインを制限したい場合は、テナントの契約設定で AllowOriginalLogin を 0 にします。テナント管理者以外 はローカルログインできなくなります。
INFO
テナント管理者(TenantManager = true)はこの制限の対象外です。IdP 障害時にも管理者がログインして運用を継続できるようにするための設計です。
ローカルログインはログインフォームの流れ(ロックアウト・2 段階認証・パスワード有効期限のチェック)を通りますが、SSO ログインは SAML の応答を受けたところで直接 Cookie を発行するため、プリザンター側の 2 段階認証は求められません。詳しくは 認証方式の内部動作 を見てください。
既存ユーザーを SSO に移行する
SAML ログイン時は、IdP から返る NameID を LoginId としてユーザーを照合 します。
図を読み込み中…
したがって Google のメールアドレスと LoginId が一致していれば既存ユーザーがそのまま引き継がれます。
| 既存の LoginId | Google メールアドレス | 結果 |
|---|---|---|
taro@example.com | taro@example.com | 既存ユーザーを継続利用(作業不要) |
taro | taro@example.com | そのままだと新規ユーザーが作成される |
LoginId がメールアドレスと異なる場合は、SAML 切り替え 前 に LoginId をメールアドレスに変更し、全ユーザーの変更が終わってから Provider を "SAML" にします。LoginId を変えると変更後の ID でしかログインできなくなるため、切り替え直前に一括で変更するのがおすすめです。
ユーザー数が多い場合は SQL で一括更新できます。
DANGER
SQL を直接実行する場合は、必ず事前にバックアップを取ってから行ってください。
-- LoginId をメールアドレスに一括更新する例
UPDATE u
SET u.[LoginId] = ma.[MailAddress]
FROM [Users] u
INNER JOIN [MailAddresses] ma ON u.[UserId] = ma.[OwnerId]
WHERE ma.[OwnerType] = 'Users'
AND u.[LoginId] <> ma.[MailAddress]
AND u.[TenantId] = 1;-- LoginId をメールアドレスに一括更新する例
UPDATE "Users"
SET "LoginId" = ma."MailAddress"
FROM (
SELECT "OwnerId", "MailAddress"
FROM "MailAddresses"
WHERE "OwnerType" = 'Users'
) ma
WHERE "Users"."UserId" = ma."OwnerId"
AND "Users"."LoginId" <> ma."MailAddress"
AND "Users"."TenantId" = 1;-- LoginId をメールアドレスに一括更新する例
UPDATE `Users` u
INNER JOIN `MailAddresses` ma ON u.`UserId` = ma.`OwnerId`
SET u.`LoginId` = ma.`MailAddress`
WHERE ma.`OwnerType` = 'Users'
AND u.`LoginId` <> ma.`MailAddress`
AND u.`TenantId` = 1;推奨手順は次のとおりです。
- 事前準備: 全ユーザーの LoginId と Google メールアドレスの対応一覧を作り、DB をバックアップする
- LoginId の変更(必要な場合): 変更後、独自認証でログインできることを確認する
- SAML の有効化:
Authentication.jsonを編集して反映する - 動作確認: テストユーザーで SAML ログインし、既存データ(レコード・権限設定など)が引き継がれていることを確認する
- 全ユーザーへの展開
未登録ユーザーを拒否する
{
"Provider": "SAML",
"RejectUnregisteredUser": true,
"SamlParameters": {
...
}
}| 設定値 | 動作 |
|---|---|
false(既定) | 未登録ユーザーは SAML ログイン時に自動作成される |
true | 事前に登録済みのユーザーのみ SAML ログインできる |
ログイン画面で SSO ボタンを目立たせる
SSO を有効にしたログイン画面は ID / パスワード欄が主役のままなので、拡張スタイルと拡張スクリプトで ID / パスワード欄を折りたたみ、SSO ボタンを上に出せます。IdP の種類に関係なく使えます。
/* ログインページのみに適用(body#login で限定) */
/* SSO ログインボタンをフォーム上部に移動して強調 */
body#login #SsoLogin {
order: -1;
margin-bottom: 24px;
padding: 16px;
background-color: #f0f7ff;
border: 1px solid #4285f4;
border-radius: 8px;
}
body#login #SsoLogin .ssoLoginMessage {
font-size: 1.1em;
font-weight: bold;
color: #1a73e8;
}
/* ログインエリアを flexbox で順序制御 */
body#login #Logins {
display: flex;
flex-direction: column;
}
/* ID/パスワード欄・ログインボタンを初期状態で折りたたみ */
body#login #login-fields-body,
body#login #LoginCommands {
max-height: 0;
overflow: hidden;
opacity: 0;
transition: max-height 0.3s ease, opacity 0.3s ease, margin 0.3s ease;
margin: 0;
}
/* 展開時のスタイル */
body#login #login-fields-body.expanded,
body#login #LoginCommands.expanded {
max-height: 300px;
opacity: 1;
margin-top: 8px;
}
/* 折りたたみトグルボタン */
body#login #LocalLoginToggle {
cursor: pointer;
color: #666;
font-size: 0.9em;
text-align: center;
padding: 8px;
margin-top: 16px;
border: 1px solid #ddd;
border-radius: 4px;
background: #fafafa;
order: 1;
}
body#login #LocalLoginToggle:hover {
background: #f0f0f0;
color: #333;
}
body#login #LocalLoginToggle::before {
content: "▶ ";
}
body#login #LocalLoginToggle.expanded::before {
content: "▼ ";
}// ログインページのみで実行
if (document.body.id === 'login') {
$(function () {
var $logins = $('#Logins');
var $fields = $('#login-fields-body');
var $commands = $('#LoginCommands');
// 折りたたみトグルボタンを挿入
var $toggle = $('<div>', {
id: 'LocalLoginToggle',
text: 'ID / パスワードでログイン'
});
// SSO ボタンの後に配置
var $sso = $('#SsoLogin');
if ($sso.length) {
$sso.after($toggle);
} else {
// SSO ボタンがない場合はフォームを折りたたまない
return;
}
// トグルボタンのクリックイベント
$toggle.on('click', function () {
var isExpanded = $fields.hasClass('expanded');
$fields.toggleClass('expanded');
$commands.toggleClass('expanded');
$toggle.toggleClass('expanded');
$toggle.text(isExpanded
? 'ID / パスワードでログイン'
: 'ID / パスワードでログイン');
// 展開時に LoginId にフォーカス
if (!isExpanded) {
setTimeout(function () {
$('#Users_LoginId').focus();
}, 300);
}
});
});
}ファイルを配置してパラメータリロードまたは再起動すると、SSO ボタンが上部に表示され、「ID / パスワードでログイン」をクリックしたときだけ入力欄が展開されます。

INFO
ファイルベースの拡張スタイル・拡張スクリプトは全ページに読み込まれますが、body#login セレクタと document.body.id === 'login' の条件によりログインページ以外では何もしません。
トラブルシューティング
| 症状 | 考えられる原因 | 対処法 |
|---|---|---|
| ログイン画面で 500 エラー | Authentication.json の JSON 構文エラー | カンマ漏れ・括弧の不一致などを確認 |
| Google ログイン後にエラー画面 | ACS URL またはエンティティ ID の不一致 | GWS の ACS の URL を https://<ドメイン>/Saml2/Acs、エンティティ ID を SPOptions.EntityId と同じ値にする |
| 「証明書が見つからない」エラー | 証明書のインポート先が異なる | StoreLocation・StoreName(My)をインポート先と一致させる |
| 「拇印が一致しない」エラー | 拇印のコピーミス | スペースやコロンを除いた文字列にする |
| SAML ログインできるが新規ユーザーになる | NameID と既存 LoginId の不一致 | 既存ユーザーの LoginId を Google メールアドレスに変更 |
| 未登録ユーザーがログインできない | RejectUnregisteredUser が true | 事前にユーザー登録するか false にする |
SMTP 認証に GWS の OAuth を使う 1.5.1.0 以降
1.5.1.0 から、メール送信(SMTP)の認証方式として OAuth 2.0 がサポートされ、アプリパスワードを使わずに送信できるようになりました。GWS ではユーザーの介在なしに送信するため、サービスアカウント と ドメイン全体の委任(Domain-wide Delegation) を組み合わせます。
1. Google Cloud プロジェクトの準備
- Google Cloud Console で「新しいプロジェクト」を作成する(名前は
Pleasanter-SMTP-Clientなど) - 「APIとサービス」>「ライブラリ」で「Gmail API」を検索して有効にする
2. 認証情報の作成
- OAuth 同意画面: 「APIとサービス」>「OAuth同意画面」で、ユーザータイプ「内部」を選んで必須項目を埋める
- サービスアカウント: 「認証情報」>「認証情報を作成」>「サービスアカウント」で作成(例:
pleasanter-smtp)し、詳細画面の「キー」タブから JSON の鍵を作成して保存する - OAuth 2.0 クライアント ID: 「認証情報を作成」>「OAuth クライアント ID」で種類「ウェブ アプリケーション」を選び、表示された クライアント ID と クライアントシークレット を控える(プリザンターの設定に使います)
INFO
サービスアカウントの JSON に含まれる client_id と private_key はこの後の設定では使いませんが、大切に保管してください。
3. ドメイン全体の委任
- GWS 管理コンソールで「セキュリティ」>「アクセスとデータ管理」>「APIの制御」を開く
- 「ドメイン全体の委任」で「新しく追加」する
- 次を入力する
- クライアント ID: サービスアカウントのクライアント ID(OAuth 2.0 クライアント ID ではない点に注意)
- OAuth スコープ:
https://mail.google.com/
4. Mail.json の設定
{
"SmtpHost": "smtp.gmail.com",
"SmtpPort": 587,
"SmtpUserName": "sender@example.com",
"SmtpPassword": "",
"SmtpEnableSsl": true,
"SecureSocketOptions": "StartTls",
"UseOAuth": true,
"OAuthClientId": "xxxxxxxxxxxx.apps.googleusercontent.com",
"OAuthClientSecret": "your-client-secret-value",
"OAuthScope": "https://mail.google.com/",
"OAuthGrantType": "client_credentials",
"OAuthTokenEndpoint": "https://oauth2.googleapis.com/token",
"OAuthDefaultExpiresIn": 3600,
"OAuthTokenRefreshBufferTime": 300,
"Encoding": "UTF-8",
"FixedFrom": "sender@example.com",
"SupportFrom": "sender@example.com"
}| パラメータ名 | 設定値のポイント |
|---|---|
SmtpUserName | 実際に送信元となるメールアドレス |
UseOAuth | true |
OAuthClientId | 手順 2 で作成した「ウェブ アプリケーション」のクライアント ID |
OAuthClientSecret | 手順 2 で作成したクライアントシークレット |
OAuthGrantType | client_credentials |
確認したソースでは、プリザンターはトークン取得時に OAuthTokenEndpoint へ client_id・client_secret・scope・grant_type の 4 項目を POST するだけで、サービスアカウントの鍵(JSON)は使いません(Smtp.cs)。取得したトークンは SmtpUserName と組にして SMTP の OAuth2 認証に使われます(Smtp.cs)。この要求を Google のトークンエンドポイントが受け付けるかは Google 側の仕様で、ソースからは確認できません。
設定後、プリザンターを再起動(IIS の再起動など)してテストメールを送信します。
OAuth 送信の仕組み
UseOAuth が true のとき、SMTP サーバーに接続したあと、アクセストークンを取得して MailKit の SaslMechanismOAuth2(XOAUTH2)で認証します。このとき SmtpUserName と SmtpPassword による通常の認証は行いません(Smtp.cs#L199-L213)。
図を読み込み中…
| パラメータ | 既定値 | 内容 |
|---|---|---|
UseOAuth | false | OAuth 2.0 で認証する |
OAuthClientId | null | クライアント ID |
OAuthClientSecret | null | クライアントシークレット |
OAuthScope | null | トークン取得時の scope |
OAuthGrantType | null | トークン取得時の grant_type(通常 client_credentials) |
OAuthTokenEndpoint | null | トークンエンドポイントの URL |
OAuthDefaultExpiresIn | 3600 | 応答に expires_in が無いときの有効期間(秒) |
OAuthTokenRefreshBufferTime | 300 | 期限のこの秒数前になったら取り直す |
- トークンのキャッシュ: 取得したトークンは
Sessionsテーブルに、セッション GUIDSmtpOAuthToken・キーOAuthToken:{OAuthClientId}で保存されます(Smtp.cs#L222-L251)。DB に保存されるので、複数台のサーバーで同じトークンを使い回せます。 - パラメータの検証: トークンを取りに行く前に
OAuthClientId・OAuthClientSecret・OAuthScope・OAuthGrantType・OAuthTokenEndpointが空でないか、OAuthTokenEndpointが http / https の絶対 URL かを確かめ、足りなければOAuth parameters are not configured: …の例外になります(Smtp.cs#L323-L359)。 - 送る内容は 4 項目だけ: POST するのは
client_id・client_secret・scope・grant_typeのフォームだけです。OAuthGrantTypeは自由に書けますが、JWT のアサーションなど他の項目は送れないため、実質的にクライアント資格情報(client credentials)方式だけに対応します。 - エラーの記録: 送信処理の例外は画面には出ず、SysLogs に記録されます。トークン取得の失敗は
OAuth token acquisition failed. Status: …、応答の JSON が読めないときはOAuth response JSON parsing failed、access_tokenが無いときはOAuth response validation failed(例外メッセージaccess_token not found in OAuth response)が付きます。 - シークレットを環境変数で渡す:
OAuthClientIdとOAuthClientSecretは、Mail.jsonの値が空なら環境変数{EnvironmentName}_Mail_OAuthClientId→{ServiceName}_Mail_OAuthClientId(シークレットも同様に_Mail_OAuthClientSecret)の順に読まれます(Initializer.cs#L224-L231)。Mail.jsonにシークレットを書かずに済みます。
Microsoft 365(Exchange Online)の場合
Entra ID にアプリを登録し、クライアント資格情報でトークンを取ります。
- Entra ID の「アプリの登録」で新規登録する(名前は
Pleasanter-SMTPなど) - 「API のアクセス許可」でアプリケーションの許可を追加し、管理者の同意を与える
- 「証明書とシークレット」でクライアントシークレットを作り、値を控える
- 概要ページの アプリケーション(クライアント)ID と ディレクトリ(テナント)ID を控える
- Exchange 管理センターで、送信に使うメールボックスの SMTP 認証を有効にする
{
"SmtpHost": "smtp.office365.com",
"SmtpPort": 587,
"SmtpUserName": "noreply@example.com",
"SmtpEnableSsl": true,
"SecureSocketOptions": "StartTls",
"UseOAuth": true,
"OAuthClientId": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
"OAuthClientSecret": "your-client-secret",
"OAuthScope": "https://outlook.office365.com/.default",
"OAuthGrantType": "client_credentials",
"OAuthTokenEndpoint": "https://login.microsoftonline.com/<テナント ID>/oauth2/v2.0/token",
"OAuthDefaultExpiresIn": 3600,
"OAuthTokenRefreshBufferTime": 300,
"FixedFrom": "\"Pleasanter\" <noreply@example.com>"
}OAuthScopeはhttps://outlook.office365.com/.defaultです(https://graph.microsoft.com/.defaultではありません)。SmtpUserNameには実際に送信するメールボックスのアドレスを入れます。送信元にできるメールボックスを絞るには Exchange Online 側のアクセスポリシーを使います。- 必要なアクセス許可の種類やメールボックス側の登録など、Entra ID・Exchange Online 側の設定の詳細は Microsoft のドキュメントに従ってください(ここに書いた手順以上のことはプリザンターのソースからは確認できません)。
OAuth を使わない方法(GWS の SMTP リレー)
GWS では、管理コンソールで SMTP リレーサービスを設定してプリザンターのサーバーの IP アドレスを許可すれば、IP アドレスで認証して送れます。この場合は UseOAuth を false にし、SmtpUserName / SmtpPassword を空にします(両方が入っているときだけ通常の認証を行います)。
{
"SmtpHost": "smtp-relay.gmail.com",
"SmtpPort": 587,
"SmtpEnableSsl": true,
"SecureSocketOptions": "StartTls",
"UseOAuth": false,
"FixedFrom": "\"Pleasanter\" <noreply@example.com>"
}よくあるエラー
| エラー | 確認すること |
|---|---|
OAuth token acquisition failed | クライアント ID / シークレットが正しいか。OAuth スコープに https://mail.google.com/ が正しく入っているか |
Authentication failed | SmtpUserName がドメイン全体の委任で許可したドメイン内のアドレスか。委任の設定は反映まで数分かかる場合がある。M365 では SMTP 認証の有効化とアクセス許可の同意を確認 |
OAuth parameters are not configured | OAuthClientId・OAuthClientSecret・OAuthScope・OAuthGrantType・OAuthTokenEndpoint がすべて入っているか。エンドポイントが https:// から始まる URL か |
access_token not found in OAuth response | トークンエンドポイントの URL、クライアント資格情報が正しいか |