    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.IOException;
    021import java.io.PrintWriter;
    022
    023import jakarta.servlet.Filter;
    024import jakarta.servlet.FilterChain;
    025import jakarta.servlet.FilterConfig;
    026import jakarta.servlet.ServletException;
    027import jakarta.servlet.ServletRequest;
    028import jakarta.servlet.ServletResponse;
    029import jakarta.servlet.RequestDispatcher;
    030import jakarta.servlet.http.HttpServletResponse;
    031import jakarta.servlet.http.HttpServletRequest;
    032
    033import org.opengion.fukurou.security.URLHashMap;
    034import org.opengion.fukurou.util.StringUtil;
    035import org.opengion.fukurou.util.FileUtil;                                                              // 6.4.5.2 (2016/05/06)
    036import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;              // 6.1.0.0 (2014/12/26) refactoring
    037import org.opengion.fukurou.system.HybsConst;                                                   // 6.4.5.2 (2016/05/06)
    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 *   レベル０：なにも制限はありません。
    054 *   レベル１：Referer チェックを行います。つまり、URLを直接入力しても動作しません。
    055 *             ただし、Refererが付いてさえいれば、アクセス許可を与えます。
    056 *             Referer 無しの場合でも、URLにパラメータが存在しない、または、
    057 *             アドレスがハッシュ化/暗号化されている場合は、アクセスを許可します。
    058 *             レベル１の場合、ハッシュ戻し/復号化処理は行います。あくまで、ハッシュ化
    059 *             暗号化されていない場合でも、Refererさえあれば、許可するということです。
    060 *             (パラメータなし or ハッシュあり or Refererあり の場合、許可)
    061 *   レベル２：フィルター処理としては、レベル１と同じです。
    062 *             異なるのは、URLのハッシュ化/暗号化処理を、外部URLに対してのみ行います。
    063 *             (パラメータなし or ハッシュあり or Refererあり の場合、許可)
    064 *   レベル３：URLのパラメータがハッシュ化/暗号化されている必要があります。
    065 *             レベル１同様、URLにパラメータが存在しない場合は、アクセスを許可します。
    066 *             レベル１と異なるのは、パラメータは必ずハッシュ化か、暗号化されている
    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 *     &lt;filter&gt;
    078 *         &lt;filter-name&gt;URLHashFilter&lt;/filter-name&gt;
    079 *         &lt;filter-class&gt;org.opengion.hayabusa.filter.URLHashFilter&lt;/filter-class&gt;
    080 *         &lt;init-param&gt;
    081 *             &lt;param-name&gt;filename&lt;/param-name&gt;
    082 *             &lt;param-value&gt;/jsp/custom/refuseAccess.html&lt;/param-value&gt;
    083 *         &lt;/init-param&gt;
    084 *          &lt;init-param&gt;
    085 *              &lt;param-name&gt;initPage&lt;/param-name&gt;
    086 *              &lt;param-value&gt;/jsp/index.jsp&lt;/param-value&gt;
    087 *          &lt;/init-param&gt;
    088 *          &lt;init-param&gt;
    089 *              &lt;param-name&gt;debug&lt;/param-name&gt;
    090 *              &lt;param-value&gt;false&lt;/param-value&gt;
    091 *          &lt;/init-param&gt;
    092 *     &lt;/filter&gt;
    093 *
    094 *     &lt;filter-mapping&gt;
    095 *         &lt;filter-name&gt;URLHashFilter&lt;/filter-name&gt;
    096 *         &lt;url-pattern&gt;*.jsp&lt;/url-pattern&gt;
    097 *     &lt;/filter-mapping&gt;
    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 String          filename        = "jsp/custom/refuseAccess.html" ;                      // 6.3.8.3 (2015/10/03) アクセス拒否時メッセージ表示ファイル名
    114        private boolean         isDebug         ;
    115
    116        /**
    117         * デフォルトコンストラクター
    118         *
    119         * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
    120         */
    121        public URLHashFilter() { super(); }             // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。
    122
    123        /**
    124         * フィルター処理本体のメソッドです。
    125         *
    126         * @og.rev 5.3.0.0 (2010/12/01) 文字化け対策として、setCharacterEncoding を実行する。
    127         * @og.rev 6.3.8.3 (2015/10/03) アクセス拒否を示すメッセージファイルの内容を取り出します。
    128         *
    129         * @param       request         ServletRequestオブジェクト
    130         * @param       response        ServletResponseオブジェクト
    131         * @param       chain           FilterChainオブジェクト
    132         * @throws IOException 入出力エラーが発生したとき
    133         * @throws ServletException サーブレット関係のエラーが発生した場合、throw されます。
    134         */
    135        @Override       // Filter
    136        public void doFilter( final ServletRequest request,
    137                                                        final ServletResponse response,
    138                                                        final FilterChain chain ) throws IOException, ServletException {
    139
    140                final HttpServletRequest req = (HttpServletRequest)request ;
    141                req.setCharacterEncoding( "UTF-8" );    // 5.3.0.0 (2010/12/01)
    142
    143                if( isValidAccess( req ) ) {
    144                        final String h_r = req.getParameter( REQ_KEY );
    145                        // ハッシュ化キーが存在する。
    146                        // 6.0.2.5 (2014/10/31) refactoring: findBugs:未チェック/未確認のキャスト対応。
    147                        if( h_r != null && response instanceof HttpServletResponse ) {
    148                                final HttpServletResponse resp = ((HttpServletResponse)response);
    149                                final String qu = URLHashMap.getValue( h_r );
    150                                // 6.4.1.1 (2016/01/16) PMD refactoring. Avoid if (x != y) ..; else ..;
    151                                // キーに対する実アドレスが存在しない。(行き先無しのケース)
    152                                if( qu == null ) {
    153                                        final String url = resp.encodeRedirectURL( initPage );
    154                                        resp.sendRedirect( url );
    155                                }
    156                                // キーに対する実アドレスが存在する。
    157                                else {
    158                                        final String requestURI = req.getRequestURI();          // /gf/jsp/index.jsp など
    159                                        final String cntxPath   = req.getContextPath();         // /gf など
    160                                        // 自分自身のコンテキストと同じなので、forward できる。
    161                                        if( requestURI.startsWith( cntxPath ) ) {
    162                                                final String url = requestURI.substring(cntxPath.length()) + "?" + qu ;
    163                                                final RequestDispatcher rd = request.getRequestDispatcher( url );
    164                                                rd.forward( request,response );
    165                                        }
    166                                        // そうでない場合、リダイレクトする。
    167                                        else {
    168                                                final String url = resp.encodeRedirectURL( requestURI + "?" + qu );
    169                                                resp.sendRedirect( url );
    170                                        }
    171                                }
    172                        }
    173                        // ハッシュ化キーが存在しない。
    174                        else {
    175                                chain.doFilter(request, response);
    176                        }
    177                }
    178                else {
    179                        // アクセス拒否を示すメッセージファイルの内容を出力する。
    180                        response.setContentType( "text/html; charset=UTF-8" );
    181                        final PrintWriter out = response.getWriter();
    182                        out.println( refuseMsg() );                                                     // 6.3.8.3 (2015/10/03)
    183                        out.flush();
    184                }
    185        }
    186
    187        /**
    188         * フィルターの初期処理メソッドです。
    189         *
    190         * フィルターに対してweb.xml で初期パラメータを設定します。
    191         *   ・filename   :停止時メッセージ表示ファイル名
    192         *   ・initPage   :最初にアクセスされる初期画面アドレス(初期値:/jsp/index.jsp)
    193         *   ・debug      :デバッグメッセージの表示(初期値:false)
    194         *
    195         * @og.rev 5.7.3.2 (2014/02/28) Tomcat8 対応。getRealPath( "/" ) の互換性のための修正。
    196         * @og.rev 6.2.4.1 (2015/05/22) REAL_PATH 対応。realPath は、HybsSystem経由で、取得する。
    197         * @og.rev 6.3.8.3 (2015/10/03) filenameの初期値設定。
    198         *
    199         * @param config FilterConfigオブジェクト
    200         */
    201        @Override       // Filter
    202        public void init( final FilterConfig config ) {
    203                initPage = StringUtil.nval( config.getInitParameter("initPage"), initPage );
    204                isDebug  = StringUtil.nval( config.getInitParameter("debug")   , isDebug  );
    205
    206                filename = HybsSystem.getRealPath() + StringUtil.nval( config.getInitParameter("filename") , filename );        // 6.3.8.3 (2015/10/03)
    207        }
    208
    209        /**
    210         * フィルターの終了処理メソッドです。
    211         *
    212         */
    213        @Override       // Filter
    214        public void destroy() {
    215                // ここでは処理を行いません。
    216        }
    217
    218        /**
    219         * アクセス拒否を示すメッセージ内容。
    220         *
    221         * @og.rev 6.3.8.3 (2015/10/03) アクセス拒否を示すメッセージファイルの内容を取り出します。
    222         * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
    223         * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。
    224         *
    225         * @return アクセス拒否を示すメッセージファイルの内容
    226         */
    227        private String refuseMsg() {
    228                // アクセス拒否を示すメッセージファイルの内容を管理する FileString オブジェクトを構築する。
    229
    230                // 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更
    231                return FileUtil.getValue( filename , HybsConst.UTF_8 );                 // 6.4.5.2 (2016/05/06)
    232        }
    233
    234        /**
    235         * フィルターの内部状態をチェックするメソッドです。
    236         *
    237         * 判定条件は、URL_ACCESS_SECURITY_LEVEL 変数 に応じて異なります。
    238         *     レベル０：なにも制限はありません。
    239         *     レベル１：Referer チェックを行います。つまり、URLを直接入力しても動作しません。
    240         *     レベル２：URLのハッシュ化/暗号化処理を、外部URLに対してのみ行います。(チェックは、レベル１と同等)
    241         *     レベル３：URLのパラメータがハッシュ化/暗号化されている必要があります。
    242         *     それ以外：アクセスを停止します。
    243         *
    244         * @param request HttpServletRequestオブジェクト
    245         *
    246         * @return      (true:許可  false:拒否)
    247         */
    248        private boolean isValidAccess( final HttpServletRequest request ) {
    249                if( ACCS_LVL == 0 )      { return true;  }      // レベル０：無条件アクセス
    250
    251                final String httpReferer = request.getHeader( "Referer" );
    252                final String requestURI  = request.getRequestURI();
    253                final String queryString = request.getQueryString();
    254                final String hashVal     = request.getParameter( REQ_KEY );
    255
    256                if( isDebug ) {
    257                        System.out.println( "URLHashFilter#httpReferer = " + httpReferer );
    258                        System.out.println( "URLHashFilter#requestURI  = " + requestURI  );
    259                }
    260
    261                // 基準となる許可：パラメータなし or ハッシュありの場合
    262                final boolean flag2 = queryString == null || hashVal != null ;
    263
    264                // レベル１,２：パラメータなし or ハッシュあり or Refererあり の場合、許可
    265                if( ACCS_LVL == 1 || ACCS_LVL == 2 ) {
    266                        return flag2 || httpReferer != null ;
    267                }
    268
    269                // レベル３：パラメータなし or ハッシュありの場合、許可
    270                if( ACCS_LVL == 3 ) {
    271                        final String cntxPath = request.getContextPath();               // /gf など
    272                        // 特別処置
    273                        return flag2 ||
    274                                          requestURI.equalsIgnoreCase( initPage )            ||
    275                                          requestURI.startsWith( cntxPath + "/jsp/menu/"   ) ||
    276                                          requestURI.startsWith( cntxPath + "/jsp/custom/" ) ||
    277                                          requestURI.startsWith( cntxPath + "/jsp/common/" ) ;
    278                }
    279
    280                return false;   // それ以外：無条件拒否
    281        }
    282
    283        /**
    284         * 内部状態を文字列で返します。
    285         *
    286         * @return      このクラスの文字列表示
    287         * @og.rtnNotNull
    288         */
    289        @Override       // Object
    290        public String toString() {
    291                final StringBuilder sb = new StringBuilder( BUFFER_MIDDLE )
    292                        .append( this.getClass().getCanonicalName() ).append( " : ")
    293                        .append( "initPage = [" ).append( initPage ).append( "] , ")
    294                        .append( "isDebug  = [" ).append( isDebug  ).append( "]");
    295                return sb.toString();
    296        }
    297}