1. 程式人生 > >會話管理-cookie,session,禁用cookie的URL重寫,token的單態設計模式,異常丟擲,UUID,MD5,base64編碼

會話管理-cookie,session,禁用cookie的URL重寫,token的單態設計模式,異常丟擲,UUID,MD5,base64編碼

1、會話

會話可簡單理解為:使用者開一個瀏覽器,點選多個超連結,訪問伺服器多個web資源,然後關閉瀏覽器,整個過程稱之為一個會話。
  • 會話過程中要解決的一些問題?
每個使用者在使用瀏覽器與伺服器進行會話的過程中,不可避免各自會產生一些資料,程式要想辦法為每個使用者儲存這些資料。
例如:使用者點選超連結通過一個servlet購買了一個商品,程式應該想辦法儲存使用者購買的商品,以便於使用者點結帳servlet時,結帳servlet可以得到使用者購買的商品為使用者結帳。
不能使用request或servletcontext來進行儲存

儲存會話資料的兩種技術:

  • Cookie
Cookie是客戶端技術
,程式把每個使用者的資料以cookie的形式寫給使用者各自的瀏覽器。當用戶使用瀏覽器再去訪問伺服器中的web資源時,就會帶著各自的資料去。這樣,web資源處理的就是使用者各自的資料了。


  • Session
Session是伺服器端技術,利用這個技術,伺服器在執行時可以為每一個使用者的瀏覽器建立一個其獨享的session物件,由於session為使用者瀏覽器獨享,所以使用者在訪問伺服器的web資源時,可以把各自的資料放在各自的session中,當用戶再去訪問伺服器中的其它web資源時,其它web資源再從使用者各自的session中取出資料為使用者服務。

2、cookie API

javax.servlet.http.Cookie類用於建立一個Cookie,response介面也中定義了一個addCookie方法,它用於在其響應頭中增加一個相應的Set-Cookie頭欄位。 同樣,request介面中也定義了一個getCookies方法,它用於獲取客戶端提交的Cookie。Cookie類的方法: 
  • public Cookie(String name,String value)
  • setValue與getValue方法 
  • setMaxAge與getMaxAge方法 :沒有設定的話,關閉瀏覽器cookie就失效了,存於快取。設定了寫入硬碟
  • setPath與getPath方法   /day06:訪問day06的時候帶cookie
  • setDomain與getDomain方法:域方法:https://www.taobao.com/這是主機名  .taobao.com是域名!
  • getName方法 :cookie名稱
示例: 顯示使用者上次訪問時間:
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	response.setContentType("text/html;charset=UTF-8");
	PrintWriter out = response.getWriter();
	out.write("這是網站首頁!<br>");
	out.write("您上次訪問時間是:");
	//得到上次訪問時間
	Cookie[] cookies = request.getCookies();
	if (cookies == null) {
		out.write("初次訪問");
	} else {
		for (int i = 0; i < cookies.length; i++) {
			if(cookies[i].getName().equals("LastAccessTime")){
				Long time = Long.parseLong(cookies[i].getValue());
				Date date = new Date(time);
				out.write(date.toLocaleString());
			}
		}
	}

	//將訪問時間寫入cookie中
	Cookie cookie = new Cookie("LastAccessTime",System.currentTimeMillis()+"");
	cookie.setMaxAge(3600);
	response.addCookie(cookie);
}

  • 一個Cookie只能標識一種資訊,它至少含有一個標識該資訊的名稱(NAME)和設定值(VALUE)。 
  • 一個WEB站點可以給一個WEB瀏覽器傳送多個Cookie,一個WEB瀏覽器也可以儲存多個WEB站點提供的Cookie。
  • 瀏覽器一般只允許存放300個Cookie,每個站點最多存放20個Cookie,每個Cookie的大小限制為4KB。
  • 如果建立了一個cookie,並將他傳送到瀏覽器,預設情況下它是一個會話級別的cookie(即儲存在瀏覽器的記憶體中),使用者退出瀏覽器之後即被刪除。若希望瀏覽器將該cookie儲存在磁碟上,則需要使用maxAge,並給出一個以秒為單位的時間。將最大時效設為0則是命令瀏覽器刪除該cookie。
  • 注意,刪除cookie時,path必須一致,否則不會刪除
刪除cookie:
Cookie cookie = new Cookie("LastAccessTime",System.currentTimeMillis()+"");
cookie.setMaxAge(0);
response.addCookie(cookie);
必須設定 地址一致

容器:兩種單列和雙列:collection和map。 有檢索資料的需求就用雙列,沒有就用單列。

例項:最近瀏覽過的商品


cookie3:
//首頁
public class CookieDemo3 extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		
		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();

		//1.顯示網站所有商品
		out.write("本網站有如下書籍:<br><br>");
		Set<Map.Entry<String, Book>> set = DB.getAll().entrySet();
		for(Map.Entry<String, Book> me : set){
			Book book = me.getValue();
			out.write("<a href='/day07/servlet/CookieDemo4?id="+book.getId()+"' target='_blank'>"+book.getName()+"</a>");
			out.write("<br/>");
		}		
		
		//2.顯示使用者曾經瀏覽過的商品
		out.write("<br/><br/>您曾經瀏覽過的商品:<br/>" );
		Cookie cookies[] = request.getCookies();
		for(int i=0;cookies!=null && i<cookies.length;i++){
			Cookie cookie = cookies[i];
			if(cookie.getName().equals("bookHistory")){
				String bookHistory = cookie.getValue();  //  2_3
				String ids[] = bookHistory.split("\\_");  //[2,3]
				for(String id: ids){
					Book book = (Book) DB.getAll().get(id);
					out.write(book.getName() + "<br/>");
				}
			}
		}
		
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		doGet(request, response);
	}
}

class DB
{
	private static Map<String,Book> map = new LinkedHashMap();
	static{
		map.put("1", new Book("1","javaweb開發","老張","一本好書"));
		map.put("2", new Book("2","spring開發","老黎","一本好書"));
		map.put("3", new Book("3","hibernate開發","老佟","一本好書"));
		map.put("4", new Book("4","struts開發","老畢","一本好書"));
		map.put("5", new Book("5","ajax開發","老張","一本好書"));
	}
	
	public static Map getAll(){
		return map;
	}
}


class Book{
	
	private String id;
	private String name;
	private String author;
	private String description;	
	
	public Book() {
		super();
		// TODO Auto-generated constructor stub
	}
	public Book(String id, String name, String author, String description) {
		super();
		this.id = id;
		this.name = name;
		this.author = author;
		this.description = description;
	}
	public String getId() {
		return id;
	}
	public void setId(String id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getAuthor() {
		return author;
	}
	public void setAuthor(String author) {
		this.author = author;
	}
	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}	
}
cookie4:
ublic class CookieDemo4 extends HttpServlet {

	public void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		response.setContentType("text/html;charset=UTF-8");
		PrintWriter out = response.getWriter();
		
		//1.根據使用者帶過來的id號,顯示商品的詳細資訊
		String id = request.getParameter("id");
		Book book = (Book) DB.getAll().get(id);
		
		out.write("您要檢視的書的詳細資訊如下:<br/><br/>");
		out.write(book.getId() + "<br/>");	
		out.write(book.getName() + "<br/>");
		out.write(book.getAuthor() + "<br/>");
		out.write(book.getDescription() + "<br/>");		
		
		//2.給使用者回送包含當前商品id的cookie
		String bookHistory = makeHistory(request,id);    //  3_2
		Cookie cookie = new Cookie("bookHistory",bookHistory);
		cookie.setMaxAge(1*30*24*3600);
		
		response.addCookie(cookie);		
	}

	private String makeHistory(HttpServletRequest request, String id) {
		
		String bookHistory = null;
		Cookie cookies[] = request.getCookies();
		for(int i=0;cookies!=null&&i<cookies.length;i++){
			if(cookies[i].getName().equals("bookHistory")){
				bookHistory = cookies[i].getValue();
			}
		}
		
		//  bookHistory=null      1    bookHistory=1
		//  bookHistory=3_1_5     1    bookHistory=1_3_5
		//  bookHistory=3_2_5     1    bookHistory=1_3_2
		//  bookHistory=3_2       1    bookHistory=1_3_2
		
		
		//  bookHistory=null      1    bookHistory=1
		if(bookHistory==null){
			return id;
		}
		
		List l = Arrays.asList(bookHistory.split("\\_"));   //[3,4]  //陣列  連結
		LinkedList<String> list = new  LinkedList();
		list.addAll(l);
		
		if(list.contains(id)){
			//  bookHistory=3_1_5     1    bookHistory=1_3_5
			list.remove(id);
			list.addFirst(id);
		}else{
			if(list.size()>=3){
				//  bookHistory=3_2_5     1    bookHistory=1_3_2
				list.removeLast();
				list.addFirst(id);
			}else{
				//  bookHistory=3_2       1    bookHistory=1_3_2
				list.addFirst(id);
			}
		}
		
		StringBuffer sb = new StringBuffer();  //2_3_4
		for(String lid: list){
			sb.append(lid + "_");
		}
		
		return sb.deleteCharAt(sb.length()-1).toString();
	}

	public void doPost(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {

		doGet(request, response);
	}
}

3、session

在WEB開發中,伺服器可以為每個使用者瀏覽器建立一個會話物件(session物件),注意:一個瀏覽器獨佔一個session物件(預設情況下)。因此,在需要儲存使用者資料時,伺服器程式可以把使用者資料寫到使用者瀏覽器獨佔的session中,當用戶使用瀏覽器訪問其它程式時,其它程式可以從使用者的session中取出該使用者的資料,為使用者服務。
Session和Cookie的主要區別在於:
  • Cookie是把使用者的資料寫給使用者的瀏覽器。存於使用者,客戶端技術
  • Session技術把使用者的資料寫到使用者獨佔的session中。存於伺服器,伺服器技術
Session物件由伺服器建立,開發人員可以呼叫request物件的getSession方法得到session物件。

購物車例項:

書類如上: index:
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();

request.getSession();

out.write("本網站有如下書:<br/>");

Set<Map.Entry<String,Book>> set = DB.getAll().entrySet();
for(Map.Entry<String,Book> me : set){
	Book book = me.getValue();
	String url = "/day07/servlet/BuyServlet?id=" + book.getId();
	url = response.encodeURL(url);
	out.println(book.getName()  + "   <a href='"+url+"'>購買</a><br/>");
}		
buy:
String id = request.getParameter("id");
Book book = (Book) DB.getAll().get(id);  //得到使用者想買的書

HttpSession session = request.getSession();
/*Cookie cookie = new Cookie("JSESSIONID",session.getId());
cookie.setMaxAge(30*60);
cookie.setPath("/day07");
response.addCookie(cookie);*/


List list = (List) session.getAttribute("list");  //得到使用者用於儲存所有書的容器
if(list==null){
	list = new ArrayList();
	session.setAttribute("list", list);
}
list.add(book);


//request.getRequestDispatcher("/servlet/ListCartServlet").forward(request, response);

String url = response.encodeRedirectURL("/day07/servlet/ListCartServlet");
System.out.println(url);
response.sendRedirect(url);
ListCartServlet:
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();

HttpSession session = request.getSession();
List<Book> list = (List) session.getAttribute("list");
if(list==null || list.size()==0){
	out.write("對不起,您還沒有購買任何商品!!");
	return;
}

//顯示使用者買過的商品
out.write("您買過如下商品:<br>");
for(Book book : list){
	out.write(book.getName() + "<br/>");
}

伺服器是如何實現一個session為一個使用者瀏覽器服務的?
瀏覽器第一次getsession的時候,伺服器建立一個session物件,伺服器會給每個session一個固定的ID號碼,通過cookie的形式儲存到使用者硬碟上,由於沒有設定有效時間,因此,當關閉瀏覽器這個cookie就失效了,那麼這個session就結束了。
因此要實現關閉了瀏覽器,session物件還在,那麼就需要對儲存session的ID號碼的cookie物件設定有效時間(由於畢竟是session物件,伺服器預設該session如果30分鐘內沒人使用自動銷燬,因此自己設定的cookie有效期不應該30分鐘)
String id = session.getId();
Cookie cookie = new Cookie("JSESSIONID",id);
cookie.setMaxAge(1800);//不應該超過30分鐘
cookie.setPath("/");

response.addCookie(cookie);
cookie.setPath("/");
應用於所有工程下的servlet!
禁用Cookie後servlet共享資料導致的問題:URL重寫
利用URL攜帶session的ID,網站上的所有URL地址都要重寫
response. encodeRedirectURL(java.lang.String url) 
用於對sendRedirect方法後的url地址進行重寫。
response. encodeURL(java.lang.String url)
用於對錶單action和超連結的url地址進行重寫
index:
response.setContentType("text/html;charset=UTF-8");

PrintWriter out = response.getWriter();

request.getSession();

out.write("本網站有如下圖書:<br>");
Set<Map.Entry<String,Book>> set = DB.getAll().entrySet();
for (Map.Entry<String,Book> me:set){
	Book book = me.getValue();
	String url = response.encodeRedirectURL("/Buy?id=" + book.getID());
	out.write(book.getName()+"   <a href='"+ url +"'>購買</a><br>");
}
buy:
String url = response.encodeRedirectURL("/ListCart");
response.sendRedirect(url);
禁用cookie的話,就不能實現關閉瀏覽器資料還在


1、伺服器是如何做到一個session為一個瀏覽器的多次請求而服務
1.1  伺服器建立session出來後,會把 session的id號,以cookie的形式回寫給客戶機,這樣,只要客戶機的瀏覽器不關,再
去訪問伺服器時,都會帶著session 的id號去,伺服器發現客戶機帶session id過來了,就會使用記憶體中與之對應的
session為之服務

2、如何做到一個session為多個瀏覽器服務
2.1  伺服器第一次建立session,程式設計師把session id號,手工以cookie的形式回送給瀏覽器,並設定cookie的有效期 這樣,即使使用者的瀏覽器關了,開新瀏覽器時,還會帶著session id找伺服器,伺服器從而就可以用記憶體中與之對應的 session為第二個瀏覽器視窗服務


3、如何做使用者禁用cookie後,session還能為多次請求而服務
3.1  把使用者可能點的每一個超連結後面,都跟上使用者的session id號


4、session物件的建立和銷燬時機
4.1 使用者第一次request.getSession時 4.2 session物件預設30分鐘沒有使用,則伺服器會自動銷燬session,
4.2.1  使用者在web.xml檔案中手工配置session的失效時間:session-config     timeout
4.2.2  使用者可以手工呼叫session.invalidate方法,摧毀session
購物網站可以用cookie也可以用session,cookie可以減輕伺服器壓力。

4、session案例

(1)、使用Session完成使用者登入

index.jsp
<body>
歡迎您:<br>

<%
User user = (User) session.getAttribute("user");
if (user != null)
  out.write(user.getUsername());
%>

<a href="login.jsp">登入</a>
</body>
Login(servlet)
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();

String username = request.getParameter("username");
String password = request.getParameter("password");

User user = DB.find(username, password);

if(user == null){
	out.write("使用者名稱或密碼錯誤");
	return;
}

request.getSession().setAttribute("user",user);
response.sendRedirect("/index.jsp");
login.jsp
<body>

  <form action="/Login" method="post">
    使用者名稱:<input type="text" name="username"><br>
    密碼:<input type="password" name="password"><br>
    <input type="submit" value="登入">
  </form>

</body>


(2)、防止表單重複提交

情況:a、延時導致使用者多次提交            b、提交後多次重新整理    c、後退提交後退提交 需要前臺與後臺的共同努力: 前臺:頁面利用JavaScript程式碼進行:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>

<script type="text/javascript">
    function dosubmit(){
        var submit = document.getElementById("submit");
        submit.disabled="disabled";
        return true;
    }
</script>


<body>
    <form action="FormTest" method="post" onsubmit="return dosubmit()">
        <input type="text" name="username"><br>
        <input type="submit" value="提交" id="submit">
    </form>
</body>
</html>

釋出表單的同時釋出一個隨機數,如果隨機數一致了,錄入資訊,並將session中的隨機數清除,再提交的時候由於隨機數銷燬了,就不會一致了防止了表單的重複提交。 隨機數的產生應該使用單態設計模式:

單態設計模式:一個類只生成一個實體物件

*1、把類的建構函式私有
*2、自己建立一個類的物件
*3、對外提供一個公共的方法,返回類的物件
class TokenProcess{
//單態模式,原子模式,單例模式
/*
 *單態設計模式(保證類的物件在記憶體中只有一個)
 *1、把類的建構函式私有
 *2、自己建立一個類的物件
 *3、對外提供一個公共的方法,返回類的物件
 *
 */
private TokenProcess(){}

private static final TokenProcess instance = new TokenProcess();

public static TokenProcess getInstance(){
	return instance;
}

//形成隨機數

public String makeToken(){

	String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";

	//資料指紋   128位長   16個位元組  md5

	try {
		MessageDigest md5 = MessageDigest.getInstance("md5");
		byte[] digest = md5.digest(token.getBytes());

		BASE64Encoder encoder = new BASE64Encoder();
		return encoder.encode(digest);
	} catch (NoSuchAlgorithmException e) {
		throw new RuntimeException(e);
	}

}


ServletContext:其他人來了共用了 request:請求都變了  session:√ 不用UUID:通用唯一識別碼 (Universally Unique Identifier) //base64編碼--任意二進位制編碼明文字元   adfsdfsdfsf
3個位元組變成4個位元組,3個位元組24位,4個位元組各取6位,自後向前,取完加兩個0,形成一個位元組 BASE64Encoder 一般都是向上拋執行異常:throw new RuntimeException(e);但是如果是想用異常當做一種返回值處理,需要人家處理,則應該拋編譯異常。 //資料指紋   128位長   16個位元組  md5演算法  不可逆演算法  MessageDigest
try {
	MessageDigest md5 = MessageDigest.getInstance("md5");
	byte[] digest = md5.digest(token.getBytes());

	BASE64Encoder encoder = new BASE64Encoder();
	return encoder.encode(digest);
} catch (NoSuchAlgorithmException e) {
	throw new RuntimeException(e);
}

(3)、利用Session實現一次性驗證碼