05.設計4: 画面処理設計
概要
ここでは、画面処理での設計、JSPなどのHTMLの設計などを考えてみます。
どんな設計をしようとしているのかは、実際に記事を読んでいただいた方が早いと思います。
method設計
HTMLのformのmethod属性に設定するGETやPOSTを、それぞれどのようなときに使用するかを決めます。
method
GET
POST
使用
更新などがない、データの送信がない画面
データ送信してくる画面
URLパス設計
URLのパスのネーミングルールを決めます。
「そんなもの適当でいいじゃなかいか!」と思われるかもしれませんが、意外と重要です。
サフィックスなどをきめることで、SpringSecurityの設定やSpringMVCの設定をやりやすく、間違いも少なくします。
例えば、CSRFのチェック画面を設定しやすくしたり、アクセス認可の設定、@RequestMappingを書きやすくなります。
【パスのルール】
【操作内容(サフィックス)のルール】
例えば、顧客の会員情報の入力画面は以下のパスになります。
パス: /cust/member/edit/input.html
【ルールによる恩恵例】
例えば、SpringSecurityで管理者用の画面はROLE_ADMINのみがアクセスできるようにしたい場合は、
以下のように設定できます。
設定例: <sec:intercept-url pattern="/admin/**" access="ROLE_ADMIN"/>
View名と画面名のルール
View名は、どのJSPファイルを使用してレスポンスHTMLを作成するかを決める重要な名称です。
画面が少ないうちは良いのですが、数が多くなると、View名を適当に決めていると、どの画面だったのか分からなくなります。
ですのでView名のルールは非常に重要です。
また、View名を決めておくと以下のように、Tilesなどを使用したときにワイルドカード「*」で少ない記述で設定を書け、
しかもView名を予想できるのでプログラムが楽になり、間違いも少なくなります。
【ユーザ画面でのTilesの記述例(以下の記述だけで全画面網羅できます)】
<definition name="cust/*-*-*" extends="baseLayout">
<put-attribute name="titleName" value="{1}" />
<put-attribute name="titleFuncName" value="{2}" />
<put-attribute name="titleSubName" value="{3}" />
<put-attribute name="body" value="/WEB-INF/tiles/{1}-{2}-{3}.jsp" >
</put-attribute>
</definition>
<簡単な説明>
「titleName」「titleFuncName」「titleSubName」
3つそれぞれ、メッセージリソースから文字列を取得して、連結して画面名として表示します(titleタグにも設定する)。
それぞれ「お客様情報」「更新」「入力」のように、画面名を分解した文字列です。
このサンプルソースでは、すべての画面名をこのルールで作成しています。
リンクの書き方ルール
css、jsなどの静的コンテンツのパスは、必ずc:urlを使用してルートからのパスで記述するルールとします。
そうすることで、ルートのパス名やパスの階層が変わっても問題ないようにします。
formのactionは相対パスで良いことにします。
・NGな例
<link href="../include/prog.css" rel="stylesheet" type="text/css" />
・OKな例
<link href="<c:url value='/include/prog.css'/>" rel="stylesheet" type="text/css" />
JSPのデータ出力のルール(XSS対策)
出力する文字列は全て、エスケープすることにします。
Springタグの場合:htmlEscape属性をtrueに設定
例: <spring:bind path="user.name" htmlEscape="true">${status.value}</spring:bind>
${}の場合
例: ${fn:escapeXml(user.name)}
JavaScriptについて
JavaScriptには${}などの動的に変更する文字列を渡さない方針にします。
JavaScriptに渡す文字列もエスケープしなければXSSの攻撃の対象になる可能性があります。
そもそも、JSP上で動的に文字列を出力できるのに、JavaScriptに渡さないとできない処理というのは
設計上に問題がある可能性があり、基本的には設計を見直すべきであると思われます。
CSRF対策のためのルール
CSRF対策のため、springSecurityのCSRFの機能を使用して、画面に自動でCSRFトークンを発行します。
トークンの発行はSpringのfom:formタグを使用すると自動で行われ、何もする必要がありません。
もし、Springタグのformを使用しない場合は自動でトークンが発行されないので、自分で記述します。
データの更新処理をする画面はすべてCSRFトークンを投げ、チェックするルールとします。
ログアウトについて
ログアウト処理はPOSTにすること。
POSTでないと、CSRFトークンの処理が行われないためエラーとなります。
記述例:
<form action="<c:url value='/logout'/>" method="post">
<input type="submit" value="ログアウト" />
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
</form>
なりすまし対策のためのルール
IDなどをリクエストパラメタとする場合、悪意のあるユーザが他人のIDを送ってきて、
そのIDの個人情報を盗もうとするかもしれません。
ですので、IDなどをリクエストパラメタとして受け取る場合は、ログインしたユーザと同じかをチェックし、
違う場合はエラーとするルールにします。
このサンプルでもこの対策を行っています。
ソースの「MemberController」クラスのnewRequest()を見てみてください。
HTMLのhiddenに設定するデータのルール
良くやってしまう間違いとしては、Memberなどのオブジェクトのプロパティをすべてhiddenに設定する、というやり方です。
たしかに、すべての値を 入力画面⇒確認画面⇒完了画面 に渡して遷移していけば、DBから値を取得することなく
処理をしていけます。
しかし、以下のような問題があります。
・クライアントからパラメタをハックして、悪意のある値を送られてしまい、その値でDBを更新してしまう可能性がある
・本来ユーザに見せていけない値を見られてしまう (HTMLソースは閲覧可能なので)
・Memberのプロパティが増えると、Memberを使っている画面すべてにhiddenを追記しないといけない
セキュリティの観点でも、メンテ性の観点でもやってはいけません。
処理に必要なパラメタのみをhiddenにして、基本的にはDBから値を取得するようにするルールにします。
具体的な実装方法としては、ソースの「MemberController」クラスのnewRequest、initBinderFormメソッドなどを
見てみてください。
更新系の完了画面はリダイレクトにする
insert、updateなどを実行する更新系の完了画面では、DB更新完了後にリダイレクトして参照画面を表示するようにしましょう。
リダイレクトにすることで、完了画面でリロードをされても何回もinsert文が実行されることがなくなります。
リダイレクト先の参照画面だけが実行されるからです。
【サンプル】
///ユーザ情報更新処理をする(画面は表示せず、finishにリダイレクトする)
@RequestMapping(value="edit/transactfinish", method=RequestMethod.POST)
public String transactfinish(@Valid Form form, BindingResult result, RedirectAttributes redirectAtt,
HttpServletRequest req)
throws Exception {
//中略・・・
/*リダイレクトすることで、リロードで何度もこのDB更新処理が呼び出されてしまう問題を回避する
リダイレクトでは次の画面にページング情報を送れないのでSpringのaddFlashAttributeを使用する。
これを使うとリダイレクト先のJSPに値を渡すことができる。例:${searchKeys}。
ただし、1回のみしか値が渡らないので、リダイレクト先の画面でリロードされると値が渡らずnullになることは注意点。
この仕組みは、一時的にセッションに値を保存しているようです。
*/
redirectAtt.addFlashAttribute("searchKeys", form.getPaging().getSearchKeys());
return "redirect:finish.html?member.id=" + form.member.getId();
}
///ユーザ情報更新後の完了画面を表示する。画面表示だけを行う。リロード対策。
@RequestMapping(value="edit/finish", method=RequestMethod.GET)
public String finish(@Valid Form form, BindingResult result, HttpServletRequest req) {
return this.viewPrefix + "member-Edit-Finish";
}
<簡単な説明>
完了画面のリクエストがあると上のメソッドtransactfinish()が呼ばれ、処理完了時にはreturnでリダイレクト指定しているので、
ブラウザにレスポンスが返ったのちにすぐに下のメソッドfinish()が呼ばれます。
View名として”redirect:”というプレフィックスをつけると、リダイレクトします。
(参考) 08.Controllerの処理メソッドの引数と返り値
もう一つのポイントは、通常、リダイレクトでは次の画面にデータ(POJO)を送ることはできませんが
Spring MVCでは次の画面にPOJOを送る仕組みを用意してくれています。
RedirectAttributes クラスです。これも確認してみてください。
Created Date: 2015/03/28