Servlet総合 – 課題30問

2025-08-04

初級問題 (9問)

  1. HTTPセッションとは何か、簡単に説明してください。
  2. HttpSessionオブジェクトを取得する方法を説明してください。
  3. セッションにデータを保存し、取得する方法を説明してください。
  4. Cookieとは何か、簡単に説明してください。
  5. ServletでCookieを作成し、クライアントに送信する方法を説明してください。
  6. クライアントから送信されたCookieを取得する方法を説明してください。
  7. Filterの基本的な役割を説明してください。
  8. Listenerインターフェースの主な種類を3つ挙げてください。
  9. web.xmlにFilterを設定する方法を説明してください。

中級問題 (15問)

  1. セッションの有効期限を設定する方法を2通り説明してください。
  2. セッションを明示的に無効化する方法を説明してください。
  3. Cookieの有効期限を設定する方法を説明してください。
  4. 安全なCookie(Secureフラグ、HttpOnlyフラグ)を設定する方法を説明してください。
  5. Filterチェーンとは何か、その動作を説明してください。
  6. Filterでリクエストとレスポンスをラップする方法を説明してください。
  7. ログイン認証を行うFilterの実装例を示してください。
  8. ServletContextListenerを使用してアプリケーション起動時に初期化処理を行う方法を説明してください。
  9. HttpSessionListenerを使用してセッションの作成・破棄を監視する方法を説明してください。
  10. リクエストのエンコーディングを統一するFilterの実装例を示してください。
  11. レスポンスのキャッシュを制御するFilterの実装例を示してください。
  12. セッションハイジャック対策として、セッションIDを変更する方法を説明してください。
  13. Cookieを使用した「最後に訪問した日時」を表示するServletを作成してください。
  14. アプリケーション全体で共有するデータをServletContextに保存・取得する方法を説明してください。
  15. Filterを使用して特定のURLパターンへのアクセスを制限する方法を説明してください。

上級問題 (6問)

  1. 分散環境でのセッション管理方法を説明してください。
  2. セッションストアとしてデータベースを使用する方法の概要を説明してください。
  3. 非同期処理をサポートするFilterの実装方法を説明してください。
  4. アプリケーションのライフサイクルイベントとセッションイベントを組み合わせて監視するListenerの実装例を示してください。
  5. カスタムCookie処理を行うRequest/Responseラッパークラスの実装例を示してください。
  6. RESTful APIにおけるセッション管理と認証のベストプラクティスを説明してください。

解答例

初級問題解答

  1. HTTPセッション
   HTTPセッションは、複数のリクエストにわたってユーザーの状態を保持する仕組みです。サーバー側でセッションIDを発行し、クライアント(通常はCookieを通じて)と共有することで、同一ユーザーの複数リクエストを関連付けます。
  1. HttpSession取得
   HttpSession session = request.getSession(); // 存在しなければ新規作成
   HttpSession session = request.getSession(false); // 存在しない場合null
  1. セッションへのデータ保存・取得
   // 保存
   session.setAttribute("key", object);

   // 取得
   Object value = session.getAttribute("key");

   // 削除
   session.removeAttribute("key");
  1. Cookie
   Cookieは、サーバーがクライアントに送信する小さなデータで、クライアント側に保存され、以降のリクエストでサーバーに送信されます。セッション管理やパーソナライズなどに使用されます。
  1. Cookie作成・送信
   Cookie cookie = new Cookie("name", "value");
   cookie.setMaxAge(60 * 60 * 24); // 1日
   response.addCookie(cookie);
  1. Cookie取得
   Cookie[] cookies = request.getCookies();
   if (cookies != null) {
       for (Cookie cookie : cookies) {
           if (cookie.getName().equals("name")) {
               String value = cookie.getValue();
               // 処理...
           }
       }
   }
  1. Filterの役割
   Filterは、リクエストがServletに到達する前やレスポンスがクライアントに返される前に処理を追加するコンポーネントです。認証、ロギング、エンコーディング設定、キャッシュ制御などに使用されます。
  1. Listener種類
   1. ServletContextListener - アプリケーションの開始/終了を監視
   2. HttpSessionListener - セッションの作成/破棄を監視
   3. ServletRequestListener - リクエストの開始/終了を監視
  1. web.xml Filter設定
<filter>
    <filter-name>MyFilter</filter-name>
    <filter-class>com.example.MyFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>MyFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

中級問題解答

  1. セッション有効期限設定
// 方法1: web.xmlで設定

    30 


// 方法2: プログラムで設定
session.setMaxInactiveInterval(30 * 60); // 秒単位
  1. セッション無効化
HttpSession session = request.getSession(false);
if (session != null) {
    session.invalidate();
}
  1. Cookie有効期限
Cookie cookie = new Cookie("name", "value");
cookie.setMaxAge(60 * 60 * 24 * 7); // 1週間(秒単位)
response.addCookie(cookie);
  1. 安全なCookie設定
Cookie cookie = new Cookie("name", "value");
cookie.setSecure(true); // HTTPSのみで送信
cookie.setHttpOnly(true); // JavaScriptからアクセス不可
response.addCookie(cookie);
  1. Filterチェーン

Filterチェーンは、複数のFilterが連鎖的に実行される仕組みです。各Filterはchain.doFilter()を呼び出すことで次のFilterやServletに処理を渡します。web.xmlでの定義順や@WebFilterのorder属性で実行順が決まります。

  1. リクエスト/レスポンスラップ
public class MyFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        
        HttpServletRequest wrappedRequest = new CustomRequestWrapper((HttpServletRequest)request);
        HttpServletResponse wrappedResponse = new CustomResponseWrapper((HttpServletResponse)response);
        
        chain.doFilter(wrappedRequest, wrappedResponse);
    }
}
  1. 認証Filter例
@WebFilter("/secure/*")
public class AuthFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest)request;
        HttpSession session = httpRequest.getSession(false);
        
        if (session == null || session.getAttribute("user") == null) {
            ((HttpServletResponse)response).sendRedirect(httpRequest.getContextPath() + "/login");
            return;
        }
        
        chain.doFilter(request, response);
    }
}
  1. ServletContextListener
@WebListener
public class AppInitializer implements ServletContextListener {
    public void contextInitialized(ServletContextEvent sce) {
        // アプリケーション起動時の初期化処理
        ServletContext context = sce.getServletContext();
        context.setAttribute("startTime", System.currentTimeMillis());
    }
    
    public void contextDestroyed(ServletContextEvent sce) {
        // アプリケーション終了時のクリーンアップ処理
    }
}
  1. HttpSessionListener
@WebListener
public class SessionTracker implements HttpSessionListener {
    private AtomicInteger sessionCount = new AtomicInteger();
    
    public void sessionCreated(HttpSessionEvent se) {
        sessionCount.incrementAndGet();
        System.out.println("Session created. Total: " + sessionCount);
    }
    
    public void sessionDestroyed(HttpSessionEvent se) {
        sessionCount.decrementAndGet();
        System.out.println("Session destroyed. Total: " + sessionCount);
    }
}
  1. エンコーディングFilter
@WebFilter("/*")
public class EncodingFilter implements Filter {
    private String encoding = "UTF-8";
    
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        
        request.setCharacterEncoding(encoding);
        response.setCharacterEncoding(encoding);
        chain.doFilter(request, response);
    }
}
  1. キャッシュ制御Filter
@WebFilter("/*")
public class CacheControlFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        
        HttpServletResponse httpResponse = (HttpServletResponse)response;
        httpResponse.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
        httpResponse.setHeader("Pragma", "no-cache");
        httpResponse.setHeader("Expires", "0");
        
        chain.doFilter(request, response);
    }
}
  1. セッションID変更
HttpSession oldSession = request.getSession();
// セッション属性を保持
Map attributes = new HashMap<>();
Collections.list(oldSession.getAttributeNames())
          .forEach(name -> attributes.put(name, oldSession.getAttribute(name)));

// 古いセッションを無効化
oldSession.invalidate();

// 新しいセッションを作成
HttpSession newSession = request.getSession(true);
attributes.forEach(newSession::setAttribute);
  1. 最終訪問日時Cookie
@WebServlet("/visit")
public class VisitServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        Cookie[] cookies = request.getCookies();
        String lastVisit = "This is your first visit";
        
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().equals("lastVisit")) {
                    lastVisit = "Last visit: " + cookie.getValue();
                    break;
                }
            }
        }
        
        String currentTime = Instant.now().toString();
        Cookie visitCookie = new Cookie("lastVisit", currentTime);
        visitCookie.setMaxAge(60 * 60 * 24 * 365); // 1年間有効
        response.addCookie(visitCookie);
        
        response.setContentType("text/html");
        response.getWriter().println("

" + lastVisit + "

"); } }
  1. ServletContextデータ共有
// アプリケーション全体でデータを保存
getServletContext().setAttribute("globalData", data);

// データを取得
Object data = getServletContext().getAttribute("globalData");

// データを削除
getServletContext().removeAttribute("globalData");
  1. アクセス制限Filter
@WebFilter("/admin/*")
public class AdminFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest)request;
        HttpServletResponse httpResponse = (HttpServletResponse)response;
        
        String ip = httpRequest.getRemoteAddr();
        if (!ip.startsWith("192.168.")) {
            httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Access denied");
            return;
        }
        
        chain.doFilter(request, response);
    }
}

上級問題解答

  1. 分散環境セッション管理

分散環境では、セッション情報を複数のサーバー間で共有する必要があります。主な方法:

  1. セッション複製 (Session Replication):
  • クラスタ内の全サーバーにセッションを複製
  • TomcatのDeltaManagerなど
  1. 永続化セッション (Persistent Session):
  • セッションをデータベースや共有ファイルシステムに保存
  • TomcatのPersistentManager
  1. セッションストア (Centralized Session Store):
  • RedisやMemcachedなどの集中型ストアを使用
  • 高可用性とスケーラビリティに優れる
// 設定例 (Tomcatのcontext.xml)
<Manager className="org.apache.catalina.ha.session.DeltaManager"
         expireSessionsOnShutdown="false"
         notifyListenersOnReplication="true"/>
  1. DBセッションストア

データベースをセッションストアとして使用する方法:

Tomcat設定 (context.xml):

セッションテーブル作成:
CREATE TABLE sessions (
  session_id VARCHAR(100) PRIMARY KEY,
  session_data BLOB,
  creation_time BIGINT,
  last_accessed_time BIGINT,
  max_inactive_interval INT
);
//カスタムSessionManager実装:
public class DatabaseSessionManager extends ManagerBase {
  public Session createSession(String sessionId) {
    // DBに新規セッション作成
  }

  public Session findSession(String id) {
    // DBからセッション取得
  }

  public void remove(Session session) {
    // DBからセッション削除
  }
}
  1. 非同期Filter
@WebFilter(asyncSupported = true, value = "/*")
public class AsyncFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        
        if (request.isAsyncSupported() && request.isAsyncStarted()) {
            // 非同期リクエスト処理
            AsyncContext context = request.getAsyncContext();
            context.addListener(new AsyncListener() {
                public void onComplete(AsyncEvent event) {
                    // 非同期処理完了時
                }
                // 他のメソッド実装...
            });
        }
        
        chain.doFilter(request, response);
    }
}
  1. ライフサイクル監視Listener
@WebListener
public class AppMonitoringListener implements ServletContextListener, 
                                             HttpSessionListener, 
                                             ServletRequestListener {
    
    // アプリケーションライフサイクル
    public void contextInitialized(ServletContextEvent sce) {
        sce.getServletContext().log("Application started");
    }
    
    public void contextDestroyed(ServletContextEvent sce) {
        sce.getServletContext().log("Application stopped");
    }
    
    // セッションライフサイクル
    public void sessionCreated(HttpSessionEvent se) {
        se.getSession().getServletContext().log("Session created: " + se.getSession().getId());
    }
    
    public void sessionDestroyed(HttpSessionEvent se) {
        se.getSession().getServletContext().log("Session destroyed: " + se.getSession().getId());
    }
    
    // リクエストライフサイクル
    public void requestInitialized(ServletRequestEvent sre) {
        sre.getServletContext().log("Request started: " + 
            ((HttpServletRequest)sre.getServletRequest()).getRequestURI());
    }
    
    public void requestDestroyed(ServletRequestEvent sre) {
        sre.getServletContext().log("Request completed: " + 
            ((HttpServletRequest)sre.getServletRequest()).getRequestURI());
    }
}
  1. カスタムCookieラッパー
public class CookieRequestWrapper extends HttpServletRequestWrapper {
    private Map cookiesMap;
    
    public CookieRequestWrapper(HttpServletRequest request) {
        super(request);
        this.cookiesMap = new HashMap<>();
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                cookiesMap.put(cookie.getName(), cookie.getValue());
            }
        }
    }
    
    @Override
    public Cookie[] getCookies() {
        return cookiesMap.entrySet().stream()
            .map(e -> new Cookie(e.getKey(), e.getValue()))
            .toArray(Cookie[]::new);
    }
    
    public String getCookieValue(String name) {
        return cookiesMap.get(name);
    }
    
    public void addCookie(String name, String value) {
        cookiesMap.put(name, value);
    }
}

// 使用例
@WebFilter("/*")
public class CookieFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        
        CookieRequestWrapper wrappedRequest = new CookieRequestWrapper((HttpServletRequest)request);
        chain.doFilter(wrappedRequest, response);
    }
}
  1. RESTful APIセッション管理

RESTful APIにおけるセッション管理と認証のベストプラクティス:

  1. ステートレスな設計:
  • サーバー側でセッションを維持しない
  • 各リクエストに認証情報を含める
  1. トークンベース認証:
  • JWT (JSON Web Token)を使用
  • トークンに署名と有効期限を設定
  1. OAuth2.0:
  • サードパーティ認証と権限委譲をサポート
  1. セキュアなCookie:
  • トークンをCookieで送信する場合はHttpOnlyとSecureフラグを設定
  1. CSRF対策:
  • ステートフルな操作にはCSRFトークンを使用
  • SameSite Cookie属性を設定
// JWTフィルター例
@WebFilter("/api/*")
public class JwtFilter implements Filter {
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
        throws IOException, ServletException {
        
        HttpServletRequest httpRequest = (HttpServletRequest)request;
        String token = httpRequest.getHeader("Authorization");
        
        try {
            if (token != null && JwtUtil.validateToken(token)) {
                String username = JwtUtil.getUsernameFromToken(token);
                httpRequest.setAttribute("username", username);
                chain.doFilter(request, response);
            } else {
                ((HttpServletResponse)response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
            }
        } catch (Exception e) {
            ((HttpServletResponse)response).sendError(HttpServletResponse.SC_UNAUTHORIZED);
        }
    }
}