Session 是另外一種記錄瀏覽器狀態的機制。不一樣的是Cookie保存在瀏覽器中,Session保存在服務器中。用戶使用瀏覽器訪問服務器的時候,服務器把用戶的信息以某種的形式記錄在服務器,這就是Sessionjavascript
若是說Cookie是檢查用戶身上的」通行證「來確認用戶的身份,那麼Session就是經過檢查服務器上的」客戶明細表「來確認用戶的身份的。Session至關於在服務器中創建了一份「客戶明細表」。php
Session比Cookie使用方便,Session能夠解決Cookie解決不了的事情【Session能夠存儲對象,Cookie只能存儲字符串。】。html
從上面的API看出,Session有着request和ServletContext相似的方法。其實Session也是一個域對象。Session做爲一種記錄瀏覽器狀態的機制,只要Session對象沒有被銷燬,Servlet之間就能夠經過Session對象實現通信java
//獲得Session對象
HttpSession httpSession = request.getSession();
//設置Session屬性
httpSession.setAttribute("name", "看完博客就要點贊!!");
複製代碼
//獲取到從Servlet4的Session存進去的值
HttpSession httpSession = request.getSession();
String value = (String) httpSession.getAttribute("name");
System.out.println(value);
複製代碼
Session在用戶第一次訪問服務器Servlet,jsp等動態資源就會被自動建立,Session對象保存在內存裏,這也就爲何上面的例子能夠直接使用request對象獲取獲得Session對象。web
若是訪問HTML,IMAGE等靜態資源Session不會被建立。數據庫
Session生成後,只要用戶繼續訪問,服務器就會更新Session的最後訪問時間,不管是否對Session進行讀寫,服務器都會認爲Session活躍了一次。跨域
因爲會有愈來愈多的用戶訪問服務器,所以Session也會愈來愈多。爲了防止內存溢出,服務器會把長時間沒有活躍的Session從內存中刪除,這個時間也就是Session的超時時間。瀏覽器
Session的超時時間默認是30分鐘,有三種方式能夠對Session的超時時間進行修改緩存
**第一種方式:**在tomcat/conf/web.xml文件中設置,時間值爲20分鐘,全部的WEB應用都有效tomcat
<session-config>
<session-timeout>20</session-timeout>
</session-config>
複製代碼
<session-config>
<session-timeout>20</session-timeout>
</session-config>
複製代碼
//設置Session最長超時時間爲60秒,這裏的單位是秒
httpSession.setMaxInactiveInterval(60);
System.out.println(httpSession.getMaxInactiveInterval());
複製代碼
response.setContentType("text/html;charset=UTF-8");
PrintWriter printWriter = response.getWriter();
printWriter.write("網頁上全部的書籍:" + "<br/>");
//拿到數據庫全部的書
LinkedHashMap<String, Book> linkedHashMap = DB.getAll();
Set<Map.Entry<String, Book>> entry = linkedHashMap.entrySet();
//顯示全部的書到網頁上
for (Map.Entry<String, Book> stringBookEntry : entry) {
Book book = stringBookEntry.getValue();
String url = "/ouzicheng/Servlet6?id=" + book.getId();
printWriter.write(book.getName());
printWriter.write("<a href='" + url + "'>購買</a>");
printWriter.write("<br/>");
}
複製代碼
//獲得用戶想買書籍的id
String id = request.getParameter("id");
//根據書籍的id找到用戶想買的書
Book book = (Book) DB.getAll().get(id);
//獲取到Session對象
HttpSession httpSession = request.getSession();
//因爲用戶可能想買多本書的,因此咱們用一個容器裝着書籍
List list = (List) httpSession.getAttribute("list");
if (list == null) {
list = new ArrayList();
//設置Session屬性
httpSession.setAttribute("list",list);
}
//把書籍加入到list集合中
list.add(book);
複製代碼
//獲得用戶想買書籍的id
String id = request.getParameter("id");
//根據書籍的id找到用戶想買的書
Book book = (Book) DB.getAll().get(id);
//獲取到Session對象
HttpSession httpSession = request.getSession();
//建立List集合
List list = new ArrayList();
list.add(book);
httpSession.setAttribute("list", list);
複製代碼
//獲得用戶想買書籍的id
String id = request.getParameter("id");
//根據書籍的id找到用戶想買的書
Book book = (Book) DB.getAll().get(id);
//獲取到Session對象
HttpSession httpSession = request.getSession();
//因爲用戶可能想買多本書的,因此咱們用一個容器裝着書籍
List list = (List) httpSession.getAttribute("list");
if (list == null) {
list = new ArrayList();
//設置Session屬性
httpSession.setAttribute("list",list);
}
//把書籍加入到list集合中
list.add(book);
String url = "/ouzicheng/Servlet7";
response.sendRedirect(url);
複製代碼
//要獲得用戶購買過哪些書籍,獲得Session的屬性遍歷便可
HttpSession httpSession = request.getSession();
List<Book> list = (List) httpSession.getAttribute("list");
if (list == null || list.size() == 0) {
printWriter.write("對不起,你尚未買過任何商品");
} else {
printWriter.write("您購買過如下商品:");
printWriter.write("<br/>");
for (Book book : list) {
printWriter.write(book.getName());
printWriter.write("<br/>");
}
}
複製代碼
//獲得Session對象
HttpSession httpSession = request.getSession();
//設置Session屬性
httpSession.setAttribute("name", "看完博客就要點贊!!");
複製代碼
String value = (String) request.getSession().getAttribute("name");
printWriter.write(value);
複製代碼
如今問題來了:服務器是如何實現一個session爲一個用戶瀏覽器服務的?換個說法:爲何服務器可以爲不一樣的用戶瀏覽器提供不一樣session?
HTTP協議是無狀態的,Session不能依據HTTP鏈接來判斷是否爲同一個用戶。因而乎:服務器向用戶瀏覽器發送了一個名爲JESSIONID的Cookie,它的值是Session的id值。其實Session依據Cookie來識別是不是同一個用戶。
簡單來講:Session 之因此能夠識別不一樣的用戶,依靠的就是Cookie
該Cookie是服務器自動頒發給瀏覽器的,不用咱們手工建立的。該Cookie的maxAge值默認是-1,也就是說僅當前瀏覽器使用,不將該Cookie存在硬盤中
咱們來捋一捋思路流程:當咱們訪問Servlet4的時候,服務器就會建立一個Session對象,執行咱們的程序代碼,並自動頒發個Cookie給用戶瀏覽器
上面說了Session是依靠Cookie來識別用戶瀏覽器的。若是個人用戶瀏覽器禁用了Cookie了呢?絕大多數的手機瀏覽器都不支持Cookie,那個人Session怎麼辦?
一看,Session好像不能用了。可是Java Web提供瞭解決方法:URL地址重寫
HttpServletResponse類提供了兩個URL地址重寫的方法:
須要值得注意的是:這兩個方法會自動判斷該瀏覽器是否支持Cookie,若是支持Cookie,重寫後的URL地址就不會帶有jsessionid了【固然了,即便瀏覽器支持Cookie,第一次輸出URL地址的時候仍是會出現jsessionid(由於沒有任何Cookie可帶)】
下面咱們就以上面「購物」的例子來作試驗吧!首先咱們來看看禁用掉Cookie對原來的小例子有什麼影響。
訪問Servlet1,隨便點擊一本書籍購買
緣由也很是簡單,沒有Cookie傳遞給服務器,服務器每次建立的時候都是新的Session,致使最後獲取到的List集合必定是空的。
不一樣Servlet獲取到的Session的id號都是不一樣的。
下面咱們就對URL進行重寫,看看能不能恢復沒有禁掉Cookie以前的效果。
原則:把Session的屬性帶過去【傳遞給】另一個Servlet,都要URL地址重寫
在跳轉到顯示購買過商品的Servlet的時候,URL地址重寫。
String url = "/ouzicheng/Servlet7";
response.sendRedirect(response.encodeURL(url));
複製代碼
Java Web規範支持經過配置禁用Cookie
禁用本身項目的Cookie
<?xml version='1.0' encoding='utf-8'?>
<Context path="/ouzicheng" cookies="false">
</Context>
複製代碼
禁用所有web應用的Cookie
注意:該配置只是讓服務器不能自動維護名爲jsessionid的Cookie,並不能阻止Cookie的讀寫。
private String username = null;
private String password = null;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
....各類set、get方法
複製代碼
private static List<User> list = new ArrayList<>();
//裝載些數據進數據庫
static {
list.add(new User("aaa","111"));
list.add(new User("bbb","222"));
list.add(new User("ccc","333"));
}
//經過用戶名和密碼查找用戶
public static User find(String username, String password) {
for (User user : list) {
if (user.getUsername().equals(username) && user.getPassword().equals(password)) {
return user;
}
}
return null;
}
複製代碼
<form action="/ouzicheng/LoginServlet" method="post">
用戶名:<input type="text" name="username"><br/>
密碼:<input type="password" name="password"><br/>
<input type="submit" value="提交">
</form>
複製代碼
String username = request.getParameter("username");
String password = request.getParameter("password");
User user = UserDB.find(username, password);
//若是找不到,就是用戶名或密碼出錯了。
if (user == null) {
response.getWriter().write("you can't login");
return;
}
//標記着該用戶已經登錄了!
HttpSession httpSession = request.getSession();
httpSession.setAttribute("user", user);
//跳轉到其餘頁面,告訴用戶成功登錄了。
response.sendRedirect(response.encodeURL("index.jsp"));
複製代碼
重複提交的危害:
首先咱們來看一下常見的重複提交。
下面的gif是後退再提交,在處理提交請求的Servlet中刷新
下面的gif是網絡延遲,屢次點擊提交按鈕
對於網絡延遲形成的屢次提交數據給服務器,實際上是客戶端的問題。因而,咱們可使用javaScript來防止這種狀況
要作的事情也很是簡單:當用戶第一次點擊提交按鈕時,把數據提交給服務器。當用戶再次點擊提交按鈕時,就不把數據提交給服務器了。
監聽用戶提交事件。只能讓用戶提交一次表單!
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>表單提交</title>
<script type="text/javascript"> //定義一個全局標識量:是否已經提交過表單數據 var isCommitted = false; function doSubmit() { //false表示的是沒有提交過,因而就可讓表單提交給Servlet if(isCommitted==false) { isCommitted = true; return true; }else { return false; } } </script>
</head>
<body>
<form action="/ouzicheng/Servlet7" onsubmit="return doSubmit()">
用戶名:<input type="text" name="username">
<input type="submit" value="提交">
</form>
</body>
</html>
複製代碼
因爲網絡延遲形成的屢次提交數據給服務器,咱們還可使用javaScript代碼這樣解決:當我點擊過一次提交按鈕時,我就把提交的按鈕隱藏起來。不能讓用戶點擊了!
想要讓按鈕隱藏起來,也很簡單。只要獲取到按鈕的節點,就能夠控制按鈕的隱藏或顯示了!
<script type="text/javascript">
function doSubmit() {
var button = document.getElementById("button");
button.disabled = disabled;
return true;
}
</script>
複製代碼
在處理表單的Servlet中刷新和後退再提交這兩種方式不能只靠客戶端來限制了。也就是說javaScript代碼沒法阻止這兩種狀況的發生。
因而乎,咱們就想得用其餘辦法來阻止表單數據重複提交了。咱們如今學了Session,Session能夠用來標識一個用戶是否登錄了。Session的原理也說了:不一樣的用戶瀏覽器會擁有不一樣的Session。而request和ServletContext爲何就不行呢?request的域對象只能是一次http請求,提交表單數據的時候request域對象的數據取不出來。ServletContext表明整個web應用,若是有幾個用戶瀏覽器同時訪問,ServletContext域對象的數據會被屢次覆蓋掉,也就是說域對象的數據就毫無心義了。
可能到這裏,咱們會想到:在提交數據的時候,存進Session域對象的數據,在處理提交數據的Servlet中判斷Session域對象數據????。究竟判斷Session什麼?判斷Session域對象的數據不爲null?沒用呀,既然已經提交過來了,那確定不爲null。
此時,咱們就想到了,在表單中還有一個隱藏域,能夠經過隱藏域把數據交給服務器。
咱們向Session域對象的存入數據到底是什麼呢?簡單的一個數字?好像也行啊。由於只要Session域對象的數據和jsp隱藏域帶過去的數據對得上號就好了呀,反正在Servlet上判斷完是否重複提交,會立馬把Session的數據移除掉的。更專業的作法是:向Session域對象存入的數據是一個隨機數【Token--令牌】。
生成一個獨一無二的隨機數
/* * 產生隨機數就應該用一個對象來生成,這樣能夠避免隨機數的重複。 * 因此設計成單例 * */
public class TokenProcessor {
private TokenProcessor() {
}
private final static TokenProcessor TOKEN_PROCESSOR = new TokenProcessor();
public static TokenProcessor getInstance() {
return TOKEN_PROCESSOR;
}
public static String makeToken() {
//這個隨機生成出來的Token的長度是不肯定的
String token = String.valueOf(System.currentTimeMillis() + new Random().nextInt(99999999));
try {
//咱們想要隨機數的長度一致,就要獲取到數據指紋
MessageDigest messageDigest = MessageDigest.getInstance("md5");
byte[] md5 = messageDigest.digest(token.getBytes());
//若是咱們直接 return new String(md5)出去,獲得的隨機數會亂碼。
//由於隨機數是任意的01010101010,在轉換成字符串的時候,會查gb2312的碼錶,gb2312碼錶不必定支持該二進制數據,獲得的就是亂碼
//因而乎通過base64編碼成了明文的數據
BASE64Encoder base64Encoder = new BASE64Encoder();
return base64Encoder.encode(md5);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return null;
}
}
複製代碼
//生出隨機數
TokenProcessor tokenProcessor = TokenProcessor.getInstance();
String token = tokenProcessor.makeToken();
//將隨機數存進Session中
request.getSession().setAttribute("token", token);
//跳轉到顯示頁面
request.getRequestDispatcher("/login.jsp").forward(request, response);
複製代碼
<form action="/ouzicheng/Servlet7" >
用戶名:<input type="text" name="username">
<input type="submit" value="提交" id="button">
<%--使用EL表達式取出session中的Token--%>
<input type="hidden" name="token" value="${token}" >
</form>
複製代碼
String serverValue = (String) request.getSession().getAttribute("token");
String clientValue = request.getParameter("token");
if (serverValue != null && clientValue != null && serverValue.equals(clientValue)) {
System.out.println("處理請求");
//清除Session域對象數據
request.getSession().removeAttribute("token");
}else {
System.out.println("請不要重複提交數據!");
}
複製代碼
實現原理是很是簡單的:
一次性校驗碼其實就是爲了防止暴力猜想密碼
在講response對象的時候,咱們使用response對象輸出過驗證碼,可是沒有去驗證!
驗證的原理也很是簡單:生成驗證碼後,把驗證碼的數據存進Session域對象中,判斷用戶輸入驗證碼是否和Session域對象的數據一致。
生成驗證碼圖片,並將驗證碼存進Session域中
//在內存中生成圖片
BufferedImage bufferedImage = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
//獲取到這張圖片
Graphics2D graphics2D = (Graphics2D) bufferedImage.getGraphics();
//設置背景色爲白色
graphics2D.setColor(Color.white);
graphics2D.fillRect(0, 0, 80, 20);
//設置圖片的字體和顏色
graphics2D.setFont(new Font(null, Font.BOLD, 20));
graphics2D.setColor(Color.BLUE);
//生成隨機數
String randomNum = makeNum();
//往這張圖片上寫數據,橫座標是0,縱座標是20
graphics2D.drawString(randomNum, 0, 20);
//將隨機數存進Session域中
request.getSession().setAttribute("randomNum", randomNum);
//控制瀏覽器不緩存該圖片
response.setHeader("Expires", "-1");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
//通知瀏覽器以圖片的方式打開
response.setHeader("Content-type", "image/jpeg");
//把圖片寫給瀏覽器
ImageIO.write(bufferedImage, "jpg", response.getOutputStream());
複製代碼
private String makeNum() {
Random random = new Random();
//生成0-6位的隨機數
int num = random.nextInt(999999);
//驗證碼的數位全都要6位數,因而將該隨機數轉換成字符串,不夠位數就添加
String randomNum = String.valueOf(num);
//使用StringBuffer來拼湊字符串
StringBuffer stringBuffer = new StringBuffer();
for (int i = 0; i < 6 - randomNum.length(); i++) {
stringBuffer.append("0");
}
return stringBuffer.append(randomNum).toString();
}
複製代碼
<form action="/ouzicheng/Login2Servlet">
用戶名:<input type="text" name="username"><br>
密碼:<input type="password" name="password"><br>
驗證碼:<input type="text" name="randomNum">
<img src="/ouzicheng/ImageServlet" ><br><br>
<input type="submit" value="提交">
</form>
複製代碼
//獲取用戶輸入驗證碼的數據
String client_randomNum = request.getParameter("randomNum");
//獲取Session中的數據
String session_randomNum = (String) request.getSession().getAttribute("randomNum");
//判斷他倆數據是否相等,用戶是否有輸入驗證碼,Session中是否爲空
if (client_randomNum == null || session_randomNum == null || !client_randomNum.equals(session_randomNum)) {
System.out.println("驗證碼錯誤了!!!");
return ;
}
//下面就是驗證用戶名和密碼...................
複製代碼
對於校驗碼實現思路是這樣子的:
從存儲方式上比較
從隱私安全上比較
從有效期上比較
從對服務器的負擔比較
從瀏覽器的支持上比較
從跨域名上比較
若是僅僅使用Cookie或僅僅使用Session可能達不到理想的效果。這時應該嘗試一下同時使用Session和Cookie
那麼,何時才須要同時使用Cookie和Session呢?
在上一篇博客中,咱們使用了Session來進行簡單的購物,功能也的確實現了。如今有一個問題:我在購物的途中,不當心關閉了瀏覽器。當我再返回進去瀏覽器的時候,發現我購買過的商品記錄都沒了!!爲何會沒了呢?緣由也很是簡單:服務器爲Session自動維護的Cookie的maxAge屬性默認是-1的,當瀏覽器關閉掉了,該Cookie就自動消亡了。當用戶再次訪問的時候,已經不是原來的Cookie了。
咱們如今想的是:即便我不當心關閉了瀏覽器了,我從新進去網站,我還能找到個人購買記錄。
要實現該功能也十分簡單,問題其實就在:服務器爲Session自動維護的Cookie的maxAge屬性是-1,Cookie沒有保存在硬盤中。我如今要作的就是:把Cookie保存在硬盤中,即便我關閉了瀏覽器,瀏覽器再次訪問頁面的時候,能夠帶上Cookie,從而服務器識別出Session。
**第一種方式:**只須要在處理購買頁面上建立Cookie,Cookie的值是Session的id返回給瀏覽器便可
Cookie cookie = new Cookie("JSESSIONID",session.getId());
cookie.setMaxAge(30*60);
cookie.setPath("/ouzicheng/");
response.addCookie(cookie);
複製代碼
第二種方式: 在server.xml文件中配置,將每一個用戶的Session在服務器關閉的時候序列化到硬盤或數據庫上保存。但此方法不經常使用,知道便可!
下面看一下效果
若是文章有錯的地方歡迎指正,你們互相交流。習慣在微信看技術文章的同窗,能夠關注微信公衆號:Java3y