OSDN Git Service

CSRFインターセプターをマシに。
[rabbit-bts/RabbitBTS.git] / src / jp / sourceforge / rabbitBTS / interceptors / CSRFInterceptor.java
index 8f2222b..42add8f 100644 (file)
@@ -1,4 +1,5 @@
-/*
+/* vim:set ts=4 sts=4 sw=4 noet fenc=utf-8:
+
    Copyright 2009 senju@users.sourceforge.jp
 
    Licensed under the Apache License, Version 2.0 (the "License");
 
 package jp.sourceforge.rabbitBTS.interceptors;
 
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
@@ -31,6 +38,7 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
  * CSRF対策用インターセプター
  */
 public class CSRFInterceptor extends HandlerInterceptorAdapter {
+       private int expireInSecond;
 
        /**
         * POSTの場合チェック処理
@@ -40,18 +48,14 @@ public class CSRFInterceptor extends HandlerInterceptorAdapter {
                        HttpServletResponse response, Object handler) throws Exception {
                if (request.getMethod().equals("POST")
                                && handler instanceof IController) {
-                       final String sentToken = request.getParameter("secureToken");
-                       final String sessionToken = (String) request.getSession()
-                                       .getAttribute("secureToken");
                        final IController c = (IController) handler;
-
-                       if (StringUtils.equals(sentToken, sessionToken)) {
+                       final CsrfChecker checker = new CsrfChecker(request);
+                       if (checker.checkTokenValid()) {
                                c.setCsrfSafe(true);
                        } else {
                                c.setCsrfSafe(false);
                                Sht.log(this).warning("CSRF detected.");
                        }
-
                }
                return true;
        }
@@ -61,12 +65,16 @@ public class CSRFInterceptor extends HandlerInterceptorAdapter {
         */
        @Override
        public void postHandle(HttpServletRequest request,
-                       HttpServletResponse response, Object handler,
-                       ModelAndView modelAndView) throws Exception {
-               final String token = RandomStringUtils.randomAlphanumeric(128);
-
-               request.getSession().setAttribute("secureToken", token);
-               modelAndView.addObject("secureToken", token);
+                       HttpServletResponse response, Object handler, ModelAndView mav)
+                       throws Exception {
+               // リダイレクトする場合は不要
+               if (mav != null) {
+                       if (!StringUtils.startsWith(mav.getViewName(), "redirect:")) {
+                               final CsrfChecker checker = new CsrfChecker(request);
+                               final String token = checker.saveNewToken();
+                               mav.addObject("secureToken", token);
+                       }
+               }
 
                if (request.getMethod().equals("POST")
                                && handler instanceof IController) {
@@ -79,4 +87,95 @@ public class CSRFInterceptor extends HandlerInterceptorAdapter {
                }
        }
 
+       /**
+        * チェック用クラス
+        */
+       class CsrfChecker {
+               private final HttpServletRequest req;
+               private Map<String, Date> tokens;
+
+               /**
+                * コンストラクタ
+                * 
+                * <p>
+                * セッションにトークンのリストが存在しない場合、新規に作成しセッションに保存する。
+                * 
+                * @param request
+                */
+               @SuppressWarnings("unchecked")
+               public CsrfChecker(HttpServletRequest request) {
+                       this.req = request;
+                       this.tokens = (Map<String, Date>) request.getSession()
+                                       .getAttribute("tokens");
+                       if (this.tokens == null) {
+                               this.tokens = new HashMap<String, Date>();
+                               request.getSession().setAttribute("tokens", this.tokens);
+                       }
+               }
+
+               /**
+                * トークンをセッションに保存する。
+                * 
+                * @return 保存された新規トークン
+                */
+               public String saveNewToken() {
+                       final String token = RandomStringUtils.randomAlphanumeric(128);
+                       this.tokens.put(token, new Date());
+                       return token;
+               }
+
+               /**
+                * チェックを行う
+                * 
+                * @return 正しいパラメータが送信された場合true
+                */
+               public boolean checkTokenValid() {
+                       final String reqToken = this.req.getParameter("secureToken");
+                       final Date datenow = new Date();
+                       // トークンチェック
+                       boolean found = false;
+                       if (this.tokens.containsKey(reqToken)) {
+                               found = checkExpire(datenow, this.tokens.get(reqToken));
+                       }
+
+                       // 削除チェック
+                       if (this.tokens.size() > 30) {
+                               final List<String> removeList = new ArrayList<String>();
+                               for (final String token : this.tokens.keySet()) {
+                                       final Date created = this.tokens.get(token);
+                                       if (!this.checkExpire(datenow, created)) {
+                                               // 期限切れの場合
+                                               removeList.add(token);
+                                               Sht.log(this).finer("delete token: " + token);
+                                       }
+                               }
+                               for (final String token : removeList) {
+                                       this.tokens.remove(token);
+                               }
+                       }
+
+                       return found;
+               }
+
+               /**
+                * 期限切れかチェックする。
+                * 
+                * @param datenow
+                * @param created
+                * @return 期限以内の場合true
+                */
+               private boolean checkExpire(Date datenow, Date created) {
+                       final long ageInMils = datenow.getTime() - created.getTime();
+                       return ageInMils / 1000 < CSRFInterceptor.this.expireInSecond;
+               }
+       }
+
+       /**
+        * @param expireInSecond
+        *            the expireInSecond to set
+        */
+       public void setExpireInSecond(int expireInMinute) {
+               this.expireInSecond = expireInMinute;
+       }
+
 }