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