`_\ を参照されたい。
* メール本文のテンプレートファイルを作成する。
**テンプレートファイルの設定例**
.. code-block:: text
<#escape x as x?html> <#-- (1) -->
Hi ${userName}, welcome to TERASOLUNA.ORG!
<#-- (2) -->
If you were not an intended recipient, Please notify the sender.
#escape>
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | XSS攻撃への対策としてHTMLエスケープを行うように設定している。
* - | (2)
- | データモデルに設定された\ ``userName``\ の値を埋め込む。
.. note::
テンプレート言語(FTL)の詳細については、\ `FreeMarker Manual (Template Language Reference) `_\ を参照されたい。
* テンプレートを使用してメール本文を生成し、メール送信する。
**Javaクラスの実装例**
.. code-block:: java
@Inject
JavaMailSender mailSender;
@Inject
Configuration freemarkerConfiguration; // (1)
public void register(User user) {
// omitted
mailSender.send(new MimeMessagePreparator() {
@Override
public void prepare(MimeMessage mimeMessage) throws Exception {
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage,
StandardCharsets.UTF_8.name());
helper.setFrom("EXAMPLE.COM ");
helper.setTo(user.getEmailAddress());
helper.setSubject("Registration confirmation.");
Template template = freemarkerConfiguration
.getTemplate("registration-confirmation.ftl"); // (2)
String text = FreeMarkerTemplateUtils
.processTemplateIntoString(template, user); // (3)
helper.setText(text, true);
}
});
// omitted
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ `Configuration `_\ をインジェクションする。
* - | (2)
- | \ ``Configuration``\ の\ ``getTemplate``\ メソッドを利用して\ `Template `_\ を取得する。
| この例では、テンプレートファイルとして"registration-confirmation.ftl"を指定している。
* - | (3)
- | 取得した\ ``Template``\ をもとに、\ ``org.springframework.ui.freemarker.FreeMarkerTemplateUtils``\ の\ ``processTemplateIntoString``\ メソッドを利用してテンプレートから文字列を生成する。
| この例では、データモデルとして\ ``userName``\ プロパティを持つ\ ``User``\ オブジェクト(JavaBeans)を指定している。
これにより、テンプレートファイルの\ ``${userName}``\ の箇所に\ ``userName``\ プロパティの値が埋め込まれる。
|
Appendix
--------------------------------------------------------------------------------
.. _email-iso-2022-jp:
ISO-2022-JPのエンコードについての考慮
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
日本語のメールを送信する際、送信したメールを受信するメールクライアントを限定できない場合は、エンコードにISO-2022-JPを利用することを検討する必要がある。
この理由としては、レガシーなメールクライアントがUTF-8に対応していない場合を考慮するためである。
MS932で入力された文字列に対し、エンコードにISO-2022-JPをはじめとするJIS X 0208の文字集合をベースとしたエンコードを設定した場合、
以下の表に記載する7文字において文字化けが発生する。
.. tabularcolumns:: |p{0.20\linewidth}|p{0.10\linewidth}|p{0.15\linewidth}|p{0.15\linewidth}|p{0.15\linewidth}|p{0.20\linewidth}|
.. list-table::
:header-rows: 2
:widths: 20 15 15 15 15 20
* - 変換前
-
-
- 変換後
-
-
* - | MS932
| 入力文字
- | 入力値
| (SJIS)
- | Unicode
| (UTF-16)
- | Unicode
| (UTF-16)
- | ISO-2022-JP
| (JIS)
- | JIS X 0208
| 代替文字
* - | ―(全角ハイフン)
- | 815D
- | U+2015
- | U+2014
- | 213E
- | —(EM ダッシュ)
* - | -(ハイフンマイナス)
- | 817C
- | U+FF0D
- | U+2212
- | 215D
- | −(全角マイナス)
* - | ~(全角チルド)
- | 8160
- | U+FF5E
- | U+301C
- | 2141
- | 〜(波ダッシュ)
* - | ∥(平行記号)
- | 8161
- | U+2225
- | U+2016
- | 2142
- | ‖(双柱)
* - | ¢(全角セント記号)
- | 8191
- | U+FFE0
- | U+00A2
- | 2171
- | ¢(セント記号)
* - | £(全角ポンド記号)
- | 8192
- | U+FFE1
- | U+00A3
- | 2172
- | £(ポンド記号)
* - | ¬(全角否定記号)
- | 81CA
- | U+FFE2
- | U+00AC
- | 224C
- | ¬(否定記号)
この問題は、Unicodeを介して文字コード変換を行う際に、MS932に有りJIS X 0208に無い文字が存在するためであり、
文字化けを回避するためには、文字化けする文字について代替文字に文字コードを置き換えるなどの対処を行う必要がある。
なお、後述するx-windows-iso2022jpを使用する場合、変換処理は不要である。
以下に、変換処理の実装例を示す。
.. code-block:: java
public static String convertISO2022JPCharacters(String targetStr) {
if (targetStr == null) {
return null;
}
char[] ch = targetStr.toCharArray();
for (int i = 0; i < ch.length; i++) {
// @formatter:off
ch[i] = switch (ch[i]) {
case '\u2015' -> '\u2014'; // '―'(全角ハイフン) -> '—'(EM ダッシュ)
case '\uff0d' -> '\u2212'; // '-'(ハイフンマイナス) -> '−'(全角マイナス)
case '\uff5e' -> '\u301c'; // '~'(全角チルド) -> '〜'(波ダッシュ)
case '\u2225' -> '\u2016'; // '∥'(平行記号) -> '‖'(双柱)
case '\uffe0' -> '\u00A2'; // '¢'(全角セント記号) -> '¢'(セント記号)
case '\uffe1' -> '\u00A3'; // '£'(全角ポンド記号) -> '£'(ポンド記号)
case '\uffe2' -> '\u00AC'; // '¬'(全角否定記号) -> '¬'(否定記号)
default -> ch[i];
};
// @formatter:on
}
return String.valueOf(ch);
}
.. note::
Unicodeへのマッピング時の問題であるため、入力値の文字コードに依らず変換は必要である。
変換対象となるのは日本語を含む文字列が設定される可能性のあるヘッダおよび本文の文字列である。
日本語を含む可能性があり一般的によく使われると考えられるヘッダとしては、From、To、Cc、Bcc、Reply-To、Subjectが挙げられる。
また、エンコードにISO-2022-JPを設定する場合、以下のような範囲外となる拡張文字が文字化けする。
.. figure:: ./images_Email/EmailOutofEscapeCharacter.png
:alt: Out of EscapeCharacter
:width: 100%
:align: center
**図-範囲外となる拡張文字の例**
これらの文字は本来使用すべきではない。
もし、これらの文字を使用する必要がある場合、JVMの起動オプションとして以下のように設定することで
ISO-2022-JPのエンコードが指定された場合にx-windows-iso2022jpでマッピングするように差し替えることが可能である。
.. code-block:: text
-Dsun.nio.cs.map=x-windows-iso2022jp/ISO-2022-JP
.. warning::
x-windows-iso2022jpはISO-2022-JPの規格と異なるマッピング(MS932ベース)を含むISO-2022-JP実装である。
メールヘッダでISO-2022-JPのエンコードが指定された場合に範囲外の拡張文字を扱えるような実装となっているかはメールクライアントに依存する。
このため、x-windows-iso2022jpを使用してマッピングした場合でも、すべてのメールクライアントで確実に文字化けしないことが保証されるわけではない。
拡張文字を代替文字に変換してもよい場合、前述した7文字と同様にアプリケーションで独自に変換を行う方法も合わせて検討されたい。
|
JavaMailで発生していたマルチバイト文字を使用する際の不具合について
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
JavaMailでは、送信するメールの本文の終端がマルチバイト文字で終わっていると、終端に余計な文字(「?」や「w)」等)が出力される場合があり、従来は以下の方法で回避していた。
* メール本文の終端文字を半角文字にする
* メール本文の終端を改行コード(CRLF)にする
.. tip::
本事象は、シングルバイト文字とマルチバイト文字の切り替えのために付与される制御コードが付与されていなかったことに起因し、JavaMail 1.4.4でワークアラウンドが施されたことによって、以降のバージョンでは当事象が発生しなくなった。
|
.. _email-header-injection:
メールヘッダ・インジェクション対策
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
メールヘッダ・インジェクション攻撃が成功すると、本来意図していない宛先にメール送信され、
迷惑メール送信の踏み台に悪用される可能性がある。
メールヘッダ(Subject等)の内容に外部から入力された文字列を利用する場合、メールヘッダ・インジェクション攻撃への対策が必要となる。
例えば、\ ``MimeMessageHelper``\ の\ ``setSubject``\ メソッドで以下の文字列を設定すると、Bccヘッダを追加し本文を改ざんすることが可能となる。
.. code-block:: text
Notification\r\nBcc: attacker@exapmle.com\r\n\r\nManipulated body.
メールヘッダ・インジェクション攻撃への対策としては、以下のような方法が考えられる。
* メールヘッダに設定する内容は固定値とし、外部から入力された文字列はすべてメール本文に出力する。
* メールヘッダに設定する内容に改行文字が含まれないことをチェックする。
|
.. _email-processing-method:
処理方式
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
メール送信は時間のかかる処理であるため、Webアプリケーションのリクエストの中で送信処理を行うと応答時間が長くなってしまう。
このため、通常はWebアプリケーションのリクエストの中では送信処理を行わず、非同期でメール送信を行う処理方式とすることが多い。
メール送信の処理方式について詳細については言及しないが、以下に一例を示すので参考にされたい。
データベースまたはメッセージキューに保持されたメール情報をもとにメール送信を行う
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
データベースまたはメッセージキューに保持されたメール情報をもとにメール送信を行うには、以下のような機能をアプリケーションに組み込む。
* 送信するメールの情報(宛先や本文、添付ファイル等)をデータベース(またはメッセージキュー)に登録する。
* データベース(またはメッセージキュー)から未送信のメール情報を定期的に取得し、SMTPによるメール送信を行う。
* 送信結果をデータベース(またはメッセージキュー)に登録する。
なお、以下の点を含めて検討する必要がある。
* 登録されたメール情報やメール送信結果の確認方法
* メール送信エラー時の取り扱い
.. tip::
メールサービスによっては、連続してメールが送信された場合に、スパムメールと判定されることがある。
左記への対策としては、同一ドメインに対し連続で送信処理を行わないように、送信順序をランダムにする方法が考えられる。
|
.. **TBD** GreenMailはまだalpha版しか提供されておらず、FY2022では案内しない方が良いためコメントアウトとする
".."に合わせ以降をインデントすることでコメントアウトしているため、次年度以降GreenMailが安定したタイミングで以降の行のインデントは見直すこと
GreenMailに関しては\ `https://mvnrepository.com/artifact/com.icegreen/greenmail`\ を参照。
ちなみに、2022/12/31 時点で最新が"2.0.0-alpha-3"となる。
\ `リリースノート `\ を読む限りでは
「The 'alpha' in the version is for minor potential API changes but not for stability regarding bugs.」と言っていることから、
ライブラリ自体の単体試験はクリアしていても、Jakarta EE 10として安定しているとはいえない。
.. _email-test-with-greenmail:
GreenMailを利用したテスト
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
メール送信機能をテストするためにフェイクサーバとして\ `GreenMail `_\ を利用する方法を紹介する。
GreenMailはライブラリとして利用する以外に、warファイルをデプロイして利用することも可能である。
GreenMailを利用したテストコードの実装例を以下に示す。
**pom.xmlの設定例**
.. code-block:: xml
com.icegreen
greenmail-spring
2.0.0-alpha-3
test
com.sun.mail
jakarta.mail
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | GreenMailのライブラリをdependenciesに追加する。
* - | (2)
- | GreenMailはJakarta Mailの前身であるJavaMailに依存している。
| \ :ref:`depends_jakartaMail`\ においてJakarta Mailを追加している前提で、クラス競合を防ぐためJavaMailは除外すると良い。
**JUnitソースの実装例**
.. code-block:: java
@Inject
EmailService emailService;
@Rule
public final GreenMailRule greenMail = new GreenMailRule(
ServerSetupTest.SMTP); // (1)
@Test
public void testSend() {
String from = "info@example.com";
String to = "foo@example.com";
String subject = "Registration confirmation.";
String text = "Hi "
+ to
+ ", welcome to EXAMPLE.COM!\r\n"
+ "If you were not an intended recipient, Please notify the sender.";
emailService.send(from, to, subject, text);
assertTrue(greenMail.waitForIncomingEmail(3000, 1)); // (2)
Message[] messages = greenMail.getReceivedMessages(); // (3)
assertNotNull(messages);
assertEquals(1, messages.length);
// omitted
}
.. tabularcolumns:: |p{0.10\linewidth}|p{0.90\linewidth}|
.. list-table::
:header-rows: 1
:widths: 10 90
* - 項番
- 説明
* - | (1)
- | \ ``ServerSetupTest.SMTP``\ を指定した\ ``GreenMailRule``\ をルールとして設定する。
| SMTPのポート番号はデフォルトで\ ``3025``\ が使用される。
* - | (2)
- | \ ``waitForIncomingEmail``\ メソッドを利用してメールの到達を待機する。
| 別スレッドで非同期にメール送信が行われる際に利用する。
| この例では、メール送信が非同期で行われている前提で、1通のメールが到達するまで最大3秒待機する。
* - | (3)
- | \ ``getReceivedMessages``\ メソッドを利用してすべての受信メールを取得する。
| GreenMailで送信したメールは宛先に係らず、すべてGreenMailで受信される。
.. raw:: latex
\newpage