




版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡(jiǎn)介
安全編碼規(guī)范版本號(hào):V1.0
修訂頁(yè)編號(hào)章節(jié)名稱修訂內(nèi)容簡(jiǎn)述修訂日期修訂前版本號(hào)修訂后版本號(hào)修訂人批準(zhǔn)人
目錄1 目的 52 背景 53 安全編碼規(guī)范 53.1 輸入驗(yàn)證和數(shù)據(jù)合法性校驗(yàn) 53.1.1 避免SQL注入 53.1.2 避免XML注入 53.1.3 避免跨站點(diǎn)腳本(XSS) 63.2 聲明和初始化 63.2.1 避免類初始化的相互依賴 63.3 表達(dá)式 73.3.1 不可忽略方法的返回值 73.3.2 不要引用空指針 83.3.3 使用Arrays.equals()來(lái)比較數(shù)組的內(nèi)容 83.4 數(shù)字類型和操作 83.4.1 防止整數(shù)溢出 83.4.2 避免除法和取模運(yùn)算分母為零 93.5 類和方法操作 103.5.1 數(shù)據(jù)成員聲明為私有,提供可訪問(wèn)的包裝方法 103.5.2 敏感類不允許復(fù)制 103.5.3 比較類的正確做法 103.5.4 不要硬編碼敏感信息 113.5.5 驗(yàn)證方法參數(shù) 113.5.6 不要使用過(guò)時(shí)、陳舊或低效的方法 113.5.7 數(shù)組引用問(wèn)題 113.5.8 不要產(chǎn)生內(nèi)存泄露 123.6 異常處理 123.6.1 不要忽略捕獲的異常 123.6.2 不允許暴露異常的敏感信息 133.6.3 不允許拋出RuntimeException,Exception,Throwable 143.6.4 不要捕獲NullPointerException或其他父類異常 143.7 多線程編程 153.7.1 確保共享變量的可見(jiàn)性 153.7.2 確保共享變量的操作是原子的 163.7.3 不要調(diào)用Thread.run(),不要使用Thread.stop()以終止線程 183.7.4 確保執(zhí)行阻塞操作的線程可以終止 183.7.5 相互依存的任務(wù)不要在一個(gè)有限的線程池執(zhí)行 193.8 輸入輸出 193.8.1 程序終止前刪除臨時(shí)文件 193.8.2 檢測(cè)和處理文件相關(guān)的錯(cuò)誤 193.8.3 及時(shí)釋放資源 193.9 序列化 203.9.1 不要序列化未加密的敏感數(shù)據(jù) 203.9.2 在序列化過(guò)程中避免內(nèi)存和資源泄漏 213.9.3 反序列化要在程序最小權(quán)限的安全環(huán)境中 22安全編碼規(guī)范輸入驗(yàn)證和數(shù)據(jù)合法性校驗(yàn)程序接受數(shù)據(jù)也許來(lái)源于未經(jīng)驗(yàn)證的用戶,網(wǎng)絡(luò)連接和其他不受信任的來(lái)源,假如未對(duì)程序接受數(shù)據(jù)進(jìn)行校驗(yàn),則也許會(huì)引發(fā)安全問(wèn)題。避免SQL注入使用PreparedStatement預(yù)編譯SQL,解決SQL注入問(wèn)題,傳遞給PreparedStatement對(duì)象的參數(shù)可以被強(qiáng)制進(jìn)行類型轉(zhuǎn)換,保證在插入或查詢數(shù)據(jù)時(shí)與底層的數(shù)據(jù)庫(kù)格式匹配。
StringsqlString="select*fromdb_userwhereusername=?andpassword=?";PreparedStatementstmt=connection.prepareStatement(sqlString);stmt.setString(1,username);stmt.setString(2,pwd);ResultSetrs=stmt.executeQuery();避免XML注入通過(guò)StringBulider或StringBuffer拼接X(jué)ML文獻(xiàn)時(shí),需對(duì)輸入數(shù)據(jù)進(jìn)行合法性校驗(yàn)。對(duì)數(shù)量quantity進(jìn)行合法性校驗(yàn),控制只能傳入0-9的數(shù)字:if(!Pattern.matches("[0-9]+",quantity)){//Formatviolation}StringxmlString="<item>\n<description>Widget</description>\n"+"<price>500</price>\n"+"<quantity>"+quantity+"</quantity></item>";outStream.write(xmlString.getBytes());outStream.flush();避免跨站點(diǎn)腳本(XSS)對(duì)產(chǎn)生跨站的參數(shù)進(jìn)行嚴(yán)格過(guò)濾,嚴(yán)禁傳入<SCRIPT>標(biāo)簽//定義需過(guò)濾的字段串<script>Strings="\uFE64"+"script"+"\uFE65";//過(guò)濾字符串標(biāo)準(zhǔn)化s=Normalizer.normalize(s,Form.NFKC);//使用正則表達(dá)式匹配inputStr是否存在<script>Patternpattern=Ppile(inputStr);Matchermatcher=pattern.matcher(s);if(matcher.find()){//FoundblacklistedtagthrownewIllegalStateException();}else{//...}聲明和初始化避免類初始化的互相依賴?yán)哄e(cuò)誤的寫(xiě)法:publicclassCycle{privatefinalintbalance;privatestaticfinalCyclec=newCycle();privatestaticfinalintdeposit=(int)(Math.random()*100);//RandomdepositpublicCycle(){balance=deposit-10;//Subtractprocessingfee}publicstaticvoidmain(String[]args){System.out.println("Theaccountbalanceis:"+c.balance);}}類加載時(shí)初始化指向Cycle類的靜態(tài)變量c,而類Cycle的無(wú)參構(gòu)造方法又依賴靜態(tài)變量deposit,導(dǎo)致無(wú)法預(yù)期的結(jié)果。對(duì)的的寫(xiě)法:publicclassCycle{privatefinalintbalance;privatestaticfinalintdeposit=(int)(Math.random()*100);//RandomdepositprivatestaticfinalCyclec=newCycle();//InsertedafterinitializationofrequiredfieldspublicCycle(){balance=deposit-10;//Subtractprocessingfee}publicstaticvoidmain(String[]args){System.out.println("Theaccountbalanceis:"+c.balance);}}表達(dá)式不可忽略方法的返回值忽略方法的放回值也許會(huì)導(dǎo)致無(wú)法預(yù)料的結(jié)果。錯(cuò)誤的寫(xiě)法:publicvoiddeleteFile(){FilesomeFile=newFile("someFileName.txt");someFile.delete();}對(duì)的的寫(xiě)法:publicvoiddeleteFile(){FilesomeFile=newFile("someFileName.txt");if(!someFile.delete()){//handlefailuretodeletethefile}}不要引用空指針當(dāng)一個(gè)變量指向一個(gè)NULL值,使用這個(gè)變量的時(shí)候又沒(méi)有檢查,這時(shí)會(huì)導(dǎo)致。NullPointerException。在使用變量前一定要做是否為NULL值的校驗(yàn)。使用Arrays.equals()來(lái)比較數(shù)組的內(nèi)容數(shù)組沒(méi)有覆蓋的Object.equals()方法,調(diào)用Object.equals()方法事實(shí)上是比較數(shù)組的引用,而不是他們的內(nèi)容。程序必須使用兩個(gè)參數(shù)Arrays.equals()方法來(lái)比較兩個(gè)數(shù)組的內(nèi)容publicvoidarrayEqualsExample(){int[]arr1=newint[20];//initializedto0int[]arr2=newint[20];//initializedto0Arrays.equals(arr1,arr2);//true}數(shù)字類型和操作防止整數(shù)溢出使用\o"java.lang中的類"java.lang.Number.BigInteger類進(jìn)行整數(shù)運(yùn)算,防止整數(shù)溢出。publicclassBigIntegerUtil{privatestaticfinalBigIntegerbigMaxInt=BigInteger.valueOf(Integer.MAX_VALUE);privatestaticfinalBigIntegerbigMinInt=BigInteger.valueOf(Integer.MIN_VALUE);publicstaticBigIntegerintRangeCheck(BigIntegerval)throwsArithmeticException{if(pareTo(bigMaxInt)==1||pareTo(bigMinInt)==-1){thrownewArithmeticException("Integeroverflow");}returnval;}publicstaticintaddInt(intv1,intv2)throwsArithmeticException{BigIntegerb1=BigInteger.valueOf(v1);BigIntegerb2=BigInteger.valueOf(v2);BigIntegerres=intRangeCheck(b1.add(b2));returnValue();}publicstaticintsubInt(intv1,intv2)throwsArithmeticException{BigIntegerb1=BigInteger.valueOf(v1);BigIntegerb2=BigInteger.valueOf(v2);BigIntegerres=intRangeCheck(b1.subtract(b2));returnValue();}publicstaticintmultiplyInt(intv1,intv2)throwsArithmeticException{BigIntegerb1=BigInteger.valueOf(v1);BigIntegerb2=BigInteger.valueOf(v2);BigIntegerres=intRangeCheck(b1.multiply(b2));returnValue();}publicstaticintdivideInt(intv1,intv2)throwsArithmeticException{BigIntegerb1=BigInteger.valueOf(v1);BigIntegerb2=BigInteger.valueOf(v2);BigIntegerres=intRangeCheck(b1.divide(b2));returnValue();}}避免去法和取模運(yùn)算分母為零要避免由于分母為零而導(dǎo)致除法和取模運(yùn)算出現(xiàn)異常。if(num2==0){//handleerror}else{result1=num1/num2;result2=num1%num2;}類和方法操作數(shù)據(jù)成員聲明為私有,提供可訪問(wèn)的包裝方法襲擊者可以用意想不到的方式操縱public或protected的數(shù)據(jù)成員,所以需要將數(shù)據(jù)成員為private,對(duì)外提供可控的包裝方法訪問(wèn)數(shù)據(jù)成員。敏感類不允許復(fù)制包含私人的,機(jī)密或其他敏感數(shù)據(jù)的類是不允許被復(fù)制的,解決的方法有兩種:類聲明為finalfinalclassSensitiveClass{//...}Clone方法拋出CloneNotSupportedException異常classSensitiveClass{//...publicfinalSensitiveClassclone()throwsCloneNotSupportedException{thrownewCloneNotSupportedException();}}比較類的對(duì)的做法假如由同一個(gè)類裝載器裝載,它們具有相同的完全限定名稱,則它們是兩個(gè)相同的類。不對(duì)的寫(xiě)法://Determinewhetherobjectauthhasrequired/expectedclassobjectif(auth.getClass().getName().equals("com.application.auth.DefaultAuthenticationHandler")){//...}對(duì)的寫(xiě)法://Determinewhetherobjectauthhasrequired/expectedclassnameif(auth.getClass()==com.application.auth.DefaultAuthenticationHandler.class){//...}不要硬編碼敏感信息硬編碼的敏感信息,如密碼,服務(wù)器IP地址和加密密鑰,也許會(huì)泄露給襲擊者。敏感信息均必須存在在配置文獻(xiàn)或數(shù)據(jù)庫(kù)中。驗(yàn)證方法參數(shù)驗(yàn)證方法的參數(shù),可保證操作方法的參數(shù)產(chǎn)生有效的結(jié)果。不驗(yàn)證方法的參數(shù)也許會(huì)導(dǎo)致不對(duì)的的計(jì)算,運(yùn)營(yíng)時(shí)異常,違反類的不變量,對(duì)象的狀態(tài)不一致。對(duì)于跨信任邊界接受參數(shù)的方法,必須進(jìn)行參數(shù)合法性校驗(yàn)privateObjectmyState=null;//對(duì)于修改myState方法的入?yún)?,進(jìn)行非空和合法性校驗(yàn)voidsetState(Objectstate){
if(state==null){
//Handlenullstate
}
if(isInvalidState(state)){
//Handleinvalidstate
}
myState=state;}不要使用過(guò)時(shí)、陳舊或低效的方法在程序代碼中使用過(guò)時(shí)的、陳舊的或低效的類或方法也許會(huì)導(dǎo)致錯(cuò)誤的行為。數(shù)組引用問(wèn)題某個(gè)方法返回一個(gè)對(duì)敏感對(duì)象的內(nèi)部數(shù)組的引用,假定該方法的調(diào)用程序不改變這些對(duì)象。即使數(shù)組對(duì)象自身是不可改變的,也可以在數(shù)組對(duì)象以外操作數(shù)組的內(nèi)容,這種操作將反映在返回該數(shù)組的對(duì)象中。假如該方法返回可改變的對(duì)象,外部實(shí)體可以改變?cè)谀莻€(gè)類中聲明的public變量,這種改變將反映在實(shí)際對(duì)象中。不對(duì)的的寫(xiě)法:publicclassXXX{ privateString[]xxxx; publicString[]getXXX(){ returnxxxx; }}對(duì)的的寫(xiě)法:publicclassXXX{ privateString[]xxxx; publicString[]getXXX(){ Stringtemp[]=Arrays.copyof(…);//或其他數(shù)組復(fù)制方法 returntemp; }}不要產(chǎn)生內(nèi)存泄露垃圾收集器只收集不可達(dá)的對(duì)象,因此,存在未使用的可到達(dá)的對(duì)象,仍然表達(dá)內(nèi)存管理不善。過(guò)度的內(nèi)存泄漏也許會(huì)導(dǎo)致內(nèi)存耗盡,拒絕服務(wù)(DoS)。異常解決不要忽略捕獲的異常對(duì)于捕獲的異常要進(jìn)行相應(yīng)的解決,不能忽略已捕獲的異常不對(duì)的寫(xiě)法:classFooimplementsRunnable{publicvoidrun(){try{Thread.sleep(1000);}catch(InterruptedExceptione){//此處InterruptedException被忽略}}}對(duì)的寫(xiě)法:classFooimplementsRunnable{publicvoidrun(){try{Thread.sleep(1000);}catch(InterruptedExceptione){Thread.currentThread().interrupt();//Resetinterruptedstatus}}}不允許暴露異常的敏感信息沒(méi)有過(guò)濾敏感信息的異常堆棧往往會(huì)導(dǎo)致信息泄漏,不對(duì)的的寫(xiě)法:try{FileInputStreamfis=newFileInputStream(System.getenv("APPDATA")+args[0]);}catch(FileNotFoundExceptione){//LogtheexceptionthrownewIOException("Unabletoretrievefile",e);}對(duì)的的寫(xiě)法:classExceptionExample{publicstaticvoidmain(String[]args){Filefile=null;try{file=newFile(System.getenv("APPDATA")+args[0]).getCanonicalFile();if(!file.getPath().startsWith("c:\\homepath")){log.error("Invalidfile");return;}}catch(IOExceptionx){log.error("Invalidfile");return;}try{FileInputStreamfis=newFileInputStream(file);}catch(FileNotFoundExceptionx){log.error("Invalidfile");return;}}}不允許拋出RuntimeException,Exception,Throwable不對(duì)的的寫(xiě)法:booleanisCapitalized(Strings){if(s==null){thrownewRuntimeException("NullString");}}privatevoiddoSomething()throwsException{//...}對(duì)的寫(xiě)法:booleanisCapitalized(Strings){if(s==null){thrownewNullPointerException();}}privatevoiddoSomething()throwsIOException{//...}不要捕獲NullPointerException或其他父類異常不對(duì)的的寫(xiě)法:booleanisName(Strings){try{Stringnames[]=s.split("");if(names.length!=2){returnfalse;}return(isCapitalized(names[0])&&isCapitalized(names[1]));}catch(NullPointerExceptione){returnfalse;}}對(duì)的的寫(xiě)法:booleanisName(Strings)/*throwsNullPointerException*/{Stringnames[]=s.split("");if(names.length!=2){returnfalse;}return(isCapitalized(names[0])&&isCapitalized(names[1]));}多線程編程保證共享變量的可見(jiàn)性對(duì)于共享變量,要保證一個(gè)線程對(duì)它的改動(dòng)對(duì)其他線程是可見(jiàn)的。線程也許會(huì)看到一個(gè)陳舊的共享變量的值。為了共享變量是最新的,可以將變量聲明為volatile或同步讀取和寫(xiě)入操作。將共享變量聲明為volatile:finalclassControlledStopimplementsRunnable{privatevolatilebooleandone=false;@Overridepublicvoidrun(){while(!done){try{//...Thread.currentThread().sleep(1000);//Dosomething}catch(InterruptedExceptionie){Thread.currentThread().interrupt();//Resetinterruptedstatus}}}publicvoidshutdown(){done=true;}}同步讀取和寫(xiě)入操作:finalclassControlledStopimplementsRunnable{privatebooleandone=false;@Overridepublicvoidrun(){while(!isDone()){try{//...Thread.currentThread().sleep(1000);//Dosomething}catch(InterruptedExceptionie){Thread.currentThread().interrupt();//Resetinterruptedstatus}}}publicsynchronizedbooleanisDone(){returndone;}publicsynchronizedvoidshutdown(){done=true;}}保證共享變量的操作是原子的除了要保證共享變量的更新對(duì)其他線程可見(jiàn)的,還需要保證對(duì)共享變量的操作是原子的,這時(shí)將共享變量聲明為volatile往往是不夠的。需要使用同步機(jī)制或Lock同步讀取和寫(xiě)入操作:finalclassFlag{privatevolatilebooleanflag=true;publicsynchronizedvoidtoggle(){flag^=true;//Sameasflag=!flag;}publicbooleangetFlag(){returnflag;}}//使用讀取鎖保證讀取和寫(xiě)入操作的原子性finalclassFlag{privatebooleanflag=true;privatefinalReadWriteLocklock=newReentrantReadWriteLock();privatefinalLockreadLock=lock.readLock();privatefinalLockwriteLock=lock.writeLock();publicvoidtoggle(){writeLock.lock();try{flag^=true;//Sameasflag=!flag;}finally{writeLock.unlock();}}publicbooleangetFlag(){readLock.lock();try{returnflag;}finally{readLock.unlock();}}}不要調(diào)用Thread.run(),不要使用Thread.stop()以終止線程保證執(zhí)行阻塞操作的線程可以終止publicfinalclassSocketReaderimplementsRunnable{privatefinalSocketChannelsc;privatefinalObjectlock=newObject();publicSocketReader(Stringhost,intport)throwsIOException{sc=SocketChannel.open(newInetSocketAddress(host,port));}@Overridepublicvoidrun(){ByteBufferbuf=ByteBuffer.allocate(1024);try{synchronized(lock){while(!Terrupted()){sc.read(buf);//...}}}catch(IOExceptionie){//Forwardtohandler}}publicstaticvoidmain(String[]args)throwsIOException,InterruptedException{SocketReaderreader=newSocketReader("somehost",25);Threadthread=newThread(reader);thread.start();Thread.sleep(1000);errupt();}}互相依存的任務(wù)不要在一個(gè)有限的線程池執(zhí)行有限線程池指定可以同時(shí)執(zhí)行在線程池中的線程數(shù)量的上限。程序不得使用有限線程池線程執(zhí)行互相依賴的任務(wù)。也許會(huì)導(dǎo)致線程饑餓死鎖,所有的線程池執(zhí)行的任務(wù)正在等待一個(gè)可用的線程中執(zhí)行一個(gè)內(nèi)部隊(duì)列阻塞輸入輸出程序終止前刪除臨時(shí)文獻(xiàn)檢測(cè)和解決文獻(xiàn)相關(guān)的錯(cuò)誤Java的文獻(xiàn)操作方法往往有一個(gè)返回值,而不是拋出一個(gè)異常,表達(dá)失敗。因此,忽略返回值文獻(xiàn)操作的程序,往往無(wú)法檢測(cè)到這些操作是否失敗。Java程序必須檢查執(zhí)行文獻(xiàn)I/O方法的返回值。不對(duì)的的寫(xiě)法:Filefile=newFile(args[0]);file.delete();對(duì)的的寫(xiě)法:Filefile=newFile("file");if(!file.delete()){log.error("Deletionfailed");}及時(shí)釋放資源垃圾收集器無(wú)法釋放非內(nèi)存資源,如打開(kāi)的文獻(xiàn)描述符與數(shù)據(jù)庫(kù)的連接。因此,不釋放資源,也許導(dǎo)致資源耗盡襲擊。try{finalFileInputStreamstream=newFileInputStream(fileName);try{finalBufferedReaderbufRead=newBufferedReader(newInputStreamReader(stream));Stringline;while((line=bufRead.readLine())!=null){sendLine(line);}}finally{if(stream!=null){try{stream.close();}catch(IOExceptione){//forwardtohandler}}}}catch(IOExceptione){//forwardtohandler}序列化不要序列化未加密的敏感數(shù)據(jù)序列化允許一個(gè)對(duì)象的狀態(tài)被保存為一個(gè)字節(jié)序列,然后重新在稍后的時(shí)間恢復(fù),它沒(méi)有提供任何機(jī)制來(lái)保護(hù)序列化的數(shù)據(jù)。敏感的數(shù)據(jù)不應(yīng)當(dāng)被序列化的例子涉及加密密鑰,數(shù)字證書(shū)。解決方法:對(duì)于數(shù)據(jù)成員可以使用transient,聲明該數(shù)據(jù)成員是瞬態(tài)的。重寫(xiě)序列化相關(guān)方法writeObject、readObject、readObjectNoData,防止被子類惡意重寫(xiě)classSensitiveClassextendsNumber{//...protectedfinalObjectwriteObject(java.io.ObjectOutputStreamout)throwsNotSerializableException{thrownewNotSerializableException();}protectedfinalObjectreadObject(java.io.ObjectInputStreamin)throwsNotSeriali
溫馨提示
- 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒(méi)有圖紙預(yù)覽就沒(méi)有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。
最新文檔
- 出的幼兒園主題月活動(dòng)方案
- 2025年中國(guó)水貂養(yǎng)殖行業(yè)市場(chǎng)深度分析及投資潛力預(yù)測(cè)報(bào)告
- 企業(yè)IPO上市培訓(xùn)課件
- 消防演練方案模板
- 餐飲講師培訓(xùn)課件
- 倉(cāng)儲(chǔ)安全設(shè)施定期檢測(cè)與維護(hù)計(jì)劃考核試卷
- 甲醇生產(chǎn)成本控制考核試卷
- 基礎(chǔ)陳列培訓(xùn)課件
- 生物分子光譜在生物醫(yī)學(xué)成像中的成像機(jī)理研究考核試卷
- 培訓(xùn)講師課件邏輯
- 《太原星河灣規(guī)劃》課件
- 裝配鉗工基本技能培訓(xùn)
- 幼兒園大班音樂(lè)活動(dòng)《粉刷匠》課件
- 第五講鑄牢中華民族共同體意識(shí)-2024年形勢(shì)與政策
- 建筑工程項(xiàng)目施工合同范本
- DB11∕T 1692-2019 城市樹(shù)木健康診斷技術(shù)規(guī)程
- 期末練習(xí)卷(試題)-2024-2025學(xué)年四年級(jí)上冊(cè)數(shù)學(xué)滬教版
- 汽車吊起重吊裝方案
- 法律意見(jiàn)書(shū)(適用于股權(quán)投資)
- 勞務(wù)裝修包清工勞務(wù)合同
- DB11T 418-2019 電梯日常維護(hù)保養(yǎng)規(guī)則
評(píng)論
0/150
提交評(píng)論