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 java.io.IOException; 019import java.io.PrintWriter; 020 021import javax.servlet.Filter; 022import javax.servlet.FilterChain; 023import javax.servlet.FilterConfig; 024import javax.servlet.ServletException; 025import javax.servlet.ServletRequest; 026import javax.servlet.ServletResponse; 027import javax.servlet.http.HttpServletRequest; 028 029import org.opengion.fukurou.security.HybsCryptography; 030import org.opengion.fukurou.util.StringUtil; 031import org.opengion.hayabusa.common.HybsSystem; 032import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE; // 6.1.0.0 (2014/12/26) refactoring 033import org.opengion.fukurou.system.ThrowUtil; // 6.4.2.0 (2016/01/29) 034import org.opengion.fukurou.system.HybsConst; // 6.4.5.2 (2016/05/06) 035 036import org.opengion.fukurou.util.FileUtil; // 6.4.5.2 (2016/05/06) 037 038/** 039 * URLCheckFilter は、Filter インターフェースを継承した URLチェッククラスです。 040 * web.xml で filter 設定することにより、該当のリソースに対して、og:linkタグで、 041 * useURLCheck="true"が指定されたリンクURL以外を拒否することができます。 042 * また、og:linkタグを経由した場合でも、リンクの有効期限を設定することで、 043 * リンクURLの漏洩に対しても、一定時間の経過を持って、アクセスを拒否することができます。 044 * また、リンク時にユーザー情報も埋め込んでいますので(初期値は、ログインユーザー)、 045 * リンクアドレスが他のユーザーに知られた場合でも、アクセスを拒否することができます。 046 * 047 * システムリソースの「URL_CHECK_CRYPT」で暗号復号化のキーを指定可能です。 048 * 指定しない場合はデフォルトのキーが利用されます。 049 * キーの形式はHybsCryptographyに従います。 050 * 051 * フィルターに対してweb.xml でパラメータを設定します。 052 * ・filename :停止時メッセージ表示ファイル名 053 * ・ignoreURL:暗号化されたURLのうち空白に置き換える接頭文字列を指定します。 054 * 外部からアクセスしたURLがロードバランサで内部向けURLに変換されてチェックが動作しないような場合に 055 * 利用します。https://wwwX.のように指定します。通常は設定しません。 056 * ・debug :標準出力に状況を表示します(true/false) 5.10.18.0 (2019/11/29) 057 * ・ommitURL :正規表現で、URLチェックを行わないパターンを記載します 5.10.18.0 (2019/11/29) 058 * ・ommitReferer:ドメイン(ホスト名)を指定すると、このRefererを持つものはURLチェックを行いません。 5.10.18.0 (2019/11/29) 059 * 内部の遷移はチェックを行わず、外部からのリンクのみチェックを行う場合に利用します。 060 * ・ignoreRelative:trueにすると暗号化されたURLのうち、相対パスの箇所(../)を削除して評価します。 5.10.18.0 (2019/11/29) 061 * 062 * 【WEB-INF/web.xml】 063 * <filter> 064 * <filter-name>URLCheckFilter</filter-name> 065 * <filter-class>org.opengion.hayabusa.filter.URLCheckFilter</filter-class> 066 * <init-param> 067 * <param-name>filename</param-name> 068 * <param-value>jsp/custom/refuseAccess.html</param-value> 069 * </init-param> 070 * </filter> 071 * 072 * <filter-mapping> 073 * <filter-name>URLCheckFilter</filter-name> 074 * <url-pattern>/jsp/*</url-pattern> 075 * </filter-mapping> 076 * 077 * @og.group フィルター処理 078 * 079 * @version 4.0 080 * @author Hiroki Nakamura 081 * @since JDK5.0, 082 */ 083public final class URLCheckFilter implements Filter { 084 085 private static final HybsCryptography HYBS_CRYPTOGRAPHY 086 = new HybsCryptography( HybsSystem.sys( "URL_CHECK_CRYPT" ) ); // 5.8.8.0 (2015/06/05) 087 088 private static final String USERID_HEADER = HybsSystem.sys( "USERID_HEADER_NAME" ); // 5.10.18.0 (2019/11/29) 089 090 private String filename = "jsp/custom/refuseAccess.html" ; // 6.3.8.3 (2015/10/03) アクセス拒否時メッセージ表示ファイル名 091 private boolean isDebug ; 092 private boolean isDecode = true; // 5.4.5.0(2012/02/28) URIDecodeするかどうか 093 094 private String ignoreURL ; // 5.8.6.1 (2015/04/17) 飛んできたcheckURLから取り除くURL文字列 095 private String ignoreRelative = "false"; // 5.10.18.1 (2019/12/09) 相対パスの../を無視する 096 private String ommitURL ; // 5.10.11.0 (2019/05/03) URLチェックを行わないURLの正規表現 097 private String ommitReferer; // 5.10.11.0 (2019/05/03) URLチェックを行わないドメイン 098 099 private String encoding = "utf-8"; // 5.10.12.4 (2019/06/21) 日本語対応 100 101 /** 102 * デフォルトコンストラクター 103 * 104 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor. 105 */ 106 public URLCheckFilter() { super(); } // これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。 107 108 /** 109 * フィルター処理本体のメソッドです。 110 * 111 * @og.rev 6.2.0.0 (2015/02/27) new BufferedReader … を、FileUtil.getBufferedReader … に変更。 112 * @og.rev 6.3.1.0 (2015/06/28) nioを使用すると UTF-8とShuft-JISで、エラーになる。 113 * @og.rev 6.3.8.3 (2015/10/03) アクセス拒否を示すメッセージファイルの内容を取り出します。 114 * @og.rev 5.10.12.4 (2019/06/21) 日本語対応(encoding指定) 115 * @og.rev 5.10.16.1 (2019/10/11) デバッグ追加 116 * 117 * @param request ServletRequestオブジェクト 118 * @param response ServletResponseオブジェクト 119 * @param chain FilterChainオブジェクト 120 * @throws ServletException サーブレット関係のエラーが発生した場合、throw されます。 121 */ 122 public void doFilter( final ServletRequest request, 123 final ServletResponse response, 124 final FilterChain chain ) throws IOException, ServletException { 125 126 if( !isValidAccess( request ) ) { 127 if( isDebug ) { 128 System.out.println( " check NG... " ); // 5.10.16.1 129 } 130 131 response.setContentType( "text/html; charset=UTF-8" ); 132 final PrintWriter out = response.getWriter(); 133 out.println( refuseMsg() ); // 6.3.8.3 (2015/10/03) 134 out.flush(); 135 return; 136 } 137 138 request.setAttribute( "RequestEncoding", encoding ); // 5.10.12.1 (2019/06/21) リクエスト変数で送信しておく 139 140 chain.doFilter(request, response); 141 } 142 143 /** 144 * フィルターの初期処理メソッドです。 145 * 146 * フィルターに対してweb.xml で初期パラメータを設定します。 147 * ・filename :停止時メッセージ表示ファイル名(初期:jsp/custom/refuseAccess.html) 148 * ・decode :URLデコードを行ってチェックするか(初期:true) 149 * ・ignoreURL :暗号化されたURLのうち空白に置き換える接頭文字列を指定します(初期:null) 150 * ・debug :URLデコードを行ってチェックするか(初期:false) 151 * 152 * @og.rev 5.4.5.0 (2102/02/28) 153 * @og.rev 5.7.3.2 (2014/02/28) Tomcat8 対応。getRealPath( "/" ) の互換性のための修正。 154 * @og.rev 5.8.6.1 (2015/04/17) DMZのURL変換対応 155 * @og.rev 6.2.4.1 (2015/05/22) REAL_PATH 対応。realPath は、HybsSystem経由で、取得する。 156 * @og.rev 6.3.8.3 (2015/10/03) filenameの初期値設定。 157 * @og.rev 5.10.11.0 (2019/05/03) ommitURL,ommitReferer 158 * @og.rev 5.10.12.4 (2019/06/21) encoding 159 * @og.rev 5.10.18.1 (2019/12/09) 相対パス対応 160 * 161 * @param filterConfig FilterConfigオブジェクト 162 */ 163 public void init(final FilterConfig filterConfig) { 164 165 filename = HybsSystem.getRealPath() + StringUtil.nval( filterConfig.getInitParameter("filename") , filename ); // 6.3.8.3 (2015/10/03) 166 isDecode = StringUtil.nval( filterConfig.getInitParameter("decode"), true ); // 5.4.5.0(2012/02/28) 167 ignoreURL = filterConfig.getInitParameter("ignoreURL"); // 5.8.6.1 (2015/04/17) 168 ignoreRelative = filterConfig.getInitParameter("ignoreRelative"); // 5.10.18.1 (2019/12/09) 169 isDebug = StringUtil.nval( filterConfig.getInitParameter("debug"), false ); 170 171 ommitURL = filterConfig.getInitParameter("ommitURL"); // 5.10.11.0 (2019/05/03) 172 ommitReferer = filterConfig.getInitParameter("ommitReferer"); // 5.10.11.0 (2019/05/03) 173 174 encoding = StringUtil.nval( filterConfig.getInitParameter("encoding"), encoding ); // 5.10.12.4 (2019/06/21) 175 } 176 177 /** 178 * フィルターの終了処理メソッドです。 179 * 180 */ 181 public void destroy() { 182 // ここでは処理を行いません。 183 } 184 185 /** 186 * アクセス拒否を示すメッセージ内容。 187 * 188 * @og.rev 6.3.8.3 (2015/10/03) アクセス拒否を示すメッセージファイルの内容を取り出します。 189 * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更 190 * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。 191 * 192 * @return アクセス拒否を示すメッセージファイルの内容 193 */ 194 private String refuseMsg() { 195 // アクセス拒否を示すメッセージファイルの内容を管理する FileString オブジェクトを構築する。 196 197 // 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更 198 return FileUtil.getValue( filename , HybsConst.UTF_8 ); // 6.4.5.2 (2016/05/06) 199 } 200 201 /** 202 * フィルターの内部状態をチェックするメソッドです。 203 * 204 * @og.rev 5.4.5.0 (2012/02/28) Decode 205 * @og.rev 5.8.6.1 (2015/04/17) DMZのURL変換対応 206 * @og.rev 5.8.8.2 (2015/07/17) マルチバイト対応追加 207 * @og.rev 6.3.8.4 (2015/10/09) デバッグメッセージの追加 208 * @og.rev 6.4.2.0 (2016/01/29) ex.printStackTrace() を、ThrowUtil#ogStackTrace(Throwable) に置き換え。 209 * @og.rev 5.10.11.0 (2019/05/03) ommitURL,ommitReferer 210 * @or.rev 5.10.16 (2019/10/11) デバッグ追加 211 * @og.rev 5.10.18.0 (2019/11/29) ヘッダ認証 212 * @og.rev 5.10.18.1 (2019/12/09) 相対パス対応 213 * 214 * @param request ServletRequestオブジェクト 215 * 216 * @return (true:許可 false:拒否) 217 */ 218 private boolean isValidAccess( final ServletRequest request ) { 219 // 6.3.8.4 (2015/10/09) デバッグメッセージの追加 220 if( isDebug ) { 221 System.out.println( ((HttpServletRequest)request).getRequestURI() ); 222 } 223 224 // 5.10.11.0 (2019/05/03) データ取得位置変更 225 final String queryStr = ((HttpServletRequest)request).getQueryString(); 226 String reqStr = ((HttpServletRequest)request).getRequestURL().toString(); 227 final String referer = ((HttpServletRequest)request).getHeader("REFERER"); 228 229 // 5.10.11.0 referer判定追加 230 // 入っている場合はtrueにする。 231 if(referer != null && ommitReferer != null && referer.indexOf( ommitReferer ) >= 0 ) { 232 if( isDebug ) { 233 System.out.println("URLCheck ommitRef"+reqStr); 234 } 235 return true; 236 } 237 238 // リクエスト変数をURLに追加 239 reqStr = reqStr + (queryStr != null ? "?" + queryStr : ""); 240 241 // 5.10.11.0 ommitURL追加 242 // ommitに合致する場合はtrueにする。 243 if(ommitURL != null && reqStr.matches( ommitURL )) { 244 if( isDebug ) { 245 System.out.println("URLCheck ommitURL"+reqStr); 246 } 247 return true; 248 } 249 250 String checkKey = request.getParameter( HybsSystem.URL_CHECK_KEY ); 251 if( checkKey == null || checkKey.isEmpty() ) { 252 if( isDebug ) { 253// System.out.println( " check NG [ No Check Key ]" ); 254 System.out.println( " check NG [ No Check Key ] = " + reqStr ); // 5.10.16.1 (2019/10/11) reqStr追加 255 } 256 return false; 257 } 258 259 boolean rtn = false; 260 try { 261 checkKey = HYBS_CRYPTOGRAPHY.decrypt( checkKey ).replace( "&", "&" ); 262 263 if( isDebug ) { 264 System.out.println( " checkKey=" + checkKey ); 265 } 266 267 // 5.8.6.1 (2015/04/17) DMZのURL変換対応 (ちょっと整理しておきます) 268 269 final int tmAd = checkKey.lastIndexOf( ",time=" ); 270 final int usAd = checkKey.lastIndexOf( ",userid=" ); 271 272 String url = checkKey.substring( 0 , tmAd ); 273 final long time = Long.parseLong( checkKey.substring( tmAd + 6, usAd ) ); 274 final String userid = checkKey.substring( usAd + 8 ); 275 276 // 4.3.8.0 (2009/08/01) 277 final String[] userArr = StringUtil.csv2Array( userid ); 278 279 // 5.8.6.1 (2015/04/17)ignoreURL対応 280 if( ignoreURL != null && ignoreURL.length()>0 && url.indexOf( ignoreURL ) == 0 ){ 281 url = url.substring( ignoreURL.length() ); 282 } 283 284 // 5.10.18.1 (2019/12/09) ../を削除 285 if ( "true".equals( ignoreRelative ) ) { 286 url = url.replaceAll( "\\.\\./", "" ); 287 } 288 289 if( isDebug ) { 290 System.out.println( " [ignoreURL]=" + ignoreURL ); // 2015/04/17 (2015/04/17) 291 System.out.println( " [ignoreRelative]=" + ignoreRelative ); 292 System.out.println( " [url] =" + url ); 293 System.out.println( " [vtime] =" + time ); 294 System.out.println( " [userid] =" + userid ); 295 } 296 297// String reqStr = ((HttpServletRequest)request).getRequestURL().toString() + "?" + ((HttpServletRequest)request).getQueryString(); 298 // 5.4.5.0 (2012/02/28) URLDecodeを行う 299 if( isDecode ){ 300 if( isDebug ) { 301 System.out.println( "[BeforeURIDecode]="+reqStr ); 302 } 303 reqStr = StringUtil.urlDecode( reqStr ); 304 url = StringUtil.urlDecode( url ); // 5.8.8.2 (2015/07/17) 305 } 306 reqStr = reqStr.substring( 0, reqStr.lastIndexOf( HybsSystem.URL_CHECK_KEY ) -1 ); 307 // String reqStr = ((HttpServletRequest)request).getRequestURL().toString(); 308// final String reqUser = ((HttpServletRequest)request).getRemoteUser(); 309 String reqUser = ((HttpServletRequest)request).getRemoteUser() ; // 5.10.18.0 (2019/11/29) 310 if( USERID_HEADER != null && USERID_HEADER.length() > 0 && ( reqUser == null || reqUser.length() == 0) ) { 311 reqUser = ((HttpServletRequest)request).getHeader( USERID_HEADER ); 312 } 313 314 if( isDebug ) { 315 System.out.println( " [reqURL] =" + reqStr ); 316 System.out.println( " [ctime] =" + System.currentTimeMillis() ); 317 System.out.println( " [reqUser]=" + reqUser ); 318 System.out.println( " endWith=" + reqStr.endsWith( url ) ); 319 System.out.println( " times=" + (System.currentTimeMillis() - time) ); 320 System.out.println( " [userArr.length]=" + userArr.length ); 321 } 322 323 if( reqStr.endsWith( url ) 324 && System.currentTimeMillis() - time < 0 325 && userArr != null && userArr.length > 0 ) { 326 // 4.3.8.0 (2009/08/01) 327 for( int i=0; i<userArr.length; i++ ) { 328 if( isDebug ) { 329 System.out.println( " [userArr] =" + userArr[i] ); // 5.10.16.1 330 } 331 if( "*".equals( userArr[i] ) || reqUser.equals( userArr[i] ) ) { 332 rtn = true; 333 if( isDebug ) { 334 System.out.println( " check OK" ); 335 } 336 break; 337 } 338 } 339 } 340 } 341 catch( final RuntimeException ex ) { 342 if( isDebug ) { 343 final String errMsg = "チェックエラー。 " 344 + " checkKey=" + checkKey 345 + " " + ex.getMessage(); // 5.1.8.0 (2010/07/01) errMsg 修正 346 System.out.println( errMsg ); 347 System.err.println( ThrowUtil.ogStackTrace( ex ) ); // 6.4.2.0 (2016/01/29) 348 } 349 rtn = false; 350 } 351 return rtn; 352 } 353 354 /** 355 * 内部状態を文字列で返します。 356 * 357 * @return このクラスの文字列表示 358 * @og.rtnNotNull 359 */ 360 @Override 361 public String toString() { 362 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 363 .append( "UrlCheckFilter" ) 364 .append( "filename=[" ).append( filename ).append( "],") 365 .append( "isDecode=[" ).append( isDecode ).append( ']'); // 6.0.2.5 (2014/10/31) char を append する。 366 return buf.toString(); 367 } 368}