「ログイン機能ってどこから作ればいい?」「セッション管理って具体的に何をするの?」「パスワードはそのままDBに保存したらダメなの?」
Java の Servlet/JSP を一通り学んだあと、多くの人がぶつかる壁がこの「認証・セッション管理」です。概念はなんとなくわかっても、実際にコードに落とし込もうとすると途端に手が止まる…そんな経験、ありませんか?
この記事では、Java研修のプロジェクト型演習で実際に使用しているログイン・ユーザー管理システムのソースコードを全公開します。前回紹介した汎用DAO(BaseDAO / UserDAO)をベースに、セットアップ手順・セキュリティ設計・よくあるエラーの対処法まで、実践的な内容を丸ごとまとめました。
読み終える頃には「ログイン機能の全体像」と「現場で使えるセキュリティの基本」が身についているはずです!
目次
- このシステムでできること
- ファイル構成と各ファイルの役割
- セットアップ手順(4ステップ)
- URL設計:どのURLが何をするか
- セキュリティ設計の5つのポイント
- 実践コードサンプル集
- よくあるエラーと対処法
- まとめ
このシステムでできること
今回作成したシステムは、Webアプリ開発で必ずといっていいほど必要になる「認証付きユーザー管理」の基本形です。
| 機能 | 内容 |
|---|---|
| 🔐 ログイン | メールアドレス+パスワードで認証。成功するとセッションを生成 |
| 🚪 ログアウト | セッションを完全破棄してログイン画面へ戻る |
| 👥 ユーザー一覧 | 登録ユーザーの一覧表示+名前での部分一致検索 |
| ➕ 新規登録 | バリデーション付きの登録フォーム。メール重複チェック込み |
| ✏️ 編集 | ユーザー情報の更新。パスワード欄が空なら現在のパスワードを維持 |
| 🗑️ 削除 | 確認画面付きの削除。自分自身は削除できないガード付き |
| 🛡️ アクセス制御 | 未ログイン状態で /user/* にアクセスするとログイン画面へ自動リダイレクト |
このシステムには管理者(admin)と一般ユーザー(user)の2種類のロールがあり、loginUser.isAdmin() で権限チェックができます。演習のテーマに合わせてロール別の表示切り替えや機能制限に応用できます。
ファイル構成と各ファイルの役割
全19ファイルで構成されています。レイヤー別に整理すると、役割がひと目でわかります。
プロジェクト/
├── sql/
│ └── users_with_auth.sql ← テーブル作成+サンプルデータ
│
├── src/
│ ├── dao/
│ │ ├── BaseDAO.java ← 汎用DB処理(変更不要)
│ │ └── UserDAO.java ← 認証メソッド追加版
│ ├── dto/
│ │ └── UserDTO.java ← password・role フィールド追加版
│ ├── filter/
│ │ └── AuthFilter.java ← /user/* を一括でセッションチェック
│ ├── servlet/
│ │ ├── LoginServlet.java ← GET:画面表示 / POST:認証処理
│ │ ├── LogoutServlet.java ← セッション破棄→リダイレクト
│ │ ├── UserListServlet.java ← 一覧取得・名前検索
│ │ ├── UserCreateServlet.java ← バリデーション付き登録
│ │ ├── UserEditServlet.java ← パスワード有無で更新を切り替え
│ │ └── UserDeleteServlet.java ← 自分自身の削除防止付き
│ └── util/
│ └── PasswordUtil.java ← SHA-256ハッシュ化・照合
│
└── WebContent/
├── index.jsp ← ルート(/)→ログイン画面へ転送
├── WEB-INF/web.xml ← セッションタイムアウト設定
└── jsp/
├── login.jsp ← ログイン画面(CSS込み)
├── common/header.jsp ← ナビバー・フラッシュメッセージ共通
└── user/
├── list.jsp ← 一覧+検索・操作ボタン
├── form.jsp ← 登録・編集の共通フォーム
└── delete_confirm.jsp ← 削除確認画面
構造のポイントは「レイヤーを分けている」こと。Servlet は処理の司令塔として動き、DB操作は DAO に、データ保持は DTO に、セキュリティチェックは Filter に委ねる設計です。役割が明確なので、チーム開発でも担当を分担しやすくなります。
セットアップ手順(4ステップ)
ファイルを配置したあと、以下の4ステップで動かせます。
Step 1|DBのセットアップ
提供している SQL ファイルをそのまま実行するだけです。テーブル作成とサンプルデータの投入が一括で完了します。
mysql -u root -p < sql/users_with_auth.sql
実行後、以下の5件のサンプルユーザーが作成されます(パスワードはすべて password123)。
| メールアドレス | パスワード | 権限 |
|---|---|---|
| admin@example.com | password123 | admin(管理者) |
| tanaka.taro@example.com | password123 | user |
| suzuki.hanako@example.com | password123 | user |
| sato.jiro@example.com | password123 | user |
| takahashi.misaki@example.com | password123 | user |
Step 2|BaseDAO.java の接続情報を変更
以下の3か所を自分の環境に合わせて変更します。
private static final String URL =
"jdbc:mysql://localhost:3306/your_database" // ← DB名
+ "?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Tokyo";
private static final String USER = "your_user"; // ← ユーザー名
private static final String PASSWORD = "your_password"; // ← パスワード
Step 3|JDBCドライバを配置
WEB-INF/lib/mysql-connector-j-8.x.x.jar
Step 4|Tomcat にデプロイして起動
ブラウザで http://localhost:8080/アプリ名/ を開くと、自動的にログイン画面へ転送されます。
URL設計:どのURLが何をするか
URL の設計はシステム全体の「地図」です。誰がどのURLにアクセスしたときに何が起きるかを把握しておくことが、開発・デバッグ両方で重要になります。
| URL | メソッド | 内容 | 認証 |
|---|---|---|---|
/ |
GET | ログイン画面へリダイレクト | 不要 |
/login |
GET | ログイン画面を表示 | 不要 |
/login |
POST | 認証処理 | 不要 |
/logout |
GET | セッション破棄→ログイン画面へ | 不要 |
/user/list |
GET | 一覧(?keyword= で検索) | 必要 |
/user/create |
GET / POST | 新規登録フォーム・登録処理 | 必要 |
/user/edit?id=xx |
GET / POST | 編集フォーム・更新処理 | 必要 |
/user/delete?id=xx |
GET / POST | 削除確認・削除処理 | 必要 |
黄色の行(/user/*)は AuthFilter が自動でセッションチェックします。未ログインでアクセスしようとすると、自動的にログイン画面へリダイレクトされます。
セキュリティ設計の5つのポイント
「動けばいい」から卒業して「現場で使えるコード」にするために、今回は5つのセキュリティ対策を実装しています。
① パスワードは必ずハッシュ化してDBに保存する
パスワードを平文(そのままの文字列)でDBに保存するのは絶対にNGです。DBが万一流出した場合にパスワードが丸見えになってしまいます。今回は SHA-256 でハッシュ化してから保存しています。
// 登録時:平文をハッシュ化してDBに保存
String hashed = PasswordUtil.hash("password123");
// → "ef92b778bafe771e89245b89ecbc08..."(64文字の不可逆な文字列)
// ログイン時:入力値を同じようにハッシュ化して比較
UserDTO user = dao.authenticate(email, plainPassword);
// authenticate() の内部で PasswordUtil.matches() が照合を行う
ハッシュ化は一方向の変換なので、ハッシュ値から元のパスワードを復元することはできません。これがパスワード保護の基本です。
② セッション固定化攻撃への対策
ログイン前から存在するセッションIDをそのまま使い続けると「セッション固定化攻撃」という脆弱性につながります。ログイン成功時に旧セッションを破棄してから新しいセッションを発行することで防ぎます。
// ✅ 正しい実装:旧セッションを無効化してから新規生成
HttpSession oldSession = request.getSession(false);
if (oldSession != null) oldSession.invalidate(); // ← ここが重要!
HttpSession newSession = request.getSession(true);
newSession.setAttribute(AuthFilter.SESSION_KEY, user);
newSession.setMaxInactiveInterval(30 * 60); // 30分でタイムアウト
③ AuthFilter で /user/* を一括保護する
Servlet ごとに「ログインしているか?」をチェックするのは面倒なうえにチェック漏れが起きやすいです。Filter を使えばアノテーション1行で /user/ 以下の全 URL を一括保護できます。
@WebFilter("/user/*") // ← この1行だけで /user/ 以下すべてに適用
public class AuthFilter implements Filter {
@Override
public void doFilter(...) {
HttpSession session = req.getSession(false);
boolean isLoggedIn = (session != null)
&& (session.getAttribute(SESSION_KEY) != null);
if (isLoggedIn) {
chain.doFilter(request, response); // 通過
} else {
res.sendRedirect(contextPath + "/login"); // ログイン画面へ
}
}
}
④ エラーメッセージはあえて曖昧にする
「パスワードが違います」という親切なメッセージは、攻撃者にとって「このメールアドレスは登録済みだ」という情報を与えてしまいます。
// ❌ NG:どちらが間違いかを伝えてしまう
request.setAttribute("errorMsg", "パスワードが違います");
// ✅ OK:どちらが間違いか教えない
request.setAttribute("errorMsg",
"メールアドレスまたはパスワードが正しくありません");
⑤ 自分自身の削除を防止する
管理者が誤って自分自身のアカウントを削除すると、管理者がいなくなるという致命的な問題が起きます。削除処理の GET・POST 両方で二重チェックしています。
// ログインユーザーと削除対象が同じIDなら処理しない
if (loginUser != null && loginUser.getId() == id) {
session.setAttribute("flashMsg", "自分自身は削除できません");
response.sendRedirect(contextPath + "/user/list");
return; // ← 処理を中断
}
実践コードサンプル集
Servlet や他のクラスから使うときの典型的なパターンをまとめます。
ユーザーをプログラムから登録する
UserDAO dao = UserDAO.getInstance();
UserDTO newUser = new UserDTO(
"山田 花子",
"yamada.hanako@example.com",
PasswordUtil.hash("securePass!"), // ← 必ずハッシュ化してから渡す
28,
"user"
);
boolean success = dao.insert(newUser);
if (success) {
System.out.println("登録完了!");
}
ログイン認証を手動で行う
UserDAO dao = UserDAO.getInstance();
UserDTO user = dao.authenticate("admin@example.com", "password123");
if (user != null) {
System.out.println("認証成功: " + user.getName()); // → 管理者
System.out.println("管理者?: " + user.isAdmin()); // → true
} else {
System.out.println("認証失敗(メールまたはパスワードが違う)");
}
セッションからログインユーザーを取得する
// Servlet や JSP 内で使う基本パターン
HttpSession session = request.getSession(false);
UserDTO loginUser = (UserDTO) session.getAttribute(AuthFilter.SESSION_KEY);
if (loginUser != null) {
System.out.println(loginUser.getName()); // ログイン中のユーザー名
if (loginUser.isAdmin()) {
// 管理者にだけ見せる処理・ボタンなどをここに書く
}
}
パスワードなしで更新する(名前・年齢だけ変える場合)
UserDAO dao = UserDAO.getInstance();
UserDTO user = dao.findById(2);
user.setName("田中 次郎");
user.setAge(26);
dao.update(user); // パスワードはDBの現在値をそのまま維持
パスワードも含めて更新する
UserDAO dao = UserDAO.getInstance();
UserDTO user = dao.findById(2);
user.setName("田中 次郎");
user.setPassword(PasswordUtil.hash("newPassword!")); // ← ハッシュ化を忘れずに
dao.updateWithPassword(user); // パスワードも一緒に更新
よくあるエラーと対処法
演習中にハマりやすいエラーを5つ厳選しました。
| 症状 | 原因 | 対処法 |
|---|---|---|
ログイン後も /login に戻される |
セッションが保存されていない | session.setAttribute() の呼び出しを確認。getSession(true) で新規セッションを生成しているか確認 |
/user/list が開けず /login に飛ばされる |
AuthFilter が機能していない | @WebFilter("/user/*") の記述があるか・パッケージが正しいか確認 |
| 正しいパスワードなのに認証失敗する | ハッシュ化のタイミングが間違い | 登録時・比較時ともに PasswordUtil.hash() を使っているか確認。DBのパスワード列が64文字か確認 |
| フォームの日本語が文字化けする | 文字コード設定不足 | doPost の先頭に request.setCharacterEncoding("UTF-8") を追加 |
| すぐセッションが切れてログアウトされる | タイムアウト設定が短すぎる | LoginServlet で setMaxInactiveInterval(30 * 60)(30分)に設定。web.xml の <session-timeout>30</session-timeout> も確認 |
まとめ
今回は、Java Servlet/JSP で作るログイン・ユーザー管理システムの使い方を全公開しました。
- セットアップは SQL実行 → 接続情報変更 → JARファイル配置 → 起動の4ステップ
- URL設計は
/user/*を AuthFilter が一括保護。未ログインはすべてログイン画面へ - セキュリティ対策として①パスワードのSHA-256ハッシュ化 ②セッション固定化攻撃対策 ③Filter による一括アクセス制御 ④エラーメッセージの曖昧化 ⑤自分自身の削除防止 を実装
- コードサンプルでDAO・PasswordUtil・セッションの実践的な使い方を確認
このシステムは「そのまま演習で使える」レベルの実装ですが、さらに発展させるなら管理者専用画面の追加・ロール別の機能制限・ログイン履歴の記録などに挑戦してみてください。
セキュリティは「動くから大丈夫」ではなく、「なぜこの実装が必要か」を理解して書くことが大切です。今回紹介した5つの対策は、現場でも求められる基本中の基本。ぜひ自分の言葉で説明できるようになるまで、コードを読み込んでみてください!
次のステップも一緒に頑張りましょう!💪
