1一152期六合图库资料
posts - 110, comments - 100, trackbacks - 0, articles - 7
  BlogJava :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

2015年7月19日

轉自:http://blog.csdn.net/jeffreynicole/article/details/46953059 


一個性能較好的web服務器jvm參數配置:


  1. -server //服務器模式  
  2. -Xmx2g //JVM最大允許分配的堆內存,按需分配  
  3. -Xms2g //JVM初始分配的堆內存,一般和Xmx配置成一樣以避免每次gc后JVM重新分配內存。  
  4. -Xmn256m //年輕代內存大小,整個JVM內存=年輕代 + 年老代 + 持久代  
  5. -XX:PermSize=128m //持久代內存大小  
  6. -Xss256k //設置每個線程的堆棧大小  
  7. -XX:+DisableExplicitGC //忽略手動調用GC, System.gc()的調用就會變成一個空調用,完全不觸發GC  
  8. -XX:+UseConcMarkSweepGC //并發標記清除(CMS)收集器  
  9. -XX:+CMSParallelRemarkEnabled //降低標記停頓  
  10. -XX:+UseCMSCompactAtFullCollection //在FULL GC的時候對年老代的壓縮  
  11. -XX:LargePageSizeInBytes=128m //內存頁的大小  
  12. -XX:+UseFastAccessorMethods //原始類型的快速優化  
  13. -XX:+UseCMSInitiatingOccupancyOnly //使用手動定義初始化定義開始CMS收集  
  14. -XX:CMSInitiatingOccupancyFraction=70 //使用cms作為垃圾回收使用70%后開始CMS收集  


說明:

-Xmn和-Xmx之比大概是1:9,如果把新生代內存設置得太大會導致young gc時間較長

一個好的Web系統應該是每次http請求申請內存都能在young gc回收掉,full gc永不發生,當然這是最理想的情況

xmn的值應該是保證夠用(夠http并發請求之用)的前提下設置得盡量小

web服務器和游戲服務器的配置思路不太一樣,最重要的區別是對游戲服務器的xmn即年輕代設置比較大,和Xmx大概1:3的關系,因為游戲服務器一般是長連接,在保持一定的并發量后需要較大的年輕代堆內存,如果設置得大小了會經常引發young gc


  • 對JVM的簡介


由上圖可以看出jvm堆內存的分類情況,JVM內存被分成多個獨立的部分。
廣泛地說,JVM堆內存被分為兩部分——年輕代(Young Generation)和老年代(Old Generation)。


  • 年輕代
年輕代是所有新對象產生的地方。當年輕代內存空間被用完時,就會觸發垃圾回收。這個垃圾回收叫做Minor GC。年輕代被分為3個部分——Enden區和兩個Survivor區。


年輕代空間的要點:
大多數新建的對象都位于Eden區。
當Eden區被對象填滿時,就會執行Minor GC。并把所有存活下來的對象轉移到其中一個survivor區。
Minor GC同樣會檢查存活下來的對象,并把它們轉移到另一個survivor區。這樣在一段時間內,總會有一個空的survivor區。
經過多次GC周期后,仍然存活下來的對象會被轉移到年老代內存空間。通常這是在年輕代有資格提升到年老代前通過設定年齡閾值來完成的。

  • 年老代
年老代內存里包含了長期存活的對象和經過多次Minor GC后依然存活下來的對象。通常會在老年代內存被占滿時進行垃圾回收。老年代的垃圾收集叫做Major GC。Major GC會花費更多的時間。


Stop the World事件
所有的垃圾收集都是“Stop the World”事件,因為所有的應用線程都會停下來直到操作完成(所以叫“Stop the World”)。

因為年輕代里的對象都是一些臨時(short-lived )對象,執行Minor GC非常快,所以應用不會受到(“Stop the World”)影響。

由于Major GC會檢查所有存活的對象,因此會花費更長的時間。應該盡量減少Major GC。因為Major GC會在垃圾回收期間讓你的應用反應遲鈍,所以如果你有一個需要快速響應的應用發生多次Major GC,你會看到超時錯誤。

垃圾回收時間取決于垃圾回收策略。這就是為什么有必要去監控垃圾收集和對垃圾收集進行調優。從而避免要求快速響應的應用出現超時錯誤。


  • 永久代
永久代或者“Perm Gen”包含了JVM需要的應用元數據,這些元數據描述了在應用里使用的類和方法。注意,永久代不是Java堆內存的一部分。
永久代存放JVM運行時使用的類。永久代同樣包含了Java SE庫的類和方法。永久代的對象在full GC時進行垃圾收集。


方法區
方法區是永久代空間的一部分,并用來存儲類型信息(運行時常量和靜態變量)和方法代碼和構造函數代碼。


內存池
如果JVM實現支持,JVM內存管理會為創建內存池,用來為不變對象創建對象池。字符串池就是內存池類型的一個很好的例子。內存池可以屬于堆或者永久代,這取決于JVM內存管理的實現。


運行時常量池
運行時常量池是每個類常量池的運行時代表。它包含了類的運行時常量和靜態方法。運行時常量池是方法區的一部分。


Java棧內存
Java棧內存用于運行線程。它們包含了方法里的臨時數據、堆里其它對象引用的特定數據。

Java垃圾回收
Java垃圾回收會找出沒用的對象,把它從內存中移除并釋放出內存給以后創建的對象使用。Java程序語言中的一個最大優點是自動垃圾回收,不像其他的程序語言那樣需要手動分配和釋放內存,比如C語言。

垃圾收集器是一個后臺運行程序。它管理著內存中的所有對象并找出沒被引用的對象。所有的這些未引用的對象都會被刪除,回收它們的空間并分配給其他對象。

一個基本的垃圾回收過程涉及三個步驟:
標記:這是第一步。在這一步,垃圾收集器會找出哪些對象正在使用和哪些對象不在使用。
正常清除:垃圾收集器清會除不在使用的對象,回收它們的空間分配給其他對象。
壓縮清除:為了提升性能,壓縮清除會在刪除沒用的對象后,把所有存活的對象移到一起。這樣可以提高分配新對象的效率。


簡單標記和清除方法存在兩個問題:
效率很低。因為大多數新建對象都會成為“沒用對象”。
經過多次垃圾回收周期的對象很有可能在以后的周期也會存活下來。
上面簡單清除方法的問題在于Java垃圾收集的分代回收的,而且在堆內存里有年輕代和年老代兩個區域。


  • Java垃圾回收類型
這里有五種可以在應用里使用的垃圾回收類型。

僅需要使用JVM開關就可以在我們的應用里啟用垃圾回收策略。

Serial GC(-XX:+UseSerialGC):Serial GC使用簡單的標記、清除、壓縮方法對年輕代和年老代進行垃圾回收,即Minor GC和Major GC。Serial GC在client模式(客戶端模式)很有用,比如在簡單的獨立應用和CPU配置較低的機器。這個模式對占有內存較少的應用很管用。
Parallel GC(-XX:+UseParallelGC):除了會產生N個線程來進行年輕代的垃圾收集外,Parallel GC和Serial GC幾乎一樣。這里的N是系統CPU的核數。我們可以使用 -XX:ParallelGCThreads=n 這個JVM選項來控制線程數量。并行垃圾收集器也叫throughput收集器。因為它使用了多CPU加快垃圾回收性能。Parallel GC在進行年老代垃圾收集時使用單線程。
Parallel Old GC(-XX:+UseParallelOldGC):和Parallel GC一樣。不同之處,Parallel Old GC在年輕代垃圾收集和年老代垃圾回收時都使用多線程收集。
并發標記清除(CMS)收集器(-XX:+UseConcMarkSweepGC):CMS收集器也被稱為短暫停頓并發收集器。它是對年老代進行垃圾收集的。CMS收集器通過多線程并發進行垃圾回收,盡量減少垃圾收集造成的停頓。CMS收集器對年輕代進行垃圾回收使用的算法和Parallel收集器一樣。這個垃圾收集器適用于不能忍受長時間停頓要求快速響應的應用。可使用 -XX:ParallelCMSThreads=n JVM選項來限制CMS收集器的線程數量。
G1垃圾收集器(-XX:+UseG1GC) G1(Garbage First):垃圾收集器是在Java 7后才可以使用的特性,它的長遠目標時代替CMS收集器。G1收集器是一個并行的、并發的和增量式壓縮短暫停頓的垃圾收集器。G1收集器和其他的收集器運行方式不一樣,不區分年輕代和年老代空間。它把堆空間劃分為多個大小相等的區域。當進行垃圾收集時,它會優先收集存活對象較少的區域,因此叫“Garbage First”。

posted @ 2015-07-19 22:57 云云 閱讀(925) | 評論 (0)編輯 收藏

2014年9月28日

posted @ 2014-09-28 23:45 云云| 編輯 收藏

     摘要: class文件簡介及加載     Java編譯器編譯好Java文件之后,產生.class 文件在磁盤中。這種class文件是二進制文件,內容是只有JVM虛擬機能夠識別的機器碼。JVM虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析.class 文件內的信息,生成對應的 Class對象:     &nb...  閱讀全文

posted @ 2014-09-28 23:44 云云| 編輯 收藏

 
package com.qiyi.appstore.util;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.qiyi.appstore.exception.AppStoreException;
import com.qiyi.cloud.user.ApiCode;
public class XssUtils {
private static final Logger logger=LoggerFactory.getLogger(XssUtils.class);
public static String getSafeStringXSS(String s){
      if (StringUtils.isBlank(s)) {  
          return s;  
      }  
      StringBuilder sb = new StringBuilder(s.length() + 16);  
      for (int i = 0; i < s.length(); i++) {  
          char c = s.charAt(i);  
          switch (c) {  
          case '<':  
              sb.append("&lt;");  
              break; 
          case '>':  
              sb.append("&gt;");  
              break;  
          case '\'':  
              sb.append("&prime;");// &acute;");  
              break;  
          case '′':  
              sb.append("&prime;");// &acute;");  
              break;  
          case '\"':  
              sb.append("&quot;");  
              break;  
          case '"':  
              sb.append("&quot;");  
              break;  
          case '&':  
              sb.append("&");  
              break;  
          case '#':  
              sb.append("#");  
              break;  
          case '\\':  
              sb.append('¥');  
              break; 
          case '=':  
              sb.append("=");  
              break;
          default:  
              sb.append(c);  
              break;  
          }  
      }  
      return sb.toString(); 
  }
public static <T> void getXssSaftBean(Class<?> clz,T bean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException{
String classname = clz.getSimpleName();
logger.info("map target class name is {} .",classname);
Field[] fields = clz.getDeclaredFields();
for(Field field : fields){
Class<?> type = field.getType();
if(type.equals(String.class)){
String fieldname = field.getName();
String value = BeanUtils.getProperty(bean, fieldname);
if(StringUtils.isNotBlank(value)){
BeanUtils.setProperty(bean, fieldname, getSafeStringXSS(value));
}
}
}
}
}

posted @ 2014-09-28 13:49 云云 閱讀(355) | 評論 (0)編輯 收藏

2014年1月16日

提升tomcat 性能 apr擴展lib
使用apr類庫 可以讓tomcat的性能提升到3到4倍  
目前項目中都使用這樣的配置
<Connector port="8080" protocol="org.apache.coyote.http11.Http11AprProtocol" URIEncoding="UTF-8"
               enableLookups="false"
               acceptCount="300"
               connectionTimeout="20000"
               disableUploadTimeout="true" maxThreads="1000" maxSpareThreads="50" minSpareThreads="25"
               redirectPort="8443" />


catalia.sh 
CATALINA_OPTS="$CATALINA_OPTS -Djava.library.path=/usr/local/apr/lib"

posted @ 2014-01-16 14:50 云云 閱讀(604) | 評論 (0)編輯 收藏

2014年1月15日

     摘要: public static boolean acquireLock(String lock) {    // 1. 通過SETNX試圖獲取一個lock    boolean success = false;    Jedis jedis = pool.getResource();...  閱讀全文

posted @ 2014-01-15 19:00 云云 閱讀(11919) | 評論 (1)編輯 收藏

2014年1月9日

對eclipse的默認配置很不爽,黑色字體白色底好刺眼,而且字體習慣用Courier New
改變背景顏色:
windows->Preferences->General->Editor->Text Editors
右邊選擇Appearance color options 
選Background color 選擇背景顏色
個人比較舒服的豆沙綠色和黑色背景,但黑色背景還要把其他的字體顏色也改了才好看,而且豆沙綠色跟默認的字體顏色搭配的很好。
豆沙綠色(色調:85   飽和度:123   亮度:205 )
據說這個色調是眼科專家配的, 因其顏色比較柔和,據說閱讀的時候用這種顏色做背景有利于保護眼睛, word底色就許多人設置成豆沙綠色。
xml的字體調整: 
window--preferences--General--appearance--colors and fonts--Basic-- "Text font "  
然后點change,可以設置字體,我喜歡Courier New
Java的字體調整: 
window--preferences--General--appearance--colors and fonts--java 

posted @ 2014-01-09 16:41 云云 閱讀(1443) | 評論 (0)編輯 收藏

2013年12月5日

有時候在項目中 會變化路徑 把原有路徑的文件拷到新的路徑下面
再刪除原來不想的路徑再提交一次 這樣以來 原來的路徑確實不存在了
但是拷過來的文件帶有原來路徑的svn信息 這樣以來 在提交的時候 就無法提交
想要文件按照的路徑提交 但始終svn還是再往以前的路徑提交 并提示你路徑不存在
在網上搜了下 如何刪除文件自帶的svn路徑信息
按照下面的方式來操作即可

Windows Registry Editor Version 5.00
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN] 
@="刪除該目錄下面.svn文件"
[HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Folder\shell\DeleteSVN\command] 
@="cmd.exe /c \"TITLE Removing SVN Folders in %1 && COLOR 9A && FOR /r \"%1\" %%f IN (.svn) DO RD /s /q \"%%f\" \""


把上面這段文字保存問一個Done.reg文件
然后執行,導入到注冊表
就會在你右鍵一個文件夾的時候多出來一個菜單"刪除該目錄下面.svn文件"
執行該命令即可

 

posted @ 2013-12-05 17:17 云云 閱讀(374) | 評論 (0)編輯 收藏

2013年11月26日

在ibatis中不需要關注這些參數 而轉到mybatis后 如果字段值為空 必須設置jdbcType

insert into testTable
   (ID,
   NAME,
   DESCRIPTION,
   IMAGEURL,
   LINKURL,
   ISALWAYS,
   ISDISPLAYINDEX,
   DISPLAYWEIGHT,
   STARTTIME,
   ENDTIME,
   CREATOR,
   CREATTIME,
   MODIFYTIME)
  values
   (SEQ_ACTIVITY_TABLE.NEXTVAL,
   #{name},
   #{desc,jdbcType=VARCHAR},
   #{imageUrl,jdbcType=VARCHAR},
   #{linkUrl,jdbcType=VARCHAR},
   #{isAlways,jdbcType=CHAR},
   #{isDisplayIndex,jdbcType=CHAR},
   #{displayWeight,jdbcType=VARCHAR},
   #{startTime,jdbcType=DATE},
   #{endTime,jdbcType=DATE},
   #{creator,jdbcType=VARCHAR},
   sysdate,
   sysdate
   )
 </insert>

這些設置之多,太煩了,最讓人煩的是  jdbcType = DATE,類型還必須大寫,不能小寫。
如下面的例子,將DATE 改成 Date 。結果讓人很抓狂啊!!!
insert into testTable
   (ID,
   NAME,
   DESCRIPTION,
   IMAGEURL,
   LINKURL,
   ISALWAYS,
   ISDISPLAYINDEX,
   DISPLAYWEIGHT,
   STARTTIME,
   ENDTIME,
   CREATOR,
   CREATTIME,
   MODIFYTIME)
  values
   (SEQ_ACTIVITY_TABLE.NEXTVAL,
   #{name},
   #{desc,jdbcType=VARCHAR},
   #{imageUrl,jdbcType=VARCHAR},
   #{linkUrl,jdbcType=VARCHAR},
   #{isAlways,jdbcType=CHAR},
   #{isDisplayIndex,jdbcType=CHAR},
   #{displayWeight,jdbcType=VARCHAR},
   #{startTime,jdbcType=Date},
   #{endTime,jdbcType=DATE},
   #{creator,jdbcType=VARCHAR},
   sysdate,
   sysdate
   )
 </insert>
org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.builder.BuilderException: Error resolving JdbcType. Cause: java.lang.IllegalArgumentException: No enum const class org.apache.ibatis.type.JdbcType.Date
	org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:75)
	org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:368)
更坑爹的在后面,上面insert時的時候用#{endTime,jdbcType=DATE},可以將時間插入成功,且可以精確到時分秒
但如果在update語句中也這樣使用,那你得到的只會有日期,這夠坑爹的了吧 ,尼瑪  比起ibatis方便之處差遠了
要想在update語句中 將時間格式化成時分秒 不得不再加一個類型 如下面:
startTime = #{startTime,javaType=DATE, jdbcType=VARCHAR}













posted @ 2013-11-26 21:02 云云 閱讀(20145) | 評論 (1)編輯 收藏

2013年11月5日

CSRF 背景與介紹

CSRF(Cross Site Request Forgery, 跨站域請求偽造)是一種網絡的攻擊方式,它在 2007 年曾被列為互聯網 20 大安全隱患之一。其他安全隱患,比如 SQL 腳本注入,跨站域腳本攻擊等在近年來已經逐漸為眾人熟知,很多網站也都針對他們進行了防御。然而,對于大多數人來說,CSRF 卻依然是一個陌生的概念。即便是大名鼎鼎的 Gmail, 在 2007 年底也存在著 CSRF 漏洞,從而被黑客攻擊而使 Gmail 的用戶造成巨大的損失。

CSRF 攻擊實例

CSRF 攻擊可以在受害者毫不知情的情況下以受害者名義偽造請求發送給受攻擊站點,從而在并未授權的情況下執行在權限保護之下的操作。比如說,受害者 Bob 在銀行有一筆存款,通過對銀行的網站發送請求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2 可以使 Bob 把 1000000 的存款轉到 bob2 的賬號下。通常情況下,該請求發送到網站后,服務器會先驗證該請求是否來自一個合法的 session,并且該 session 的用戶 Bob 已經成功登陸。黑客 Mallory 自己在該銀行也有賬戶,他知道上文中的 URL 可以把錢進行轉帳操作。Mallory 可以自己發送一個請求給銀行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory。但是這個請求來自 Mallory 而非 Bob,他不能通過安全認證,因此該請求不會起作用。這時,Mallory 想到使用 CSRF 的攻擊方式,他先自己做一個網站,在網站中放入如下代碼: src=”http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory ”,并且通過廣告等誘使 Bob 來訪問他的網站。當 Bob 訪問該網站時,上述 url 就會從 Bob 的瀏覽器發向銀行,而這個請求會附帶 Bob 瀏覽器中的 cookie 一起發向銀行服務器。大多數情況下,該請求會失敗,因為他要求 Bob 的認證信息。但是,如果 Bob 當時恰巧剛訪問他的銀行后不久,他的瀏覽器與銀行網站之間的 session 尚未過期,瀏覽器的 cookie 之中含有 Bob 的認證信息。這時,悲劇發生了,這個 url 請求就會得到響應,錢將從 Bob 的賬號轉移到 Mallory 的賬號,而 Bob 當時毫不知情。等以后 Bob 發現賬戶錢少了,即使他去銀行查詢日志,他也只能發現確實有一個來自于他本人的合法請求轉移了資金,沒有任何被攻擊的痕跡。而 Mallory 則可以拿到錢后逍遙法外。

CSRF 攻擊的對象

在討論如何抵御 CSRF 之前,先要明確 CSRF 攻擊的對象,也就是要保護的對象。從以上的例子可知,CSRF 攻擊是黑客借助受害者的 cookie 騙取服務器的信任,但是黑客并不能拿到 cookie,也看不到 cookie 的內容。另外,對于服務器返回的結果,由于瀏覽器同源策略的限制,黑客也無法進行解析。因此,黑客無法從返回的結果中得到任何東西,他所能做的就是給服務器發送請求,以執行請求中所描述的命令,在服務器端直接改變數據的值,而非竊取服務器中的數據。所以,我們要保護的對象是那些可以直接產生數據改變的服務,而對于讀取數據的服務,則不需要進行 CSRF 的保護。比如銀行系統中轉賬的請求會直接改變賬戶的金額,會遭到 CSRF 攻擊,需要保護。而查詢余額是對金額的讀取操作,不會改變數據,CSRF 攻擊無法解析服務器返回的結果,無需保護。

當前防御 CSRF 的幾種策略

在業界目前防御 CSRF 攻擊主要有三種策略:驗證 HTTP Referer 字段;在請求地址中添加 token 并驗證;在 HTTP 頭中自定義屬性并驗證。下面就分別對這三種策略進行詳細介紹。

驗證 HTTP Referer 字段

根據 HTTP 協議,在 HTTP 頭中有一個字段叫 Referer,它記錄了該 HTTP 請求的來源地址。在通常情況下,訪問一個安全受限頁面的請求來自于同一個網站,比如需要訪問 http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory,用戶必須先登陸 bank.example,然后通過點擊頁面上的按鈕來觸發轉賬事件。這時,該轉帳請求的 Referer 值就會是轉賬按鈕所在的頁面的 URL,通常是以 bank.example 域名開頭的地址。而如果黑客要對銀行網站實施 CSRF 攻擊,他只能在他自己的網站構造請求,當用戶通過黑客的網站發送請求到銀行時,該請求的 Referer 是指向黑客自己的網站。因此,要防御 CSRF 攻擊,銀行網站只需要對于每一個轉賬請求驗證其 Referer 值,如果是以 bank.example 開頭的域名,則說明該請求是來自銀行網站自己的請求,是合法的。如果 Referer 是其他網站的話,則有可能是黑客的 CSRF 攻擊,拒絕該請求。

這種方法的顯而易見的好處就是簡單易行,網站的普通開發人員不需要操心 CSRF 的漏洞,只需要在最后給所有安全敏感的請求統一增加一個攔截器來檢查 Referer 的值就可以。特別是對于當前現有的系統,不需要改變當前系統的任何已有代碼和邏輯,沒有風險,非常便捷。

然而,這種方法并非萬無一失。Referer 的值是由瀏覽器提供的,雖然 HTTP 協議上有明確的要求,但是每個瀏覽器對于 Referer 的具體實現可能有差別,并不能保證瀏覽器自身沒有安全漏洞。使用驗證 Referer 值的方法,就是把安全性都依賴于第三方(即瀏覽器)來保障,從理論上來講,這樣并不安全。事實上,對于某些瀏覽器,比如 IE6 或 FF2,目前已經有一些方法可以篡改 Referer 值。如果 bank.example 網站支持 IE6 瀏覽器,黑客完全可以把用戶瀏覽器的 Referer 值設為以 bank.example 域名開頭的地址,這樣就可以通過驗證,從而進行 CSRF 攻擊。

即便是使用最新的瀏覽器,黑客無法篡改 Referer 值,這種方法仍然有問題。因為 Referer 值會記錄下用戶的訪問來源,有些用戶認為這樣會侵犯到他們自己的隱私權,特別是有些組織擔心 Referer 值會把組織內網中的某些信息泄露到外網中。因此,用戶自己可以設置瀏覽器使其在發送請求時不再提供 Referer。當他們正常訪問銀行網站時,網站會因為請求沒有 Referer 值而認為是 CSRF 攻擊,拒絕合法用戶的訪問。

在請求地址中添加 token 并驗證

CSRF 攻擊之所以能夠成功,是因為黑客可以完全偽造用戶的請求,該請求中所有的用戶驗證信息都是存在于 cookie 中,因此黑客可以在不知道這些驗證信息的情況下直接利用用戶自己的 cookie 來通過安全驗證。要抵御 CSRF,關鍵在于在請求中放入黑客所不能偽造的信息,并且該信息不存在于 cookie 之中。可以在 HTTP 請求中以參數的形式加入一個隨機產生的 token,并在服務器端建立一個攔截器來驗證這個 token,如果請求中沒有 token 或者 token 內容不正確,則認為可能是 CSRF 攻擊而拒絕該請求。

這種方法要比檢查 Referer 要安全一些,token 可以在用戶登陸后產生并放于 session 之中,然后在每次請求時把 token 從 session 中拿出,與請求中的 token 進行比對,但這種方法的難點在于如何把 token 以參數的形式加入請求。對于 GET 請求,token 將附在請求地址之后,這樣 URL 就變成 http://url?csrftoken=tokenvalue。 而對于 POST 請求來說,要在 form 的最后加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,這樣就把 token 以參數的形式加入請求了。但是,在一個網站中,可以接受請求的地方非常多,要對于每一個請求都加上 token 是很麻煩的,并且很容易漏掉,通常使用的方法就是在每次頁面加載時,使用 javascript 遍歷整個 dom 樹,對于 dom 中所有的 a 和 form 標簽后加入 token。這樣可以解決大部分的請求,但是對于在頁面加載之后動態生成的 html 代碼,這種方法就沒有作用,還需要程序員在編碼時手動添加 token。

該方法還有一個缺點是難以保證 token 本身的安全。特別是在一些論壇之類支持用戶自己發表內容的網站,黑客可以在上面發布自己個人網站的地址。由于系統也會在這個地址后面加上 token,黑客可以在自己的網站上得到這個 token,并馬上就可以發動 CSRF 攻擊。為了避免這一點,系統可以在添加 token 的時候增加一個判斷,如果這個鏈接是鏈到自己本站的,就在后面添加 token,如果是通向外網則不加。不過,即使這個 csrftoken 不以參數的形式附加在請求之中,黑客的網站也同樣可以通過 Referer 來得到這個 token 值以發動 CSRF 攻擊。這也是一些用戶喜歡手動關閉瀏覽器 Referer 功能的原因。

在 HTTP 頭中自定義屬性并驗證

這種方法也是使用 token 并進行驗證,和上一種方法不同的是,這里并不是把 token 以參數的形式置于 HTTP 請求之中,而是把它放到 HTTP 頭中自定義的屬性里。通過 XMLHttpRequest 這個類,可以一次性給所有該類請求加上 csrftoken 這個 HTTP 頭屬性,并把 token 值放入其中。這樣解決了上種方法在請求中加入 token 的不便,同時,通過 XMLHttpRequest 請求的地址不會被記錄到瀏覽器的地址欄,也不用擔心 token 會透過 Referer 泄露到其他網站中去。

然而這種方法的局限性非常大。XMLHttpRequest 請求通常用于 Ajax 方法中對于頁面局部的異步刷新,并非所有的請求都適合用這個類來發起,而且通過該類請求得到的頁面不能被瀏覽器所記錄下,從而進行前進,后退,刷新,收藏等操作,給用戶帶來不便。另外,對于沒有進行 CSRF 防護的遺留系統來說,要采用這種方法來進行防護,要把所有請求都改為 XMLHttpRequest 請求,這樣幾乎是要重寫整個網站,這代價無疑是不能接受的。

Java 代碼示例

下文將以 Java 為例,對上述三種方法分別用代碼進行示例。無論使用何種方法,在服務器端的攔截器必不可少,它將負責檢查到來的請求是否符合要求,然后視結果而決定是否繼續請求或者丟棄。在 Java 中,攔截器是由 Filter 來實現的。我們可以編寫一個 Filter,并在 web.xml 中對其進行配置,使其對于訪問所有需要 CSRF 保護的資源的請求進行攔截。

在 filter 中對請求的 Referer 驗證代碼如下
清單 1. 在 Filter 中驗證 Referer

1
2
3
4
5
6
7
8
// 從 HTTP 頭中取得 Referer 值
 String referer=request.getHeader("Referer"); 
 // 判斷 Referer 是否以 bank.example 開頭
 if((referer!=null) &&(referer.trim().startsWith(“bank.example”))){ 
    chain.doFilter(request, response); 
 }else
    request.getRequestDispatcher(“error.jsp”).forward(request,response); 
 }

以上代碼先取得 Referer 值,然后進行判斷,當其非空并以 bank.example 開頭時,則繼續請求,否則的話可能是 CSRF 攻擊,轉到 error.jsp 頁面。

如果要進一步驗證請求中的 token 值,代碼如下

1
<em><strong>清單 2. 在 filter 中驗證請求中的</strong></em> token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
HttpServletRequest req = (HttpServletRequest)request; 
 HttpSession s = req.getSession(); 
  
 // 從 session 中得到 csrftoken 屬性
 String sToken = (String)s.getAttribute(“csrftoken”); 
 if(sToken == null){ 
  
    // 產生新的 token 放入 session 中
    sToken = generateToken(); 
    s.setAttribute(“csrftoken”,sToken); 
    chain.doFilter(request, response); 
 } else
  
    // 從 HTTP 頭中取得 csrftoken 
    String xhrToken = req.getHeader(“csrftoken”); 
  
    // 從請求參數中取得 csrftoken 
    String pToken = req.getParameter(“csrftoken”); 
    if(sToken != null && xhrToken != null && sToken.equals(xhrToken)){ 
        chain.doFilter(request, response); 
    }else if(sToken != null && pToken != null && sToken.equals(pToken)){ 
        chain.doFilter(request, response); 
    }else
        request.getRequestDispatcher(“error.jsp”).forward(request,response); 
    
 }

首先判斷 session 中有沒有 csrftoken,如果沒有,則認為是第一次訪問,session 是新建立的,這時生成一個新的 token,放于 session 之中,并繼續執行請求。如果 session 中已經有 csrftoken,則說明用戶已經與服務器之間建立了一個活躍的 session,這時要看這個請求中有沒有同時附帶這個 token,由于請求可能來自于常規的訪問或是 XMLHttpRequest 異步訪問,我們分別嘗試從請求中獲取 csrftoken 參數以及從 HTTP 頭中獲取 csrftoken 自定義屬性并與 session 中的值進行比較,只要有一個地方帶有有效 token,就判定請求合法,可以繼續執行,否則就轉到錯誤頁面。生成 token 有很多種方法,任何的隨機算法都可以使用,Java 的 UUID 類也是一個不錯的選擇。

除了在服務器端利用 filter 來驗證 token 的值以外,我們還需要在客戶端給每個請求附加上這個 token,這是利用 js 來給 html 中的鏈接和表單請求地址附加 csrftoken 代碼,其中已定義 token 為全局變量,其值可以從 session 中得到。

1
<em><strong>清單 3. 在客戶端對于請求附加</strong> </em>token
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
function appendToken(){ 
    updateForms(); 
    updateTags(); 
 
  
 function updateForms() { 
    // 得到頁面中所有的 form 元素
    var forms = document.getElementsByTagName('form'); 
    for(i=0; i<forms.length; i++) { 
        var url = forms[i].action; 
  
        // 如果這個 form 的 action 值為空,則不附加 csrftoken 
        if(url == null || url == "" ) continue
  
        // 動態生成 input 元素,加入到 form 之后
        var e = document.createElement("input"); 
        e.name = "csrftoken"
        e.value = token; 
        e.type="hidden"
        forms[i].appendChild(e); 
    
 
  
 function updateTags() { 
    var all = document.getElementsByTagName('a'); 
    var len = all.length; 
  
    // 遍歷所有 a 元素
    for(var i=0; i<len; i++) { 
        var e = all[i]; 
        updateTag(e, 'href', token); 
    
 
  
 function updateTag(element, attr, token) { 
    var location = element.getAttribute(attr); 
    if(location != null && location != '' '' ) { 
        var fragmentIndex = location.indexOf('#'); 
        var fragment = null
        if(fragmentIndex != -1){ 
  
            //url 中含有只相當頁的錨標記
            fragment = location.substring(fragmentIndex); 
            location = location.substring(0,fragmentIndex); 
        
  
        var index = location.indexOf('?'); 
  
        if(index != -1) { 
            //url 中已含有其他參數
            location = location + '&csrftoken=' + token; 
        } else
            //url 中沒有其他參數
            location = location + '?csrftoken=' + token; 
        
        if(fragment != null){ 
            location += fragment; 
        
  
        element.setAttribute(attr, location); 
    
 }

在客戶端 html 中,主要是有兩個地方需要加上 token,一個是表單 form,另一個就是鏈接 a。這段代碼首先遍歷所有的 form,在 form 最后添加一隱藏字段,把 csrftoken 放入其中。然后,代碼遍歷所有的鏈接標記 a,在其 href 屬性中加入 csrftoken 參數。注意對于 a.href 來說,可能該屬性已經有參數,或者有錨標記。因此需要分情況討論,以不同的格式把 csrftoken 加入其中。

如果你的網站使用 XMLHttpRequest,那么還需要在 HTTP 頭中自定義 csrftoken 屬性,利用 dojo.xhr 給 XMLHttpRequest 加上自定義屬性代碼如下:

1
<strong><em>清單 4. 在 HTTP 頭中自定義屬性</em></strong>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
                 
var plainXhr = dojo.xhr; 
 
// 重寫 dojo.xhr 方法
dojo.xhr = function(method,args,hasBody) { 
   // 確保 header 對象存在
   args.headers = args.header || {}; 
 
   tokenValue = '<%=request.getSession(false).getAttribute("csrftoken")%>'
   var token = dojo.getObject("tokenValue"); 
 
   // 把 csrftoken 屬性放到頭中
   args.headers["csrftoken"] = (token) ? token : "  "
   return plainXhr(method,args,hasBody); 
};

這里改寫了 dojo.xhr 的方法,首先確保 dojo.xhr 中存在 HTTP 頭,然后在 args.headers 中添加 csrftoken 字段,并把 token 值從 session 里拿出放入字段中。

CSRF 防御方法選擇之道

通過上文討論可知,目前業界應對 CSRF 攻擊有一些克制方法,但是每種方法都有利弊,沒有一種方法是完美的。如何選擇合適的方法非常重要。如果網站是一個現有系統,想要在最短時間內獲得一定程度的 CSRF 的保護,那么驗證 Referer 的方法是最方便的,要想增加安全性的話,可以選擇不支持低版本瀏覽器,畢竟就目前來說,IE7+, FF3+ 這類高版本瀏覽器的 Referer 值還無法被篡改。

如果系統必須支持 IE6,并且仍然需要高安全性。那么就要使用 token 來進行驗證,在大部分情況下,使用 XmlHttpRequest 并不合適,token 只能以參數的形式放于請求之中,若你的系統不支持用戶自己發布信息,那這種程度的防護已經足夠,否則的話,你仍然難以防范 token 被黑客竊取并發動攻擊。在這種情況下,你需要小心規劃你網站提供的各種服務,從中間找出那些允許用戶自己發布信息的部分,把它們與其他服務分開,使用不同的 token 進行保護,這樣可以有效抵御黑客對于你關鍵服務的攻擊,把危害降到最低。畢竟,刪除別人一個帖子比直接從別人賬號中轉走大筆存款嚴重程度要輕的多。

如果是開發一個全新的系統,則抵御 CSRF 的選擇要大得多。筆者建議對于重要的服務,可以盡量使用 XMLHttpRequest 來訪問,這樣增加 token 要容易很多。另外盡量避免在 js 代碼中使用復雜邏輯來構造常規的同步請求來訪問需要 CSRF 保護的資源,比如 window.location 和 document.createElement(“a”) 之類,這樣也可以減少在附加 token 時產生的不必要的麻煩。

最后,要記住 CSRF 不是黑客唯一的攻擊手段,無論你 CSRF 防范有多么嚴密,如果你系統有其他安全漏洞,比如跨站域腳本攻擊 XSS,那么黑客就可以繞過你的安全防護,展開包括 CSRF 在內的各種攻擊,你的防線將如同虛設。

總結與展望

可見,CSRF 是一種危害非常大的攻擊,又很難以防范。目前幾種防御策略雖然可以很大程度上抵御 CSRF 的攻擊,但并沒有一種完美的解決方案。一些新的方案正在研究之中,比如對于每次請求都使用不同的動態口令,把 Referer 和 token 方案結合起來,甚至嘗試修改 HTTP 規范,但是這些新的方案尚不成熟,要正式投入使用并被業界廣為接受還需時日。在這之前,我們只有充分重視 CSRF,根據系統的實際情況選擇最合適的策略,這樣才能把 CSRF 的危害降到最低。

posted @ 2013-11-05 20:53 云云 閱讀(496) | 評論 (0)編輯 收藏

1一152期六合图库资料