001/*
002 * Copyright (c) 2009 The openGion Project.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
013 * either express or implied. See the License for the specific language
014 * governing permissions and limitations under the License.
015 */
016package org.opengion.hayabusa.filter;
017
018import org.opengion.hayabusa.common.HybsSystem;
019
020import java.io.File;                                                    // 5.7.3.2 (2014/02/28) Tomcat8 対応
021import java.io.IOException;
022import java.io.PrintWriter;
023
024import javax.servlet.Filter;
025import javax.servlet.FilterChain;
026import javax.servlet.FilterConfig;
027import javax.servlet.ServletContext;
028import javax.servlet.ServletException;
029import javax.servlet.ServletRequest;
030import javax.servlet.ServletResponse;
031import javax.servlet.RequestDispatcher;
032import javax.servlet.http.HttpServletResponse;
033import javax.servlet.http.HttpServletRequest;
034
035import org.opengion.fukurou.security.URLHashMap;
036import org.opengion.fukurou.util.StringUtil;
037import org.opengion.fukurou.util.FileString;
038
039/**
040 * URLHashFilter は、Filter インターフェースを継承した URLチェッククラスです。
041 * web.xml で filter 設定することにより、処理を開始します。
042 * filter 処理は、設定レベルとURLの飛び先により処理方法が異なります。
043 * このフィルターでは、ハッシュ化/暗号化ではなく、アドレスに戻す作業になります。
044 * 内部URLの場合はハッシュ化、外部URLの場合は暗号化に適用されます。
045 *
046 * 基本的には、外部へのURLでエンジンシステムへ飛ばす場合は、暗号化になります。
047 * 内部へのURLは、基本的に、パラメータのみ暗号化を行います。なお、直接画面IDを
048 * 指定して飛ばす場合を、止めるかどうかは、設定レベルに依存します。
049 *
050 * フィルターの設定レベルは、システムリソースの URL_ACCESS_SECURITY_LEVEL 変数で
051 * 設定します。
052 * なお、各レベル共通で、戻し処理はレベルに関係なく実行されます。
053 *   レベル0:なにも制限はありません。
054 *   レベル1:Referer チェックを行います。つまり、URLを直接入力しても動作しません。
055 *             ただし、Refererが付いてさえいれば、アクセス許可を与えます。
056 *             Referer 無しの場合でも、URLにパラメータが存在しない、または、
057 *             アドレスがハッシュ化/暗号化されている場合は、アクセスを許可します。
058 *             レベル1の場合、ハッシュ戻し/複合化処理は行います。あくまで、ハッシュ化
059 *             暗号化されていない場合でも、Refererさえあれば、許可するということです。
060 *             (パラメータなし or ハッシュあり or Refererあり の場合、許可)
061 *   レベル2:フィルター処理としては、レベル1と同じです。
062 *             異なるのは、URLのハッシュ化/暗号化処理を、外部URLに対してのみ行います。
063 *             (パラメータなし or ハッシュあり or Refererあり の場合、許可)
064 *   レベル3:URLのパラメータがハッシュ化/暗号化されている必要があります。
065 *             レベル1同様、URLにパラメータが存在しない場合は、アクセスを許可します。
066 *             レベル1と異なるのは、パラメータは必ずハッシュ化か、暗号化されている
067 *             必要があるということです。(内部/外部問わず)
068 *             (パラメータなし or ハッシュあり の場合、許可)
069 *   それ以外:アクセスを停止します。
070 *
071 * フィルターに対してweb.xml でパラメータを設定します。
072 *   ・filename   :停止時メッセージ表示ファイル名(例:/jsp/custom/refuseAccess.html)
073 *   ・initPage   :最初にアクセスされる初期画面アドレス(初期値:/jsp/index.jsp)
074 *   ・debug      :デバッグメッセージの表示(初期値:false)
075 *
076 * 【WEB-INF/web.xml】
077 *     <filter>
078 *         <filter-name>URLHashFilter</filter-name>
079 *         <filter-class>org.opengion.hayabusa.filter.URLHashFilter</filter-class>
080 *         <init-param>
081 *             <param-name>filename</param-name>
082 *             <param-value>/jsp/custom/refuseAccess.html</param-value>
083 *         </init-param>
084 *          <init-param>
085 *              <param-name>initPage</param-name>
086 *              <param-value>/jsp/index.jsp</param-value>
087 *          </init-param>
088 *          <init-param>
089 *              <param-name>debug</param-name>
090 *              <param-value>false</param-value>
091 *          </init-param>
092 *     </filter>
093 *
094 *     <filter-mapping>
095 *         <filter-name>URLHashFilter</filter-name>
096 *         <url-pattern>*.jsp</url-pattern>
097 *     </filter-mapping>
098 *
099 * @og.group フィルター処理
100 *
101 * @og.rev 5.2.2.0 (2010/11/01) 新規追加
102 *
103 * @version  5.2.2.0 (2010/11/01)
104 * @author   Kazuhiko Hasegawa
105 * @since    JDK1.6,
106 */
107public final class URLHashFilter implements Filter {
108        private static final String REQ_KEY = HybsSystem.URL_HASH_REQ_KEY ;
109
110        private static final int ACCS_LVL = HybsSystem.sysInt( "URL_ACCESS_SECURITY_LEVEL" );
111
112        private String          initPage        = "/jsp/index.jsp";
113        private FileString      refuseMsg       = null;                 // アクセス拒否時メッセージファイルの内容(キャッシュ)
114        private boolean         isDebug         = false;
115
116        /**
117         * フィルター処理本体のメソッドです。
118         *
119         * @og.rev 5.3.0.0 (2010/12/01) 文字化け対策として、setCharacterEncoding を実行する。
120         *
121         * @param       request         ServletRequestオブジェクト
122         * @param       response        ServletResponseオブジェクト
123         * @param       chain           FilterChainオブジェクト
124         * @throws IOException 入出力エラーが発生したとき
125         * @throws ServletException サーブレット関係のエラーが発生した場合、throw されます。
126         */
127        public void doFilter( final ServletRequest request, final ServletResponse response, final FilterChain chain ) throws IOException, ServletException {
128                HttpServletRequest req = (HttpServletRequest)request ;
129                req.setCharacterEncoding( "UTF-8" );    // 5.3.0.0 (2010/12/01)
130
131                if( isValidAccess( req ) ) {
132                        String h_r = req.getParameter( REQ_KEY );
133                        // ハッシュ化キーが存在する。
134                        if( h_r != null ) {
135                                HttpServletResponse resp = ((HttpServletResponse)response);
136                                String qu = URLHashMap.getValue( h_r );
137                                // キーに対する実アドレスが存在する。
138                                if( qu != null ) {
139                                        String requestURI = req.getRequestURI();                // /gf/jsp/index.jsp など
140                                        String cntxPath   = req.getContextPath();               // /gf など
141                                        // 自分自身のコンテキストと同じなので、forward できる。
142                                        if( requestURI.startsWith( cntxPath ) ) {
143                                                String url = requestURI.substring(cntxPath.length()) + "?" + qu ;
144                                                RequestDispatcher rd = request.getRequestDispatcher( url );
145                                                rd.forward( request,response );
146                                        }
147                                        // そうでない場合、リダイレクトする。
148                                        else {
149                                                String url = resp.encodeRedirectURL( requestURI + "?" + qu );
150                                                resp.sendRedirect( url );
151                                        }
152                                }
153                                // キーに対する実アドレスが存在しない。(行き先無しのケース)
154                                else {
155                                        String url = resp.encodeRedirectURL( initPage );
156                                        resp.sendRedirect( url );
157                                }
158                        }
159                        // ハッシュ化キーが存在しない。
160                        else {
161                                chain.doFilter(request, response);
162                        }
163                }
164                else {
165                        // アクセス拒否を示すメッセージファイルの内容を出力する。
166                        response.setContentType( "text/html; charset=UTF-8" );
167                        PrintWriter out = response.getWriter();
168                        out.println( refuseMsg.getValue() );
169                        out.flush();
170                }
171        }
172
173        /**
174         * フィルターの初期処理メソッドです。
175         *
176         * フィルターに対してweb.xml で初期パラメータを設定します。
177         *   ・filename   :停止時メッセージ表示ファイル名
178         *   ・initPage   :最初にアクセスされる初期画面アドレス(初期値:/jsp/index.jsp)
179         *   ・debug      :デバッグメッセージの表示(初期値:false)
180         *
181         * @og.rev 5.7.3.2 (2014/02/28) Tomcat8 対応。getRealPath( "/" ) の互換性のための修正。
182         *
183         * @param config FilterConfigオブジェクト
184         */
185        public void init( final FilterConfig config ) {
186                initPage = StringUtil.nval( config.getInitParameter("initPage"), initPage );
187                isDebug  = StringUtil.nval( config.getInitParameter("debug")   , isDebug  );
188
189                ServletContext context = config.getServletContext();
190                String realPath = context.getRealPath( "" ) + File.separator;           // 5.7.3.2 (2014/02/28) Tomcat8 対応
191
192                // アクセス拒否を示すメッセージファイルの内容を管理する FileString オブジェクトを構築する。
193                String filename  = realPath + config.getInitParameter("filename");
194                refuseMsg = new FileString();
195                refuseMsg.setFilename( filename );
196                refuseMsg.setEncode( "UTF-8" );
197        }
198
199        /**
200         * フィルターの終了処理メソッドです。
201         *
202         */
203        public void destroy() {
204                // ここでは処理を行いません。
205        }
206
207        /**
208         * フィルターの内部状態をチェックするメソッドです。
209         *
210         * 判定条件は、URL_ACCESS_SECURITY_LEVEL 変数 に応じて異なります。
211         *     レベル0:なにも制限はありません。
212         *     レベル1:Referer チェックを行います。つまり、URLを直接入力しても動作しません。
213         *     レベル2:URLのハッシュ化/暗号化処理を、外部URLに対してのみ行います。(チェックは、レベル1と同等)
214         *     レベル3:URLのパラメータがハッシュ化/暗号化されている必要があります。
215         *     それ以外:アクセスを停止します。
216         *
217         * @param request HttpServletRequestオブジェクト
218         *
219         * @return      (true:許可  false:拒否)
220         */
221        private boolean isValidAccess( final HttpServletRequest request ) {
222                if( ACCS_LVL == 0 )      { return true;  }      // レベル0:無条件アクセス
223
224                String httpReferer = request.getHeader( "Referer" );
225                String requestURI  = request.getRequestURI();
226                String queryString = request.getQueryString();
227                String hashVal     = request.getParameter( REQ_KEY );
228
229                if( isDebug ) {
230                        System.out.println( "URLHashFilter#httpReferer = " + httpReferer );
231                        System.out.println( "URLHashFilter#requestURI  = " + requestURI  );
232                }
233
234                // 基準となる許可:パラメータなし or ハッシュありの場合
235                boolean flag2 = queryString == null || hashVal != null ;
236
237                // レベル1,2:パラメータなし or ハッシュあり or Refererあり の場合、許可
238                if( ACCS_LVL == 1 || ACCS_LVL == 2 ) {
239                        return flag2 || httpReferer != null ;
240                }
241
242                // レベル3:パラメータなし or ハッシュありの場合、許可
243                if( ACCS_LVL == 3 ) {
244                        String cntxPath = request.getContextPath();             // /gf など
245                        // 特別処置
246                        return flag2 ||
247                                          requestURI.equalsIgnoreCase( initPage )            ||
248                                          requestURI.startsWith( cntxPath + "/jsp/menu/"   ) ||
249                                          requestURI.startsWith( cntxPath + "/jsp/custom/" ) ||
250                                          requestURI.startsWith( cntxPath + "/jsp/common/" ) ;
251                }
252
253                return false;   // それ以外:無条件拒否
254        }
255
256        /**
257         * 内部状態を文字列で返します。
258         *
259         * @return      このクラスの文字列表示
260         */
261        @Override
262        public String toString() {
263                StringBuilder sb = new StringBuilder()
264                        .append( this.getClass().getCanonicalName() ).append( " : ")
265                        .append( "initPage = [" ).append( initPage ).append( "] , ")
266                        .append( "isDebug  = [" ).append( isDebug  ).append( "]");
267                return sb.toString();
268        }
269}