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 jakarta.servlet.Filter; 022import jakarta.servlet.FilterChain; 023import jakarta.servlet.FilterConfig; 024import jakarta.servlet.ServletException; 025import jakarta.servlet.ServletRequest; 026import jakarta.servlet.ServletResponse; 027import jakarta.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 @Override // Filter 123 public void doFilter( final ServletRequest request, 124 final ServletResponse response, 125 final FilterChain chain ) throws IOException, ServletException { 126 127 if( !isValidAccess( request ) ) { 128 if( isDebug ) { 129 System.out.println( " check NG... " ); // 5.10.16.1 130 } 131 132 response.setContentType( "text/html; charset=UTF-8" ); 133 final PrintWriter out = response.getWriter(); 134 out.println( refuseMsg() ); // 6.3.8.3 (2015/10/03) 135 out.flush(); 136 return; 137 } 138 139 request.setAttribute( "RequestEncoding", encoding ); // 5.10.12.1 (2019/06/21) リクエスト変数で送信しておく 140 141 chain.doFilter(request, response); 142 } 143 144 /** 145 * フィルターの初期処理メソッドです。 146 * 147 * フィルターに対してweb.xml で初期パラメータを設定します。 148 * ・filename :停止時メッセージ表示ファイル名(初期:jsp/custom/refuseAccess.html) 149 * ・decode :URLデコードを行ってチェックするか(初期:true) 150 * ・ignoreURL :暗号化されたURLのうち空白に置き換える接頭文字列を指定します(初期:null) 151 * ・debug :URLデコードを行ってチェックするか(初期:false) 152 * 153 * @og.rev 5.4.5.0 (2102/02/28) 154 * @og.rev 5.7.3.2 (2014/02/28) Tomcat8 対応。getRealPath( "/" ) の互換性のための修正。 155 * @og.rev 5.8.6.1 (2015/04/17) DMZのURL変換対応 156 * @og.rev 6.2.4.1 (2015/05/22) REAL_PATH 対応。realPath は、HybsSystem経由で、取得する。 157 * @og.rev 6.3.8.3 (2015/10/03) filenameの初期値設定。 158 * @og.rev 5.10.11.0 (2019/05/03) ommitURL,ommitReferer 159 * @og.rev 5.10.12.4 (2019/06/21) encoding 160 * @og.rev 5.10.18.1 (2019/12/09) 相対パス対応 161 * 162 * @param filterConfig FilterConfigオブジェクト 163 */ 164 @Override // Filter 165 public void init(final FilterConfig filterConfig) { 166 167 filename = HybsSystem.getRealPath() + StringUtil.nval( filterConfig.getInitParameter("filename") , filename ); // 6.3.8.3 (2015/10/03) 168 isDecode = StringUtil.nval( filterConfig.getInitParameter("decode"), true ); // 5.4.5.0(2012/02/28) 169 ignoreURL = filterConfig.getInitParameter("ignoreURL"); // 5.8.6.1 (2015/04/17) 170 ignoreRelative = filterConfig.getInitParameter("ignoreRelative"); // 5.10.18.1 (2019/12/09) 171 isDebug = StringUtil.nval( filterConfig.getInitParameter("debug"), false ); 172 173 ommitURL = filterConfig.getInitParameter("ommitURL"); // 5.10.11.0 (2019/05/03) 174 ommitReferer = filterConfig.getInitParameter("ommitReferer"); // 5.10.11.0 (2019/05/03) 175 176 encoding = StringUtil.nval( filterConfig.getInitParameter("encoding"), encoding ); // 5.10.12.4 (2019/06/21) 177 } 178 179 /** 180 * フィルターの終了処理メソッドです。 181 * 182 */ 183 @Override // Filter 184 public void destroy() { 185 // ここでは処理を行いません。 186 } 187 188 /** 189 * アクセス拒否を示すメッセージ内容。 190 * 191 * @og.rev 6.3.8.3 (2015/10/03) アクセス拒否を示すメッセージファイルの内容を取り出します。 192 * @og.rev 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更 193 * @og.rev 6.4.5.2 (2016/05/06) fukurou.util.FileString から、fukurou.util.FileUtil に移動。 194 * 195 * @return アクセス拒否を示すメッセージファイルの内容 196 */ 197 private String refuseMsg() { 198 // アクセス拒否を示すメッセージファイルの内容を管理する FileString オブジェクトを構築する。 199 200 // 6.4.5.1 (2016/04/28) FileStringのコンストラクター変更 201 return FileUtil.getValue( filename , HybsConst.UTF_8 ); // 6.4.5.2 (2016/05/06) 202 } 203 204 /** 205 * フィルターの内部状態をチェックするメソッドです。 206 * 207 * @og.rev 5.4.5.0 (2012/02/28) Decode 208 * @og.rev 5.8.6.1 (2015/04/17) DMZのURL変換対応 209 * @og.rev 5.8.8.2 (2015/07/17) マルチバイト対応追加 210 * @og.rev 6.3.8.4 (2015/10/09) デバッグメッセージの追加 211 * @og.rev 6.4.2.0 (2016/01/29) ex.printStackTrace() を、ThrowUtil#ogStackTrace(Throwable) に置き換え。 212 * @og.rev 5.10.11.0 (2019/05/03) ommitURL,ommitReferer 213 * @or.rev 5.10.16 (2019/10/11) デバッグ追加 214 * @og.rev 5.10.18.0 (2019/11/29) ヘッダ認証 215 * @og.rev 5.10.18.1 (2019/12/09) 相対パス対応 216 * 217 * @param request ServletRequestオブジェクト 218 * 219 * @return (true:許可 false:拒否) 220 */ 221 private boolean isValidAccess( final ServletRequest request ) { 222 // 6.3.8.4 (2015/10/09) デバッグメッセージの追加 223 if( isDebug ) { 224 System.out.println( ((HttpServletRequest)request).getRequestURI() ); 225 } 226 227 // 5.10.11.0 (2019/05/03) データ取得位置変更 228 String reqStr = ((HttpServletRequest)request).getRequestURL().toString(); 229 230 // 5.10.11.0 referer判定追加 231 // 入っている場合はtrueにする。 232 final String referer = ((HttpServletRequest)request).getHeader("REFERER"); 233 if(referer != null && ommitReferer != null && referer.indexOf( ommitReferer ) >= 0 ) { 234 if( isDebug ) { 235 System.out.println("URLCheck ommitRef"+reqStr); 236 } 237 return true; 238 } 239 240 // リクエスト変数をURLに追加 241 final String queryStr = ((HttpServletRequest)request).getQueryString(); 242 reqStr = reqStr + (queryStr != null ? "?" + queryStr : ""); 243 244 // 5.10.11.0 ommitURL追加 245 // ommitに合致する場合はtrueにする。 246 if(ommitURL != null && reqStr.matches( ommitURL )) { 247 if( isDebug ) { 248 System.out.println("URLCheck ommitURL"+reqStr); 249 } 250 return true; 251 } 252 253 String checkKey = request.getParameter( HybsSystem.URL_CHECK_KEY ); 254 if( checkKey == null || checkKey.isEmpty() ) { 255 if( isDebug ) { 256// System.out.println( " check NG [ No Check Key ]" ); 257 System.out.println( " check NG [ No Check Key ] = " + reqStr ); // 5.10.16.1 (2019/10/11) reqStr追加 258 } 259 return false; 260 } 261 262 boolean rtn = false; 263 try { 264 checkKey = HYBS_CRYPTOGRAPHY.decrypt( checkKey ).replace( "&", "&" ); 265 266 if( isDebug ) { 267 System.out.println( " checkKey=" + checkKey ); 268 } 269 270 // 5.8.6.1 (2015/04/17) DMZのURL変換対応 (ちょっと整理しておきます) 271 272 final int tmAd = checkKey.lastIndexOf( ",time=" ); 273 final int usAd = checkKey.lastIndexOf( ",userid=" ); 274 275 String url = checkKey.substring( 0 , tmAd ); 276 final long time = Long.parseLong( checkKey.substring( tmAd + 6, usAd ) ); 277 final String userid = checkKey.substring( usAd + 8 ); 278 279 // 4.3.8.0 (2009/08/01) 280 final String[] userArr = StringUtil.csv2Array( userid ); 281 282 // 5.8.6.1 (2015/04/17)ignoreURL対応 283 if( ignoreURL != null && ignoreURL.length()>0 && url.indexOf( ignoreURL ) == 0 ){ 284 url = url.substring( ignoreURL.length() ); 285 } 286 287 // 5.10.18.1 (2019/12/09) ../を削除 288 if ( "true".equals( ignoreRelative ) ) { 289 url = url.replaceAll( "\\.\\./", "" ); 290 } 291 292 if( isDebug ) { 293 System.out.println( " [ignoreURL]=" + ignoreURL ); // 2015/04/17 (2015/04/17) 294 System.out.println( " [ignoreRelative]=" + ignoreRelative ); 295 System.out.println( " [url] =" + url ); 296 System.out.println( " [vtime] =" + time ); 297 System.out.println( " [userid] =" + userid ); 298 } 299 300// String reqStr = ((HttpServletRequest)request).getRequestURL().toString() + "?" + ((HttpServletRequest)request).getQueryString(); 301 // 5.4.5.0 (2012/02/28) URLDecodeを行う 302 if( isDecode ){ 303 if( isDebug ) { 304 System.out.println( "[BeforeURIDecode]="+reqStr ); 305 } 306 reqStr = StringUtil.urlDecode( reqStr ); 307 url = StringUtil.urlDecode( url ); // 5.8.8.2 (2015/07/17) 308 } 309 reqStr = reqStr.substring( 0, reqStr.lastIndexOf( HybsSystem.URL_CHECK_KEY ) -1 ); 310 // String reqStr = ((HttpServletRequest)request).getRequestURL().toString(); 311// final String reqUser = ((HttpServletRequest)request).getRemoteUser(); 312 String reqUser = ((HttpServletRequest)request).getRemoteUser() ; // 5.10.18.0 (2019/11/29) 313 if( USERID_HEADER != null && USERID_HEADER.length() > 0 && ( reqUser == null || reqUser.length() == 0) ) { 314 reqUser = ((HttpServletRequest)request).getHeader( USERID_HEADER ); 315 } 316 317 if( isDebug ) { 318 System.out.println( " [reqURL] =" + reqStr ); 319 System.out.println( " [ctime] =" + System.currentTimeMillis() ); 320 System.out.println( " [reqUser]=" + reqUser ); 321 System.out.println( " endWith=" + reqStr.endsWith( url ) ); 322 System.out.println( " times=" + (System.currentTimeMillis() - time) ); 323 System.out.println( " [userArr.length]=" + userArr.length ); 324 } 325 326 if( reqStr.endsWith( url ) 327 && System.currentTimeMillis() - time < 0 328 && userArr != null && userArr.length > 0 ) { 329 // 4.3.8.0 (2009/08/01) 330 for( int i=0; i<userArr.length; i++ ) { 331 if( isDebug ) { 332 System.out.println( " [userArr] =" + userArr[i] ); // 5.10.16.1 333 } 334 if( "*".equals( userArr[i] ) || reqUser.equals( userArr[i] ) ) { 335 rtn = true; 336 if( isDebug ) { 337 System.out.println( " check OK" ); 338 } 339 break; 340 } 341 } 342 } 343 } 344 catch( final RuntimeException ex ) { 345 if( isDebug ) { 346 final String errMsg = "チェックエラー。 " 347 + " checkKey=" + checkKey 348 + " " + ex.getMessage(); // 5.1.8.0 (2010/07/01) errMsg 修正 349 System.out.println( errMsg ); 350 System.err.println( ThrowUtil.ogStackTrace( ex ) ); // 6.4.2.0 (2016/01/29) 351 } 352 rtn = false; 353 } 354 return rtn; 355 } 356 357 /** 358 * 内部状態を文字列で返します。 359 * 360 * @return このクラスの文字列表示 361 * @og.rtnNotNull 362 */ 363 @Override // Object 364 public String toString() { 365 final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE ) 366 .append( "UrlCheckFilter" ) 367 .append( "filename=[" ).append( filename ).append( "],") 368 .append( "isDecode=[" ).append( isDecode ).append( ']'); // 6.0.2.5 (2014/10/31) char を append する。 369 return buf.toString(); 370 } 371}