0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【個人学習】CampReserve_[common]共通インターフェース・ユーティリティを読んで設計の基盤を理解した

0
Posted at

対象ファイル

  • common/CommonExecute.java
  • common/CommonUtil.java
  • common/GetUrl.java

はじめに

前回は command.yoyaku パッケージを読み終えた。
今回は common パッケージの中から
プロジェクト全体の設計基盤を支える3つのクラスを読んでいく。

このパッケージを読むことで、
これまで各Commandクラスで「なんとなく使っていた」共通処理の
実装が初めてわかった。

CommonExecute.java(共通インターフェース)

プロジェクト全体のCommandパターンを支える
最もシンプルで最も重要なインターフェースだ。

public interface CommonExecute {
    void execute(HttpServletRequest request, HttpServletResponse response)
        throws Exception;
}

たった1行のメソッド定義だが、
このインターフェースがあるおかげで
Front Controllerは全Commandを同じ方法で呼び出せる。

// Front Controller側
CommonExecute command = new MemberLogin();
command.execute(request, response); // どのCommandも同じ呼び出し方

インターフェースを使わなかった場合を考えると
その価値がよくわかる。

// インターフェースがない場合
if (gubun.equals("memberLogin")) {
    MemberLogin login = new MemberLogin();
    login.execute(request, response);
} else if (gubun.equals("memberLogout")) {
    MemberLogout logout = new MemberLogout();
    logout.doProcess(request, response); // メソッド名がバラバラになる
}

インターフェースで execute() を統一することで、
新しいCommandを追加しても呼び出し側のコードは変わらない。
これがオープン・クローズドの原則の実践だと気づいた。

気になった部分

戻り値が void になっている。

void execute(...); // 処理結果を返せない

成功・失敗・遷移先URLなどをCommandから返せないため、
各CommandがrequestにAttributeをセットして
Front Controllerへ結果を伝える間接的な方法を取っている。

// 改善案:結果オブジェクトを返す
CommandResult execute(HttpServletRequest request, HttpServletResponse response);

戻り値で遷移先やメッセージを返せるようにすると
設計がより明確になる。

CommonUtil.java(共通ユーティリティ)

プロジェクト全体で使われる共通処理をまとめたクラスだ。
各Commandで CommonUtil.〇〇() として何度も呼ばれていたメソッドの
実装がここで初めてわかった。

getToday() / getTodayTime()

public static String getToday() {
    LocalDate now = LocalDate.now();
    return now.format(DateTimeFormatter.ofPattern("yyyyMMdd"));
}

public static String getTodayTime() {
    LocalDateTime now = LocalDateTime.now();
    return now.format(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
}

Java 8の LocalDateLocalDateTime を使っている。
以前は DateCalendar が主流だったが、
こちらの方がシンプルで扱いやすい。

getSessionInfo()

public static String getSessionInfo(HttpServletRequest request) {
    HttpSession session = request.getSession(false);
    if (session == null) return "";
    String id = (String) session.getAttribute("sessionId");
    return (id != null) ? id : "";
}

getSession(false) を使っているのが重要だ。
true を渡すと存在しない場合に新しいセッションを生成してしまうが、
false なら存在しない場合は null を返すだけで安全だ。

escapeHtml()

public static String escapeHtml(String input) {
    return input.replace("&", "&")
                .replace("<", "&lt;")
                .replace(">", "&gt;")
                .replace("\"", "&quot;")
                .replace("'", "&#x27;");
}

XSS対策のサニタイズ処理だ。
各Commandで escapeHtml() を呼んでいた理由がここでわかった。

ただ手動で置換しているので
Apache Commons TextやSpring Webの
HtmlUtils.htmlEscape() などのライブラリを使う方が
見落としのリスクが少ない。

encryptSHA256()

public static String encryptSHA256(String input) throws NoSuchAlgorithmException {
    MessageDigest md = MessageDigest.getInstance("SHA-256");
    byte[] hash = md.digest(input.getBytes(StandardCharsets.UTF_8));
    StringBuilder hexString = new StringBuilder();
    for (byte b : hash) {
        hexString.append(String.format("%02x", b));
    }
    return hexString.toString();
}

パスワードのハッシュ化処理だ。
MessageDigest を使ったSHA-256ハッシュ化の実装が読めた。

ただSHA-256単体ではレインボーテーブル攻撃に弱い。

レインボーテーブル攻撃
= よく使われるパスワードのハッシュ値を事前に計算した表を使って
  ハッシュ値から元のパスワードを逆引きする攻撃

ソルト(ランダムな文字列)を加えてからハッシュ化するか、
BCrypt などのパスワードハッシュ専用アルゴリズムを使うべきだ。

// 改善案:BCryptを使う
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
String hashed = encoder.encode(rawPassword);

getFileSaveDir()

public static String getFileSaveDir(HttpServletRequest request, String category) {
    String realPath = request.getServletContext().getRealPath("/");
    return realPath + "attach/" + category + "/";
}

ファイルの保存先パスを動的に取得する処理だ。
getRealPath() でサーバー上の絶対パスを取得しているので
環境が変わっても動的に対応できる。

GetUrl.java(URLパーサー)

リクエストURLから処理に必要な部分だけを取り出す
Front Controller専用のユーティリティクラスだ。

public static String getUrl(HttpServletRequest request) {
    String uri = request.getRequestURI(); // /CampReserve/Member
    String contextPath = request.getContextPath(); // /CampReserve

    // コンテキストパスを除いてServlet名だけ取り出す
    String url = uri.substring(contextPath.length());

    // 拡張子があれば除去(/Member.do → /Member)
    if (url.contains(".")) {
        url = url.substring(0, url.lastIndexOf("."));
    }

    return url.substring(1); // 先頭のスラッシュを除去 → "Member"
}

Front Controllerがどのコマンドを実行すべきか判断するために
URLから "Member""Notice" という文字列を取り出している。

substring() を連続して使うので
URLの形式が変わると壊れやすい。

// 改善案:正規表現で取り出す
String url = uri.replaceAll("^" + contextPath + "/|\\.[^.]+$", "");

今回学んだこと

  • CommonExecute インターフェースが1メソッドだけでも
    Front Controllerパターンの核心を支えている
  • getSession(false) は既存セッションがない場合に
    新しいセッションを生成しない安全な呼び出し方
  • SHA-256単体のハッシュ化はレインボーテーブル攻撃に弱いため
    ソルトを加えるかBCryptを使うべき
  • 共通処理を1クラスにまとめることで
    変更時の修正箇所が1箇所で済む

次はDB接続・ファイルダウンロードを読んでいく。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?