我的Java路上那些事兒

          快樂(lè)成長(zhǎng)
          posts - 110, comments - 101, trackbacks - 0, articles - 7
            BlogJava :: 首頁(yè) :: 新隨筆 :: 聯(lián)系 :: 聚合  :: 管理

          2013年11月5日

          轉(zhuǎn)自:http://blog.csdn.net/jeffreynicole/article/details/46953059 


          一個(gè)性能較好的web服務(wù)器jvm參數(shù)配置:


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


          說(shuō)明:

          -Xmn和-Xmx之比大概是1:9,如果把新生代內(nèi)存設(shè)置得太大會(huì)導(dǎo)致young gc時(shí)間較長(zhǎng)

          一個(gè)好的Web系統(tǒng)應(yīng)該是每次http請(qǐng)求申請(qǐng)內(nèi)存都能在young gc回收掉,full gc永不發(fā)生,當(dāng)然這是最理想的情況

          xmn的值應(yīng)該是保證夠用(夠http并發(fā)請(qǐng)求之用)的前提下設(shè)置得盡量小

          web服務(wù)器和游戲服務(wù)器的配置思路不太一樣,最重要的區(qū)別是對(duì)游戲服務(wù)器的xmn即年輕代設(shè)置比較大,和Xmx大概1:3的關(guān)系,因?yàn)橛螒蚍?wù)器一般是長(zhǎng)連接,在保持一定的并發(fā)量后需要較大的年輕代堆內(nèi)存,如果設(shè)置得大小了會(huì)經(jīng)常引發(fā)young gc


          • 對(duì)JVM的簡(jiǎn)介


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


          • 年輕代
          年輕代是所有新對(duì)象產(chǎn)生的地方。當(dāng)年輕代內(nèi)存空間被用完時(shí),就會(huì)觸發(fā)垃圾回收。這個(gè)垃圾回收叫做Minor GC。年輕代被分為3個(gè)部分——Enden區(qū)和兩個(gè)Survivor區(qū)。


          年輕代空間的要點(diǎn):
          大多數(shù)新建的對(duì)象都位于Eden區(qū)。
          當(dāng)Eden區(qū)被對(duì)象填滿時(shí),就會(huì)執(zhí)行Minor GC。并把所有存活下來(lái)的對(duì)象轉(zhuǎn)移到其中一個(gè)survivor區(qū)。
          Minor GC同樣會(huì)檢查存活下來(lái)的對(duì)象,并把它們轉(zhuǎn)移到另一個(gè)survivor區(qū)。這樣在一段時(shí)間內(nèi),總會(huì)有一個(gè)空的survivor區(qū)。
          經(jīng)過(guò)多次GC周期后,仍然存活下來(lái)的對(duì)象會(huì)被轉(zhuǎn)移到年老代內(nèi)存空間。通常這是在年輕代有資格提升到年老代前通過(guò)設(shè)定年齡閾值來(lái)完成的。

          • 年老代
          年老代內(nèi)存里包含了長(zhǎng)期存活的對(duì)象和經(jīng)過(guò)多次Minor GC后依然存活下來(lái)的對(duì)象。通常會(huì)在老年代內(nèi)存被占滿時(shí)進(jìn)行垃圾回收。老年代的垃圾收集叫做Major GC。Major GC會(huì)花費(fèi)更多的時(shí)間。


          Stop the World事件
          所有的垃圾收集都是“Stop the World”事件,因?yàn)樗械膽?yīng)用線程都會(huì)停下來(lái)直到操作完成(所以叫“Stop the World”)。

          因?yàn)槟贻p代里的對(duì)象都是一些臨時(shí)(short-lived )對(duì)象,執(zhí)行Minor GC非常快,所以應(yīng)用不會(huì)受到(“Stop the World”)影響。

          由于Major GC會(huì)檢查所有存活的對(duì)象,因此會(huì)花費(fèi)更長(zhǎng)的時(shí)間。應(yīng)該盡量減少M(fèi)ajor GC。因?yàn)镸ajor GC會(huì)在垃圾回收期間讓你的應(yīng)用反應(yīng)遲鈍,所以如果你有一個(gè)需要快速響應(yīng)的應(yīng)用發(fā)生多次Major GC,你會(huì)看到超時(shí)錯(cuò)誤。

          垃圾回收時(shí)間取決于垃圾回收策略。這就是為什么有必要去監(jiān)控垃圾收集和對(duì)垃圾收集進(jìn)行調(diào)優(yōu)。從而避免要求快速響應(yīng)的應(yīng)用出現(xiàn)超時(shí)錯(cuò)誤。


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


          方法區(qū)
          方法區(qū)是永久代空間的一部分,并用來(lái)存儲(chǔ)類型信息(運(yùn)行時(shí)常量和靜態(tài)變量)和方法代碼和構(gòu)造函數(shù)代碼。


          內(nèi)存池
          如果JVM實(shí)現(xiàn)支持,JVM內(nèi)存管理會(huì)為創(chuàng)建內(nèi)存池,用來(lái)為不變對(duì)象創(chuàng)建對(duì)象池。字符串池就是內(nèi)存池類型的一個(gè)很好的例子。內(nèi)存池可以屬于堆或者永久代,這取決于JVM內(nèi)存管理的實(shí)現(xiàn)。


          運(yùn)行時(shí)常量池
          運(yùn)行時(shí)常量池是每個(gè)類常量池的運(yùn)行時(shí)代表。它包含了類的運(yùn)行時(shí)常量和靜態(tài)方法。運(yùn)行時(shí)常量池是方法區(qū)的一部分。


          Java棧內(nèi)存
          Java棧內(nèi)存用于運(yùn)行線程。它們包含了方法里的臨時(shí)數(shù)據(jù)、堆里其它對(duì)象引用的特定數(shù)據(jù)。

          Java垃圾回收
          Java垃圾回收會(huì)找出沒(méi)用的對(duì)象,把它從內(nèi)存中移除并釋放出內(nèi)存給以后創(chuàng)建的對(duì)象使用。Java程序語(yǔ)言中的一個(gè)最大優(yōu)點(diǎn)是自動(dòng)垃圾回收,不像其他的程序語(yǔ)言那樣需要手動(dòng)分配和釋放內(nèi)存,比如C語(yǔ)言。

          垃圾收集器是一個(gè)后臺(tái)運(yùn)行程序。它管理著內(nèi)存中的所有對(duì)象并找出沒(méi)被引用的對(duì)象。所有的這些未引用的對(duì)象都會(huì)被刪除,回收它們的空間并分配給其他對(duì)象。

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


          簡(jiǎn)單標(biāo)記和清除方法存在兩個(gè)問(wèn)題:
          效率很低。因?yàn)榇蠖鄶?shù)新建對(duì)象都會(huì)成為“沒(méi)用對(duì)象”。
          經(jīng)過(guò)多次垃圾回收周期的對(duì)象很有可能在以后的周期也會(huì)存活下來(lái)。
          上面簡(jiǎn)單清除方法的問(wèn)題在于Java垃圾收集的分代回收的,而且在堆內(nèi)存里有年輕代和年老代兩個(gè)區(qū)域。


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

          僅需要使用JVM開關(guān)就可以在我們的應(yīng)用里啟用垃圾回收策略。

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

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

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

               摘要: class文件簡(jiǎn)介及加載     Java編譯器編譯好Java文件之后,產(chǎn)生.class 文件在磁盤中。這種class文件是二進(jìn)制文件,內(nèi)容是只有JVM虛擬機(jī)能夠識(shí)別的機(jī)器碼。JVM虛擬機(jī)讀取字節(jié)碼文件,取出二進(jìn)制數(shù)據(jù),加載到內(nèi)存中,解析.class 文件內(nèi)的信息,生成對(duì)應(yīng)的 Class對(duì)象:     &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 云云 閱讀(732) | 評(píng)論 (0)編輯 收藏

          提升tomcat 性能 apr擴(kuò)展lib
          使用apr類庫(kù) 可以讓tomcat的性能提升到3到4倍  
          目前項(xiàng)目中都使用這樣的配置
          <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 云云 閱讀(917) | 評(píng)論 (0)編輯 收藏

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

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

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

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

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

          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\" \""


          把上面這段文字保存問(wèn)一個(gè)Done.reg文件
          然后執(zhí)行,導(dǎo)入到注冊(cè)表
          就會(huì)在你右鍵一個(gè)文件夾的時(shí)候多出來(lái)一個(gè)菜單"刪除該目錄下面.svn文件"
          執(zhí)行該命令即可

           

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

          在ibatis中不需要關(guān)注這些參數(shù) 而轉(zhuǎn)到mybatis后 如果字段值為空 必須設(shè)置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>

          這些設(shè)置之多,太煩了,最讓人煩的是  jdbcType = DATE,類型還必須大寫,不能小寫。
          如下面的例子,將DATE 改成 Date 。結(jié)果讓人很抓狂?。。?!
          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時(shí)的時(shí)候用#{endTime,jdbcType=DATE},可以將時(shí)間插入成功,且可以精確到時(shí)分秒
          但如果在update語(yǔ)句中也這樣使用,那你得到的只會(huì)有日期,這夠坑爹的了吧 ,尼瑪  比起ibatis方便之處差遠(yuǎn)了
          要想在update語(yǔ)句中 將時(shí)間格式化成時(shí)分秒 不得不再加一個(gè)類型 如下面:
          startTime = #{startTime,javaType=DATE, jdbcType=VARCHAR}













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

          CSRF 背景與介紹

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

          CSRF 攻擊實(shí)例

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

          CSRF 攻擊的對(duì)象

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

          當(dāng)前防御 CSRF 的幾種策略

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

          驗(yàn)證 HTTP Referer 字段

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

          這種方法的顯而易見(jiàn)的好處就是簡(jiǎn)單易行,網(wǎng)站的普通開發(fā)人員不需要操心 CSRF 的漏洞,只需要在最后給所有安全敏感的請(qǐng)求統(tǒng)一增加一個(gè)攔截器來(lái)檢查 Referer 的值就可以。特別是對(duì)于當(dāng)前現(xiàn)有的系統(tǒng),不需要改變當(dāng)前系統(tǒng)的任何已有代碼和邏輯,沒(méi)有風(fēng)險(xiǎn),非常便捷。

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

          即便是使用最新的瀏覽器,黑客無(wú)法篡改 Referer 值,這種方法仍然有問(wèn)題。因?yàn)?Referer 值會(huì)記錄下用戶的訪問(wèn)來(lái)源,有些用戶認(rèn)為這樣會(huì)侵犯到他們自己的隱私權(quán),特別是有些組織擔(dān)心 Referer 值會(huì)把組織內(nèi)網(wǎng)中的某些信息泄露到外網(wǎng)中。因此,用戶自己可以設(shè)置瀏覽器使其在發(fā)送請(qǐng)求時(shí)不再提供 Referer。當(dāng)他們正常訪問(wèn)銀行網(wǎng)站時(shí),網(wǎng)站會(huì)因?yàn)檎?qǐng)求沒(méi)有 Referer 值而認(rèn)為是 CSRF 攻擊,拒絕合法用戶的訪問(wèn)。

          在請(qǐng)求地址中添加 token 并驗(yàn)證

          CSRF 攻擊之所以能夠成功,是因?yàn)楹诳涂梢酝耆珎卧煊脩舻恼?qǐng)求,該請(qǐng)求中所有的用戶驗(yàn)證信息都是存在于 cookie 中,因此黑客可以在不知道這些驗(yàn)證信息的情況下直接利用用戶自己的 cookie 來(lái)通過(guò)安全驗(yàn)證。要抵御 CSRF,關(guān)鍵在于在請(qǐng)求中放入黑客所不能偽造的信息,并且該信息不存在于 cookie 之中??梢栽?HTTP 請(qǐng)求中以參數(shù)的形式加入一個(gè)隨機(jī)產(chǎn)生的 token,并在服務(wù)器端建立一個(gè)攔截器來(lái)驗(yàn)證這個(gè) token,如果請(qǐng)求中沒(méi)有 token 或者 token 內(nèi)容不正確,則認(rèn)為可能是 CSRF 攻擊而拒絕該請(qǐng)求。

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

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

          在 HTTP 頭中自定義屬性并驗(yàn)證

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

          然而這種方法的局限性非常大。XMLHttpRequest 請(qǐng)求通常用于 Ajax 方法中對(duì)于頁(yè)面局部的異步刷新,并非所有的請(qǐng)求都適合用這個(gè)類來(lái)發(fā)起,而且通過(guò)該類請(qǐng)求得到的頁(yè)面不能被瀏覽器所記錄下,從而進(jìn)行前進(jìn),后退,刷新,收藏等操作,給用戶帶來(lái)不便。另外,對(duì)于沒(méi)有進(jìn)行 CSRF 防護(hù)的遺留系統(tǒng)來(lái)說(shuō),要采用這種方法來(lái)進(jìn)行防護(hù),要把所有請(qǐng)求都改為 XMLHttpRequest 請(qǐng)求,這樣幾乎是要重寫整個(gè)網(wǎng)站,這代價(jià)無(wú)疑是不能接受的。

          Java 代碼示例

          下文將以 Java 為例,對(duì)上述三種方法分別用代碼進(jìn)行示例。無(wú)論使用何種方法,在服務(wù)器端的攔截器必不可少,它將負(fù)責(zé)檢查到來(lái)的請(qǐng)求是否符合要求,然后視結(jié)果而決定是否繼續(xù)請(qǐng)求或者丟棄。在 Java 中,攔截器是由 Filter 來(lái)實(shí)現(xiàn)的。我們可以編寫一個(gè) Filter,并在 web.xml 中對(duì)其進(jìn)行配置,使其對(duì)于訪問(wèn)所有需要 CSRF 保護(hù)的資源的請(qǐng)求進(jìn)行攔截。

          在 filter 中對(duì)請(qǐng)求的 Referer 驗(yàn)證代碼如下
          清單 1. 在 Filter 中驗(yàn)證 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 值,然后進(jìn)行判斷,當(dāng)其非空并以 bank.example 開頭時(shí),則繼續(xù)請(qǐng)求,否則的話可能是 CSRF 攻擊,轉(zhuǎn)到 error.jsp 頁(yè)面。

          如果要進(jìn)一步驗(yàn)證請(qǐng)求中的 token 值,代碼如下

          1
          <em><strong>清單 2. 在 filter 中驗(yàn)證請(qǐng)求中的</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){ 
            
              // 產(chǎn)生新的 token 放入 session 中
              sToken = generateToken(); 
              s.setAttribute(“csrftoken”,sToken); 
              chain.doFilter(request, response); 
           } else
            
              // 從 HTTP 頭中取得 csrftoken 
              String xhrToken = req.getHeader(“csrftoken”); 
            
              // 從請(qǐng)求參數(shù)中取得 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 中有沒(méi)有 csrftoken,如果沒(méi)有,則認(rèn)為是第一次訪問(wèn),session 是新建立的,這時(shí)生成一個(gè)新的 token,放于 session 之中,并繼續(xù)執(zhí)行請(qǐng)求。如果 session 中已經(jīng)有 csrftoken,則說(shuō)明用戶已經(jīng)與服務(wù)器之間建立了一個(gè)活躍的 session,這時(shí)要看這個(gè)請(qǐng)求中有沒(méi)有同時(shí)附帶這個(gè) token,由于請(qǐng)求可能來(lái)自于常規(guī)的訪問(wèn)或是 XMLHttpRequest 異步訪問(wèn),我們分別嘗試從請(qǐng)求中獲取 csrftoken 參數(shù)以及從 HTTP 頭中獲取 csrftoken 自定義屬性并與 session 中的值進(jìn)行比較,只要有一個(gè)地方帶有有效 token,就判定請(qǐng)求合法,可以繼續(xù)執(zhí)行,否則就轉(zhuǎn)到錯(cuò)誤頁(yè)面。生成 token 有很多種方法,任何的隨機(jī)算法都可以使用,Java 的 UUID 類也是一個(gè)不錯(cuò)的選擇。

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

          1
          <em><strong>清單 3. 在客戶端對(duì)于請(qǐng)求附加</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() { 
              // 得到頁(yè)面中所有的 form 元素
              var forms = document.getElementsByTagName('form'); 
              for(i=0; i<forms.length; i++) { 
                  var url = forms[i].action; 
            
                  // 如果這個(gè) form 的 action 值為空,則不附加 csrftoken 
                  if(url == null || url == "" ) continue
            
                  // 動(dòng)態(tài)生成 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 中含有只相當(dāng)頁(yè)的錨標(biāo)記
                      fragment = location.substring(fragmentIndex); 
                      location = location.substring(0,fragmentIndex); 
                  
            
                  var index = location.indexOf('?'); 
            
                  if(index != -1) { 
                      //url 中已含有其他參數(shù)
                      location = location + '&csrftoken=' + token; 
                  } else
                      //url 中沒(méi)有其他參數(shù)
                      location = location + '?csrftoken=' + token; 
                  
                  if(fragment != null){ 
                      location += fragment; 
                  
            
                  element.setAttribute(attr, location); 
              
           }

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

          如果你的網(wǎng)站使用 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 對(duì)象存在
             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 防御方法選擇之道

          通過(guò)上文討論可知,目前業(yè)界應(yīng)對(duì) CSRF 攻擊有一些克制方法,但是每種方法都有利弊,沒(méi)有一種方法是完美的。如何選擇合適的方法非常重要。如果網(wǎng)站是一個(gè)現(xiàn)有系統(tǒng),想要在最短時(shí)間內(nèi)獲得一定程度的 CSRF 的保護(hù),那么驗(yàn)證 Referer 的方法是最方便的,要想增加安全性的話,可以選擇不支持低版本瀏覽器,畢竟就目前來(lái)說(shuō),IE7+, FF3+ 這類高版本瀏覽器的 Referer 值還無(wú)法被篡改。

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

          如果是開發(fā)一個(gè)全新的系統(tǒng),則抵御 CSRF 的選擇要大得多。筆者建議對(duì)于重要的服務(wù),可以盡量使用 XMLHttpRequest 來(lái)訪問(wèn),這樣增加 token 要容易很多。另外盡量避免在 js 代碼中使用復(fù)雜邏輯來(lái)構(gòu)造常規(guī)的同步請(qǐng)求來(lái)訪問(wèn)需要 CSRF 保護(hù)的資源,比如 window.location 和 document.createElement(“a”) 之類,這樣也可以減少在附加 token 時(shí)產(chǎn)生的不必要的麻煩。

          最后,要記住 CSRF 不是黑客唯一的攻擊手段,無(wú)論你 CSRF 防范有多么嚴(yán)密,如果你系統(tǒng)有其他安全漏洞,比如跨站域腳本攻擊 XSS,那么黑客就可以繞過(guò)你的安全防護(hù),展開包括 CSRF 在內(nèi)的各種攻擊,你的防線將如同虛設(shè)。

          總結(jié)與展望

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

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

          “為什么存儲(chǔ)密碼用字符數(shù)組比字符串更合適”這個(gè)問(wèn)題是我的一個(gè)朋友在最近一次面試中提到的。那哥們是應(yīng)聘的是一個(gè)技術(shù)lead的職位,有超過(guò)六年的工作經(jīng)驗(yàn)。字符數(shù)組和字符串都可以用于存儲(chǔ)文本數(shù)據(jù),但是在選擇具體哪一種時(shí),如果你沒(méi)有針對(duì)具體的情況是很難回答這個(gè)問(wèn)題的。但是正如這哥們說(shuō)的任何與字符串相關(guān)的問(wèn)題一定有線索可以在字符串的屬性里面找到,比如不可變性。他就用這種方式去說(shuō)服面試官。這里我們就來(lái)探討一些關(guān)于為什么你應(yīng)該使用char[] 來(lái)存儲(chǔ)密碼而不是字符串。

          1. 因?yàn)樽址遣豢勺儗?duì)象,如果作為普通文本存儲(chǔ)密碼,那么它會(huì)一直存在內(nèi)存中直至被垃圾收集器回收。因?yàn)樽址畯淖址刂腥〕龅模ㄈ绻刂杏性撟址椭苯訌某刂蝎@取,否則new 一個(gè)出來(lái),然后把它放入池中),這樣有很大的機(jī)會(huì)長(zhǎng)期保留在內(nèi)存中,這樣會(huì)引發(fā)安全問(wèn)題。因?yàn)槿魏慰梢栽L問(wèn)內(nèi)存的人能以明碼的方式把密碼dump出來(lái)。另外你還應(yīng)該始終以加密而不是普通的文本來(lái)表示密碼。因?yàn)樽址遣豢勺?,因此沒(méi)有任何方法可以改變其內(nèi)容,任何改變都將產(chǎn)生一個(gè)新的字符串,而如果使用char[],你就可以設(shè)置所有的元素為空或者為零(這里作者的意思是說(shuō),讓認(rèn)證完后該數(shù)組不再使用了,就可以用零或者null覆蓋原來(lái)的密碼,防止別人從內(nèi)存中dump出來(lái))。所以存儲(chǔ)密碼用字符數(shù)組可以明顯的減輕密碼被盜的危險(xiǎn)。

          2. Java官方本身也推薦字符數(shù)組,JpasswordField的方法getPassword()就是返回一個(gè)字符數(shù)組,而由于安全原因getText()方法是被廢棄掉的,因?yàn)樗祷匾粋€(gè)純文本字符串。跟隨Java 團(tuán)隊(duì)的步伐吧,沒(méi)有錯(cuò)。

          3. 字符串以普通文本打印在在log文件或控制臺(tái)中也易引起危險(xiǎn),但是如果使用數(shù)組你不能打印數(shù)組的內(nèi)容,而是它的內(nèi)存地址。盡管這不是它的真正原因,但仍值得注意。

          1
          2
          3
          4
          5
          6
          7
          String strPassword="Unknown";
          char[] charPassword= new char[]{'U','n','k','w','o','n'};
          System.out.println("String password: " + strPassword);
          System.out.println("Character password: " + charPassword);
            
          String password: Unknown
          Character password: [C@110b053
          1
           

          以上所有就是為什么字符數(shù)組比字符串保存密碼要好的原因,盡管使用char[]還不足以安全。我同樣建議你用hash或者密碼加密代替普通文本,而且一旦認(rèn)證完成盡可能快的把他清除掉。

          posted @ 2013-11-05 20:00 云云 閱讀(1014) | 評(píng)論 (0)編輯 收藏

          MyISAM:這個(gè)是默認(rèn)類型,它是基于傳統(tǒng)的ISAM類型,ISAM是Indexed Sequential Access Method (有索引的順序訪問(wèn)方法) 的縮寫,它是存儲(chǔ)記錄和文件的標(biāo)準(zhǔn)方法.與其他存儲(chǔ)引擎比較,MyISAM具有檢查和修復(fù)表格的大多數(shù)工具. MyISAM表格可以被壓縮,而且它們支持全文搜索.它們不是事務(wù)安全的,而且也不支持外鍵。如果事物回滾將造成不完全回滾,不具有原子性。如果執(zhí)行大量的SELECT,MyISAM是更好的選擇。

            InnoDB:這種類型是事務(wù)安全的.它與BDB類型具有相同的特性,它們還支持外鍵.InnoDB表格速度很快.具有比BDB還豐富的特性, 因此如果需要一個(gè)事務(wù)安全的存儲(chǔ)引擎,建議使用它.如果你的數(shù)據(jù)執(zhí)行大量的INSERT或UPDATE,出于性能方面的考慮,應(yīng)該使用InnoDB表,

            對(duì)于支持事物的InnoDB類型的標(biāo),影響速度的主要原因是AUTOCOMMIT默認(rèn)設(shè)置是打開的,而且程序沒(méi)有顯式調(diào)用BEGIN 開始事務(wù),導(dǎo)致每插入一條都自動(dòng)Commit,嚴(yán)重影響了速度??梢栽趫?zhí)行sql前調(diào)用begin,多條sql形成一個(gè)事物(即使autocommit打開也可以),將大大提高性能。

            ===============================================================

            InnoDB和MyISAM是在使用MySQL最常用的兩個(gè)表類型,各有優(yōu)缺點(diǎn),視具體應(yīng)用而定。下面是已知的兩者之間的差別,僅供參考。

            innodb

            InnoDB 給 MySQL 提供了具有事務(wù)(commit)、回滾(rollback)和崩潰修復(fù)能力(crash recovery capabilities)的事務(wù)安全(transaction-safe (ACID compliant))型表。InnoDB 提供了行鎖(locking on row level),提供與 Oracle 類型一致的不加鎖讀取(non-locking read in SELECTs)。這些特性均提高了多用戶并發(fā)操作的性能表現(xiàn)。在InnoDB表中不需要擴(kuò)大鎖定(lock escalation),因?yàn)?InnoDB 的列鎖定(row level locks)適宜非常小的空間。InnoDB 是 MySQL 上第一個(gè)提供外鍵約束(FOREIGN KEY constraints)的表引擎。

            InnoDB 的設(shè)計(jì)目標(biāo)是處理大容量數(shù)據(jù)庫(kù)系統(tǒng),它的 CPU 利用率是其它基于磁盤的關(guān)系數(shù)據(jù)庫(kù)引擎所不能比的。在技術(shù)上,InnoDB 是一套放在 MySQL 后臺(tái)的完整數(shù)據(jù)庫(kù)系統(tǒng),InnoDB 在主內(nèi)存中建立其專用的緩沖池用于高速緩沖數(shù)據(jù)和索引。 InnoDB 把數(shù)據(jù)和索引存放在表空間里,可能包含多個(gè)文件,這與其它的不一樣,舉例來(lái)說(shuō),在 MyISAM 中,表被存放在單獨(dú)的文件中。InnoDB 表的大小只受限于操作系統(tǒng)的文件大小,一般為 2 GB。

            InnoDB所有的表都保存在同一個(gè)數(shù)據(jù)文件 ibdata1 中(也可能是多個(gè)文件,或者是獨(dú)立的表空間文件),相對(duì)來(lái)說(shuō)比較不好備份,免費(fèi)的方案可以是拷貝數(shù)據(jù)文件、備份 binlog,或者用 mysqldump。

            MyISAM

            MyISAM 是MySQL缺省存貯引擎 .

            每張MyISAM 表被存放在三個(gè)文件 。frm 文件存放表格定義。 數(shù)據(jù)文件是MYD (MYData) 。 索引文件是MYI (MYIndex) 引伸。

            因?yàn)镸yISAM相對(duì)簡(jiǎn)單所以在效率上要優(yōu)于InnoDB..小型應(yīng)用使用MyISAM是不錯(cuò)的選擇.

            MyISAM表是保存成文件的形式,在跨平臺(tái)的數(shù)據(jù)轉(zhuǎn)移中使用MyISAM存儲(chǔ)會(huì)省去不少的麻煩

            以下是一些細(xì)節(jié)和具體實(shí)現(xiàn)的差別:

            1.InnoDB不支持FULLTEXT類型的索引。

            2.InnoDB 中不保存表的具體行數(shù),也就是說(shuō),執(zhí)行select count(*) from table時(shí),InnoDB要掃描一遍整個(gè)表來(lái)計(jì)算有多少行,但是MyISAM只要簡(jiǎn)單的讀出保存好的行數(shù)即可。注意的是,當(dāng)count(*)語(yǔ)句包含 where條件時(shí),兩種表的操作是一樣的。

            3.對(duì)于AUTO_INCREMENT類型的字段,InnoDB中必須包含只有該字段的索引,但是在MyISAM表中,可以和其他字段一起建立聯(lián)合索引。

            4.DELETE FROM table時(shí),InnoDB不會(huì)重新建立表,而是一行一行的刪除。

            5.LOAD TABLE FROM MASTER操作對(duì)InnoDB是不起作用的,解決方法是首先把InnoDB表改成MyISAM表,導(dǎo)入數(shù)據(jù)后再改成InnoDB表,但是對(duì)于使用的額外的InnoDB特性(例如外鍵)的表不適用。

            另外,InnoDB表的行鎖也不是絕對(duì)的,如果在執(zhí)行一個(gè)SQL語(yǔ)句時(shí)MySQL不能確定要掃描的范圍,InnoDB表同樣會(huì)鎖全表,例如update table set num=1 where name like “%aaa%”

            任何一種表都不是萬(wàn)能的,只用恰當(dāng)?shù)尼槍?duì)業(yè)務(wù)類型來(lái)選擇合適的表類型,才能最大的發(fā)揮MySQL的性能優(yōu)勢(shì)。

            ===============================================================

            以下是InnoDB和MyISAM的一些聯(lián)系和區(qū)別!

            1. 4.0以上mysqld都支持事務(wù),包括非max版本。3.23的需要max版本mysqld才能支持事務(wù)。

            2. 創(chuàng)建表時(shí)如果不指定type則默認(rèn)為myisam,不支持事務(wù)。

            可以用 show create table tablename 命令看表的類型。

            2.1 對(duì)不支持事務(wù)的表做start/commit操作沒(méi)有任何效果,在執(zhí)行commit前已經(jīng)提交,測(cè)試:

            執(zhí)行一個(gè)msyql:

            use test;

            drop table if exists tn;

            create table tn (a varchar(10)) type=myisam;

            drop table if exists ty;

            create table ty (a varchar(10)) type=innodb;

            begin;

            insert into tn values('a');

            insert into ty values('a');

            select * from tn;

            select * from ty;

            都能看到一條記錄

            執(zhí)行另一個(gè)mysql:

            use test;

            select * from tn;

            select * from ty;

            只有tn能看到一條記錄

            然后在另一邊

            commit;

            才都能看到記錄。

            3. 可以執(zhí)行以下命令來(lái)切換非事務(wù)表到事務(wù)(數(shù)據(jù)不會(huì)丟失),innodb表比myisam表更安全:

            alter table tablename type=innodb;

            3.1 innodb表不能用repair table命令和myisamchk -r table_name

            但可以用check table,以及mysqlcheck [OPTIONS] database [tables]

            4. 啟動(dòng)mysql數(shù)據(jù)庫(kù)的命令行中添加了以下參數(shù)可以使新發(fā)布的mysql數(shù)據(jù)表都默認(rèn)為使用事務(wù)(

            只影響到create語(yǔ)句。)

            --default-table-type=InnoDB

            測(cè)試命令:

            use test;

            drop table if exists tn;

            create table tn (a varchar(10));

            show create table tn;

            5. 臨時(shí)改變默認(rèn)表類型可以用:

            set table_type=InnoDB;

            show variables like 'table_type';

            或:

            c:\mysql\bin\mysqld-max-nt --standalone --default-table-type=InnoDB

          posted @ 2013-11-05 14:27 云云 閱讀(324) | 評(píng)論 (0)編輯 收藏

          主站蜘蛛池模板: 岳西县| 高清| 车险| 靖江市| 昌邑市| 嘉义县| 宿州市| 德令哈市| 舟山市| 广平县| 阿拉善左旗| 保定市| 东海县| 驻马店市| 甘德县| 芷江| 太原市| 滦平县| 冀州市| 孟村| 九龙城区| 平定县| 门源| 黄平县| 海南省| 罗田县| 增城市| 舒兰市| 策勒县| 肇庆市| 九台市| 望谟县| 东阳市| 安康市| 泗水县| 平昌县| 景宁| 绥滨县| 大田县| 平江县| 桂平市|