1. 程式人生 > >Http協議與表單防止重複提交實戰解決方案

Http協議與表單防止重複提交實戰解決方案

http連線與短連線

HTTP協議與TCP/IP協議的關係

HTTP的長連線和短連線本質上是TCP長連線和短連線。HTTP屬於應用層協議,在傳輸層使用TCP協議,在網路層使用IP協議。IP協議主要解決網路路由和定址問題,TCP協議主要解決如何在IP層之上可靠的傳遞資料包,使在網路上的另一端收到發端發出的所有包,並且順序與發出順序一致。TCP有可靠,面向連線的特點。

如何理解HTTP協議是無狀態的

HTTP協議是無狀態的,指的是協議對於事務處理沒有記憶能力,伺服器不知道客戶端是什麼狀態。也就是說,開啟一個伺服器上的網頁和你之前開啟這個伺服器上的網頁之間沒有任何聯絡。HTTP是一個無狀態的面向連線的協議,無狀態不代表HTTP不能保持TCP連線,更不能代表HTTP使用的是UDP協議(無連線)。

什麼是長連線、短連線?

在HTTP/1.0中,預設使用的是短連線。也就是說,瀏覽器和伺服器每進行一次HTTP操作,就建立一次連線,但任務結束就中斷連線。如果客戶端瀏覽器訪問的某個HTML或其他型別的 Web頁中包含有其他的Web資源,如JavaScript檔案、影象檔案、CSS檔案等;當瀏覽器每遇到這樣一個Web資源,就會建立一個HTTP會話。

但從 HTTP/1.1起,預設使用長連線,用以保持連線特性。使用長連線的HTTP協議,會在響應頭有加入這行程式碼:Connection:keep-alive

在使用長連線的情況下,當一個網頁開啟完成後,客戶端和伺服器之間用於傳輸HTTP資料的 TCP連線不會關閉,如果客戶端再次訪問這個伺服器上的網頁,會繼續使用這一條已經建立的連線。Keep-Alive不會永久保持連線,它有一個保持時間,可以在不同的伺服器軟體(如Apache)中設定這個時間。實現長連線要客戶端和服務端都支援長連線。

HTTP協議的長連線和短連線,實質上是TCP協議的長連線和短連線。

TCP連線

當網路通訊時採用TCP協議時,在真正的讀寫操作之前,server與client之間必須建立一個連線,當讀寫操作完成後,雙方不再需要這個連線 時它們可以釋放這個連線,連線的建立是需要三次握手的,而釋放則需要4次握手,所以說每個連線的建立都是需要資源消耗和時間消耗的

經典的三次握手示意圖:

經典的四次握手關閉圖:

TCP短連線

我們模擬一下TCP短連線的情況,client向server發起連線請求,server接到請求,然後雙方建立連線。client向server 傳送訊息,server迴應client,然後一次讀寫就完成了,這時候雙方任何一個都可以發起close操作,不過一般都是client先發起 close操作。為什麼呢,一般的server不會回覆完client後立即關閉連線的,當然不排除有特殊的情況。從上面的描述看,短連線一般只會在 client/server間傳遞一次讀寫操作

短連線的優點是:管理起來比較簡單,存在的連線都是有用的連線,不需要額外的控制手段

 TCP長連線

接下來我們再模擬一下長連線的情況,client向server發起連線,server接受client連線,雙方建立連線。Client與server完成一次讀寫之後,它們之間的連線並不會主動關閉,後續的讀寫操作會繼續使用這個連線。

首先說一下TCP/IP詳解上講到的TCP保活功能,保活功能主要為伺服器應用提供,伺服器應用希望知道客戶主機是否崩潰,從而可以代表客戶使用資源。如果客戶已經消失,使得伺服器上保留一個半開放的連線,而伺服器又在等待來自客戶端的資料,則伺服器將應遠等待客戶端的資料,保活功能就是試圖在服務 器端檢測到這種半開放的連線。

如果一個給定的連線在兩小時內沒有任何的動作,則伺服器就向客戶發一個探測報文段,客戶主機必須處於以下4個狀態之一:

客戶主機依然正常執行,並從伺服器可達。客戶的TCP響應正常,而伺服器也知道對方是正常的,伺服器在兩小時後將保活定時器復位。

客戶主機已經崩潰,並且關閉或者正在重新啟動。在任何一種情況下,客戶的TCP都沒有響應。服務端將不能收到對探測的響應,並在75秒後超時。伺服器總共傳送10個這樣的探測 ,每個間隔75秒。如果伺服器沒有收到一個響應,它就認為客戶主機已經關閉並終止連線。

客戶主機崩潰並已經重新啟動。伺服器將收到一個對其保活探測的響應,這個響應是一個復位,使得伺服器終止這個連線。

客戶機正常執行,但是伺服器不可達,這種情況與2類似,TCP能發現的就是沒有收到探查的響應。

 

長連線短連線操作過程

短連線的操作步驟是:

建立連線——資料傳輸——關閉連線…建立連線——資料傳輸——關閉連線

長連線的操作步驟是:

建立連線——資料傳輸…(保持連線)…資料傳輸——關閉連線

長連線是什麼時候關閉

  1. 響應頭Keep-Alive: timeout。這個值能夠讓一些瀏覽器主動關閉連線,這樣伺服器就不必要去關閉連線了。
  2. tcp自動探測一次,發現對方關閉,則斷開連線

長連線和短連線的優點和缺點

由上可以看出,長連線可以省去較多的TCP建立和關閉的操作,減少浪費,節約時間。對於頻繁請求資源的客戶來說,較適用長連線。不過這裡存在一個問題,存活功能的探測週期太長,還有就是它只是探測TCP連線的存活,屬於比較斯文的做法,遇到惡意的連線時,保活功能就不夠使了。在長連線的應用場景下,client端一般不會主動關閉它們之間的連線,Client與server之間的連線如果一直不關閉的話,會存在一個問題,隨著客戶端連線越來越多,server早晚有扛不住的時候,這時候server端需要採取一些策略,如關閉一些長時間沒有讀寫事件發生的連線,這樣可 以避免一些惡意連線導致server端服務受損;如果條件再允許就可以以客戶端機器為顆粒度,限制每個客戶端的最大長連線數,這樣可以完全避免某個蛋疼的客戶端連累後端服務。

短連線對於伺服器來說管理較為簡單,存在的連線都是有用的連線,不需要額外的控制手段。但如果客戶請求頻繁,將在TCP的建立和關閉操作上浪費時間和頻寬。

長連線和短連線的產生在於client和server採取的關閉策略,具體的應用場景採用具體的策略,沒有十全十美的選擇,只有合適的選擇。

什麼時候長連線?什麼時候用短連線?


長連線多用於操作頻繁,點對點的通訊,而且連線數不能太多情況,。每個TCP連線都需要三步握手,這需要時間,如果每個操作都是先連線,再操作的話那麼處理速度會降低很多,所以每個操作完後都不斷開,次處理時直接傳送資料包就OK了,不用建立TCP連線。例如:資料庫的連線用長連線, 如果用短連線頻繁的通訊會造成socket錯誤,而且頻繁的socket 建立也是對資源的浪費。

而像WEB網站的http服務一般都用短連結,因為長連線對於服務端來說會耗費一定的資源,而像WEB網站這麼頻繁的成千上萬甚至上億客戶端的連線用短連線會更省一些資源,如果用長連線,而且同時有成千上萬的使用者,如果每個使用者都佔用一個連線的話,那可想而知吧。所以併發量大,但每個使用者無需頻繁操作情況下需用短連好。

連線與短連線應用場景

長連線應用於場景 客戶端(移動App)與伺服器訊息推送、RPC遠端呼叫

 

 

http請求工具

客戶端模擬http請求工具

Postman(谷歌外掛)、RestClient

伺服器模擬http請求工具

httpclient、HttpURLConnection

httpCient請求程式碼

/**

 * 傳送 post請求訪問本地應用並根據傳遞引數不同返回不同結果

 */

public void post() {

// 建立預設的httpClient例項.

CloseableHttpClient httpclient = HttpClients.createDefault();

// 建立httppost

HttpPost httppost = new HttpPost("http://localhost:8080/myDemo/Ajax/serivceJ.action");

// 建立引數佇列

List<NameValuePair> formparams = new ArrayList<NameValuePair>();

formparams.add(new BasicNameValuePair("type", "house"));

UrlEncodedFormEntity uefEntity;

try {

uefEntity = new UrlEncodedFormEntity(formparams, "UTF-8");

httppost.setEntity(uefEntity);

System.out.println("executing request " + httppost.getURI());

CloseableHttpResponse response = httpclient.execute(httppost);

try {

HttpEntity entity = response.getEntity();

if (entity != null) {

System.out.println("--------------------------------------");

System.out.println("Response content: " + EntityUtils.toString(entity, "UTF-8"));

System.out.println("--------------------------------------");

}

} finally {

response.close();

}

} catch (ClientProtocolException e) {

e.printStackTrace();

} catch (UnsupportedEncodingException e1) {

e1.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} finally {

// 關閉連線,釋放資源

try {

httpclient.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}



public static void get() {

CloseableHttpClient httpclient = HttpClients.createDefault();

//請求超時

httpclient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 60000);

//讀取超時

httpclient.getParams().setParameter(CoreConnectionPNames.SO_TIMEOUT, 60000);

try {

// 建立httpget.

HttpGet httpget = new HttpGet("http://www.baidu.com/");

System.out.println("executing request " + httpget.getURI());

// 執行get請求.

CloseableHttpResponse response = httpclient.execute(httpget);

try {

// 獲取響應實體

HttpEntity entity = response.getEntity();

System.out.println("--------------------------------------");

// 列印響應狀態

System.out.println(response.getStatusLine());

if (entity != null) {

// 列印響應內容長度

System.out.println("Response content length: " + entity.getContentLength());

// 列印響應內容

System.out.println("Response content: " + EntityUtils.toString(entity));

}

System.out.println("------------------------------------");

} finally {

response.close();

}

} catch (ClientProtocolException e) {

e.printStackTrace();

} catch (ParseException e) {

e.printStackTrace();

} catch (IOException e) {

e.printStackTrace();

} finally {

// 關閉連線,釋放資源

try {

httpclient.close();

} catch (IOException e) {

e.printStackTrace();

}

}



}

 

前端ajax請求  

   

$.ajax({

type : 'post',

dataType : "text",

url : "http://a.a.com/a/FromUserServlet",

data : "userName=餘勝軍&userAge=19",

success : function(msg) {

alert(msg);

}

});

 

跨域實戰解決方案

跨域原因產生:在當前域名請求網站中,預設不允許通過ajax請求傳送其他域名。

伺服器端程式碼

@WebServlet("/FromServlet")

public class FromServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

String userName = req.getParameter("userName");
JSONObject jsonObject = new JSONObject();
jsonObject.put("userName", userName);
resp.getWriter().println(jsonObject.toJSONString());

}

}

 

前端程式碼

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>B網站訪問</title>

</head>

<script type="text/javascript"

src="http://www.itmayiedu.com/static/common/jquery-1.7.2.min.js?t=2017-07-27"></script>

<script type="text/javascript">

$(document).ready(function() {

$.ajax({

type : "GET",

async : false,

url : "http://a.a.com/a/FromServlet?userName=644064",

dataType : "json",

success : function(data) {

alert(data["userName"]);

},

error : function() {

alert('fail');

}

});

});

</script>

<body>

<img alt="" src="http://a.a.com/a/imgs/log.png">

</body>

</html></script>

<body>

<img alt="" src="http://a.a.com/a/imgs/log.png">

</body>

</html>

 

 

XMLHttpRequest cannot load 跨域問題解決辦法

 

解決方案

 

使用後臺response新增header

後臺response新增header,response.setHeader("Access-Control-Allow-Origin", "*"); 支援所有網站

使用JSONP

前端程式碼:

$.ajax({

type : "POST",

async : false,

url : "http://a.a.com/a/FromUserServlet?userName=張三",

dataType : "jsonp",//資料型別為jsonp  

jsonp : "jsonpCallback",//服務端用於接收callback呼叫的function名的引數  

success : function(data) {

alert(data.result);

},

error : function() {

alert('fail');

}

});

 

jquery 中jsonp的實現原理

 

在同源策略下,在某個伺服器下的頁面是無法獲取到該伺服器以外的資料的,即一般的ajax是不能進行跨域請求的。但 img、iframe 、script等標籤是個例外,這些標籤可以通過src屬性請求到其他伺服器上的資料。利用 script標籤的開放策略,我們可以實現跨域請求資料,當然這需要伺服器端的配合。 Jquery中ajax 的核心是通過 XmlHttpRequest獲取非本頁內容,而jsonp的核心則是動態新增 <script>標籤來呼叫伺服器提供的 js指令碼。

  當我們正常地請求一個JSON資料的時候,服務端返回的是一串 JSON型別的資料,而我們使用 JSONP模式來請求資料的時候服務端返回的是一段可執行的 JavaScript程式碼。因為jsonp 跨域的原理就是用的動態載入 script的src ,所以我們只能把引數通過 url的方式傳遞,所以jsonp的 type型別只能是get 

示例:

$.ajax({

    url: 'http://192.168.1.114/yii/demos/test.php', //不同的域

    type: 'GET', // jsonp模式只有GET 是合法的

    data: {

        'action': 'aaron'

    },

    dataType: 'jsonp', // 資料型別

    jsonp: 'backfunc', // 指定回撥函式名,與伺服器端接收的一致,並回傳回來

})

其實jquery 內部會轉化成

http://192.168.1.114/yii/demos/test.php?backfunc=jQuery2030038573939353227615_1402643146875&action=aaron

然後動態載入

<script type="text/javascript"src="http://192.168.1.114/yii/demos/test.php?backfunc= jQuery2030038573939353227615_1402643146875&action=aaron"></script>

然後後端就會執行backfunc(傳遞引數 ),把資料通過實參的形式傳送出去。

  使用JSONP 模式來請求資料的整個流程:

客戶端傳送一個請求,規定一個可執行的函式名(這裡就是 jQuery做了封裝的處理,自動幫你生成回撥函式並把資料取出來供success屬性方法來呼叫,而不是傳遞的一個回撥控制代碼),伺服器端接受了這個 backfunc函式名,然後把資料通過實參的形式傳送出去

 

(在jquery 原始碼中, jsonp的實現方式是動態新增<script>標籤來呼叫伺服器提供的 js指令碼。jquery 會在window物件中載入一個全域性的函式,當 <script>程式碼插入時函式執行,執行完畢後就 <script>會被移除。同時jquery還對非跨域的請求進行了優化,如果這個請求是在同一個域名下那麼他就會像正常的 Ajax請求一樣工作。)

 

 

後端程式碼:

@WebServlet("/FromUserServlet")

public class FromUserServlet extends HttpServlet {



@Override

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

doPost(req, resp);

}



@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

resp.setCharacterEncoding("UTF-8");
// resp.setHeader("Access-Control-Allow-Origin", "*");
 String userName = req.getParameter("userName");
 String userAge = req.getParameter("userAge");
 System.out.println(userName + "----" + userAge+"---"+req.getMethod());
// JSONObject JSONObject1 = new JSONObject();
// JSONObject1.put("success", "新增成功!");
// resp.getWriter().write("callbackparam(" + JSONObject1.toJSONString()
// + ")");


try {
resp.setContentType("text/plain");
resp.setHeader("Pragma", "No-cache");
resp.setHeader("Cache-Control", "no-cache");
resp.setDateHeader("Expires", 0);
PrintWriter out = resp.getWriter();
JSONObject resultJSON = new JSONObject(); // 根據需要拼裝json
resultJSON.put("result", "content");
String jsonpCallback = req.getParameter("jsonpCallback");// 客戶端請求引數
out.println(jsonpCallback + "(" + resultJSON.toJSONString() + ")");// 返回jsonp格式資料
out.flush();
out.close();

} catch (Exception e) {

// TODO: handle exception

}

}

}

 

JSONP的優缺點:

JSONP只支援get請求不支援psot請求

後臺Http請求轉發

  使用HttpClinet轉發進行轉發

使用介面閘道器

使用nginx轉發。

使用SpringCloud閘道器

表單重複提交解決方案(防止Http重複提交

場景模擬

  建立一個from.jsp頁面

 

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

  <head>

    <title>Form表單</title>
  </head>

  <body>
      <form action="${pageContext.request.contextPath}/DoFormServlet" method="post">

        使用者名稱:<input type="text" name="userName">

        <input type="submit" value="提交" id="submit">

    </form>

  </body>

</html>

DoFormServlet 程式碼

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@WebServlet("/DoFormServlet")
public class DoFormServlet extends HttpServlet {
@Override

protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

req.setCharacterEncoding("UTF-8");

String userName = req.getParameter("userName");

try {

Thread.sleep(300);

} catch (Exception e) {

// TODO: handle exception

}

System.out.println("往資料庫插入資料...."+userName);

resp.getWriter().write("success");

}



}

 

網路延時

 在平時開發中,如果網速比較慢的情況下,使用者提交表單後,發現伺服器半天都沒有響應,那麼使用者可能會以為是自己沒有提交表單,就會再點選提交按鈕重複提交表單,我們在開發中必須防止表單重複提交。

重新重新整理

表單提交後用戶點選【重新整理】按鈕導致表單重複提交

點選瀏覽器的【後退】按鈕回退到表單頁面後進行再次提交

使用者提交表單後,點選瀏覽器的【後退】按鈕回退到表單頁面後進行再次提交

解決方案

使用javascript 解決

 既然存在上述所說的表單重複提交問題,那麼我們就要想辦法解決,比較常用的方法是採用JavaScript來防止表單重複提交,具體做法如下:

修改form.jsp頁面,新增如下的JavaScript程式碼來防止表單重複提交

程式碼:

<%@ page language="java" contentType="text/html; charset=UTF-8"

pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<title>Form表單</title>

<script type="text/javascript">

var isFlag = false; //表單是否已經提交標識,預設為false

function submitFlag() {

if (!isFlag) {

isFlag = true;

return true;

} else {

return false;

}

}
</script>
</head>

<body>

<form action="${pageContext.request.contextPath}/DoFormServlet"

method="post" onsubmit="return submitFlag()">

使用者名稱:<input type="text" name="userName"> <input type="submit"

value="提交" id="submit">

</form>

</body>

</html>

 除了用這種方式之外,經常見的另一種方式就是表單提交之後,將提交按鈕設定為不可用,讓使用者沒有機會點選第二次提交按鈕,程式碼如下:

 

function dosubmit(){

    //獲取表單提交按鈕

    var btnSubmit = document.getElementById("submit");

    //將表單提交按鈕設定為不可用,這樣就可以避免使用者再次點選提交按鈕

    btnSubmit.disabled= "disabled";

    //返回true讓表單可以正常提交

    return true;

}

 

 

使用後端解決表單重複提交的問題

 對於【場景二】和【場景三】導致表單重複提交的問題,既然客戶端無法解決,那麼就在伺服器端解決,在伺服器端解決就需要用到session了。

  具體的做法:在伺服器端生成一個唯一的隨機標識號,專業術語稱為Token(令牌),同時在當前使用者的Session域中儲存這個Token。然後將Token傳送到客戶端的Form表單中,在Form表單中使用隱藏域來儲存這個Token,表單提交的時候連同這個Token一起提交到伺服器端,然後在伺服器端判斷客戶端提交上來的Token與伺服器端生成的Token是否一致,如果不一致,那就是重複提交了,此時伺服器端就可以不處理重複提交的表單。如果相同則處理表單提交,處理完後清除當前使用者的Session域中儲存的標識號。
  在下列情況下,伺服器程式將拒絕處理使用者提交的表單請求:

儲存Session域中的Token(令牌)與表單提交的Token(令牌)不同。

當前使用者的Session中不存在Token(令牌)

使用者提交的表單資料中沒有Token(令牌)

轉發程式碼:

@WebServlet("/ForwardServlet")
public class ForwardServlet extends HttpServlet {

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

req.getSession().setAttribute("sesionToken", TokenUtils.getToken());

req.getRequestDispatcher("form.jsp").forward(req, resp);

}

}

轉發頁面:

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<title>Form表單</title>

</head>

<body>

<form action="${pageContext.request.contextPath}/DoFormServlet"

method="post" onsubmit="return dosubmit()">

<input type="hidden" name="token" value="${sesionToken}"> 使用者名稱:<input type="text"

name="userName"> <input type="submit" value="提交" id="submit">

</form>

</body>

</html>

後端Java程式碼:

@WebServlet("/DoFormServlet")
public class DoFormServlet extends HttpServlet {

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

req.setCharacterEncoding("UTF-8");

boolean flag = isFlag(req);

if (!flag) {

resp.getWriter().write("已經提交...");

System.out.println("資料已經提交了..");
return;
}

String userName = req.getParameter("userName");

try {

Thread.sleep(300);

} catch (Exception e) {

// TODO: handle exception

}
System.out.println("往資料庫插入資料...." + userName);

resp.getWriter().write("success");

}

public boolean isFlag(HttpServletRequest request) {

HttpSession session = request.getSession();

String sesionToken = (String) session.getAttribute("sesionToken");

String token = request.getParameter("token");

if (!(token.equals(sesionToken))) {

return false;

}

session.removeAttribute("sesionToken");

return true;

}

}

 

 

Filter 也稱之為過濾器,它是 Servlet 技術中最實用的技術,Web 開發人員通過 Filter 技術,對 web 伺服器管理的所有 web 資源:例如 Jsp, Servlet, 靜態圖片檔案或靜態 html 檔案等進行攔截,從而實現一些特殊的功能。例如實現 URL 級別的許可權訪問控制、過濾敏感詞彙、壓縮響應資訊等一些高階功能。

它主要用於對使用者請求進行預處理,也可以對 HttpServletResponse 進行後處理。使用 Filter 的完整流程:Filter 對使用者請求進行預處理,接著將請求交給 Servlet 進行處理並生成響應,最後 Filter 再對伺服器響應進行後處理。

使用Fileter防止XSS攻擊

什麼是XSS攻擊?

XSS攻擊使用Javascript指令碼注入進行攻擊

例如在表單中注入: <script>location.href='http://www.itmayiedu.com'</script>

注意:谷歌瀏覽器 已經防止了XSS攻擊,為了演示效果,最好使用火狐瀏覽器

例項:

演示:

程式碼: fromToXss.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>

</head>

<body>

<form action="XssDemo" method="post">

<input type="text" name="userName"> <input type="submit">

</form>

</body>

</html>

程式碼: XssDemo

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@WebServlet("/XssDemo")
public class XssDemo extends HttpServlet {

@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {

String userName = req.getParameter("userName");

req.setAttribute("userName", userName);

req.getRequestDispatcher("showUserName.jsp").forward(req, resp);

}
}

 

程式碼: showUserName.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">

<html>

<head>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">

<title>Insert title here</title>

</head>

<body>userName:${userName}
</body>
</html>

 

解決方案

使用Fileter過濾器過濾器注入標籤

XSSFilter

public class XssFiter implements Filter {

public void init(FilterConfig filterConfig) throws ServletException {

}

public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {

HttpServletRequest req = (HttpServletRequest) request;

XssAndSqlHttpServletRequestWrapper xssRequestWrapper = new XssAndSqlHttpServletRequestWrapper(req);

chain.doFilter(xssRequestWrapper, response);

}

public void destroy() {


}
}

 

XssAndSqlHttpServletRequestWrapper

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;

/**

 * 防止XSS攻擊

 */

public class XssAndSqlHttpServletRequestWrapper extends HttpServletRequestWrapper {
HttpServletRequest request;
public XssAndSqlHttpServletRequestWrapper(HttpServletRequest request) {

super(request);

this.request = request;

}

@Override
public String getParameter(String name) {

String value = request.getParameter(name);

System.out.println("name:" + name + "," + value);

if (!StringUtils.isEmpty(value)) {

// 轉換Html

value = StringEscapeUtils.escapeHtml4(value);

}

return value;

}

}

可以自己使用Cookie實現自動登入效果。

 

需要用到maven座標

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->

<dependency>

    <groupId>org.apache.commons</groupId>

    <artifactId>commons-lang3</artifactId>

    <version>3.4</version>

</dependency>