一、會話
1.1什么是會話
會話可以理解為:用戶開啟一個(gè)瀏覽器蝇完,點(diǎn)擊多個(gè)超鏈接,訪問服務(wù)器多個(gè)web資源矗蕊,然后關(guān)閉瀏覽器短蜕,整個(gè)過程稱之為一個(gè)會話。(瀏覽器關(guān)閉了傻咖,一個(gè)會話也就結(jié)束了)
1.2會話過程中要解決的一些問題
每個(gè)用戶在使用瀏覽器與服務(wù)器進(jìn)行會話的過程中朋魔,不可避免各自會產(chǎn)生一些數(shù)據(jù),程序要想辦法為每個(gè)用戶保存這些數(shù)據(jù)卿操。之前我們講了兩種方式保存數(shù)據(jù)警检。一種是將數(shù)據(jù)保存在request對象中,但是我們知道用戶沒訪問一次servlet就會創(chuàng)建一個(gè)request和response對象害淤,這樣在如網(wǎng)上購物這樣的情景下是不能夠使用的扇雕。還有另一種方式是使用servletContext,但是此容器是針對整個(gè)web應(yīng)用的窥摄,如果多個(gè)用戶訪問同一個(gè)web應(yīng)用镶奉,那么顯然會造成數(shù)據(jù)混亂。
1.3保存會話數(shù)據(jù)的兩種技術(shù)
1.3.1 Cookie
Cookie是客戶端技術(shù),程序把每個(gè)用戶的數(shù)據(jù)以Cookie的形式寫給用戶各自的瀏覽器哨苛。當(dāng)用戶使用瀏覽器再去訪問服務(wù)器中的web資源時(shí)鸽凶,就會帶著各自的數(shù)據(jù)去。這樣建峭,web資源處理的就是用戶各自的數(shù)據(jù)了玻侥。
1.3.2 Session
Session是服務(wù)器端技術(shù),利用這個(gè)技術(shù)亿蒸,服務(wù)器在運(yùn)行時(shí)可以為每一個(gè)用戶的瀏覽器創(chuàng)建一個(gè)其獨(dú)享的session對象凑兰,由于session為用戶瀏覽器獨(dú)享,所以用戶在訪問服務(wù)器的web資源時(shí)祝懂,可以把各自的數(shù)據(jù)放在各自的session中票摇,當(dāng)用戶再去訪問服務(wù)器中的其它web資源時(shí),其它web資源再從用戶各自的session中取出數(shù)據(jù)為用戶服務(wù)砚蓬。
二矢门、Cookie的應(yīng)用(工程cookie)
2.1顯示用戶上次訪問時(shí)間
package cn.itcast.service;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Date;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class CookieDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write("這是網(wǎng)站首頁!<br>");
out.write("上次訪問的時(shí)間: ");
//從request域中獲得Cookie
Cookie[] cookies = request.getCookies();
//注意for循環(huán)中的兩個(gè)條件不能互換位置灰蛙,必須先判斷是否為空
for(int i = 0; cookies != null && i < cookies.length; i++){
Cookie cookie = cookies[i];
if(cookie.getName().equals("lastAccessTime")){
Long time = Long.parseLong(cookie.getValue());
Date date = new Date(time);
out.write(date.toLocaleString());
}
}
//我們在Cookie中設(shè)置一個(gè)鍵值對lastAccessTime=時(shí)間值祟剔。
Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis() + "");
//設(shè)置Cookie保存的最長時(shí)間,單位是s
cookie.setMaxAge(10000);
//設(shè)置Cookie的路徑摩梧,即本工程的路徑
cookie.setPath("/cookie");
response.addCookie(cookie);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
說明:從程序中可以看到Cookie中就是一些鍵值對物延,我們可以自己進(jìn)行設(shè)定相關(guān)的信息。
2.2 Cookie的細(xì)節(jié)
(1)一個(gè)Cookie只能標(biāo)識一種信息仅父,它至少含有一個(gè)標(biāo)識該信息的名稱(NAME)和設(shè)置值(VALUE)叛薯。
(2)一個(gè)WEB站點(diǎn)可以給一個(gè)web瀏覽器發(fā)送多個(gè)Cookie,一個(gè)web瀏覽器也可以存儲多個(gè)web站點(diǎn)提供多個(gè)Cookie笙纤。
(3)瀏覽器一般只允許存放300個(gè)Cookie耗溜,每個(gè)站點(diǎn)最多存放20個(gè)Cookie,每個(gè)Cookie的大小限制為4kb省容。
(4)如果創(chuàng)建了一個(gè)cookie抖拴,并將他發(fā)送到瀏覽器,默認(rèn)情況下它是一個(gè)會話級別的cookie(即存儲在瀏覽器的內(nèi)存中)腥椒,用戶退出瀏覽器之后即被刪除阿宅。若希望瀏覽器將該Cookie存儲在磁盤上,則需要使用maxAge笼蛛,并給出一個(gè)以秒為單位的時(shí)間洒放。將最大時(shí)效設(shè)為0則是命令瀏覽器刪除該Cookie。
(5)注意:刪除Cookie時(shí)滨砍,path路徑必須一致拉馋,否則不會刪除榨为。同時(shí)現(xiàn)在的瀏覽器一般都自帶開發(fā)人員工具,可以進(jìn)行抓包煌茴。
2.3案例:顯示用戶上次瀏覽過的商品
(1)顯示所有書籍和用戶瀏覽過的書籍
package cn.itcast.service;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.CookieStore;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//顯示所有書籍,同時(shí)顯示瀏覽過的書籍
public class CookieDemo2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write("本網(wǎng)站有如下商品: <br><br>");
//得到所有的書籍實(shí)體
Set<Map.Entry<String, Book>> set = DB.getAll().entrySet();
for(Map.Entry<String, Book> me : set){
Book book = me.getValue();
out.write("<a href='/cookie/servlet/CookieDemo3?id="+book.getId()+"'target='_blank'>"+book.getName()+"</a>");
out.write("<br>");
}
out.write("<br><br>您曾經(jīng)瀏覽過的商品:<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();
//以下劃線分割日川,"\\_"這樣表示的目的是不管下劃線在正則表達(dá)式中有沒有定義都可以確保以下劃線分割
//獲得所有的id號蔓腐,然后進(jìn)行顯示
String ids[] = bookHistory.split("\\_");
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", "java", "張三", "一本好書"));
map.put("2", new Book("2", "web", "李四", "一本好書"));
map.put("3", new Book("3", "spring", "王五", "一本好書"));
map.put("4", new Book("4", "hibernate", "趙六", "一本好書"));
map.put("5", new Book("5", "struts1", "tom", "一本好書"));
}
public static Map getAll(){
return map;
}
}
class Book{
private String id;
private String name;
private String author;
private String discription;
public Book(){
}
public Book(String id, String name, String author, String discription) {
this.id = id;
this.name = name;
this.author = author;
this.discription = discription;
}
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 getDiscription() {
return discription;
}
public void setDiscription(String discription) {
this.discription = discription;
}
}
(2)顯示用戶選中的書籍詳細(xì)信息并產(chǎn)生和處理cookie
package cn.itcast.service;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//顯示用戶選中書籍的詳細(xì)信息,并產(chǎn)生cookie發(fā)給瀏覽器
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.根據(jù)用戶帶過來的id查詢出商品的詳細(xì)信息
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.getDiscription() + "<br>");
//得到瀏覽過所有的書籍信息
String bookHistory = makeHistory(request, id);
//new一個(gè)cookie
Cookie cookie = new Cookie("bookHistory", bookHistory);
cookie.setMaxAge(1*30*24*3600);
//向?yàn)g覽器中發(fā)送一個(gè)cookie
response.addCookie(cookie);
}
private String makeHistory(HttpServletRequest request, String id){
String bookHistory = null;
Cookie cookies[] = request.getCookies();
//從cookie中得到相關(guān)瀏覽過的書籍信息
for(int i = 0; cookies != null && i < cookies.length; i++){
if(cookies[i].getName().equals("bookHistory")){
bookHistory = cookies[i].getValue();
}
}
//下面是從瀏覽器中傳過來的cookie的四種情況龄句,這里我們假設(shè)以前瀏覽過的商品最多只能有三個(gè)回论,多的
//必須去掉
/*
* 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
if(bookHistory == null){
return id;
}
List li = Arrays.asList(bookHistory.split("\\_"));
LinkedList<String> list = new LinkedList<String>();
list.addAll(li);
//bookHistory = 3_1_5如果包含此id,那么先將其刪除分歇,然后將其添加到最前面
if(list.contains(id)){
list.remove(id);
list.addFirst(id);
}else {
//bookHistory = 3_2_5
//如果長度大于等于3傀蓉,那么直接將最后一個(gè)刪掉,將此id插入到最前面
if(list.size() >= 3){
list.removeLast();
list.addFirst(id);
}else {
//bookHistory = 3_2直接插入到最前面
list.addFirst(id);
}
}
//然后將id組合成一個(gè)字符串
StringBuffer sb = new StringBuffer();
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);
}
}
三职抡、session的應(yīng)用(工程session)
3.1在web開發(fā)中
服務(wù)器可以為每個(gè)用戶瀏覽器創(chuàng)建一個(gè)會話對象(Session對象)葬燎,注意:一個(gè)瀏覽器獨(dú)占一個(gè)session對象(默認(rèn)情況下)。因此缚甩,在需要保存用戶數(shù)據(jù)時(shí)谱净,服務(wù)器程序可以把用戶數(shù)據(jù)寫到用戶瀏覽器獨(dú)占的session中,當(dāng)用戶使用瀏覽器訪問其他程序時(shí)擅威,其他程序可以從用戶的session中取出該用戶的數(shù)據(jù)壕探,為用戶服務(wù)。
3.2 Session和Cookie的主要區(qū)別在于
Cookie把數(shù)據(jù)寫到用戶的瀏覽器中郊丛。Session技術(shù)是把用戶的數(shù)據(jù)寫到用戶獨(dú)占的session中李请,是在服務(wù)器中。
3.3 Session對象由服務(wù)器創(chuàng)建
開發(fā)人員可以調(diào)用request對象的getSession方法得到session對象厉熟。
package cn.itcast.service;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SessionDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
/*得到session對象导盅,服務(wù)器在用戶第一次訪問此web時(shí)若發(fā)現(xiàn)沒有相應(yīng)的session就會
* 創(chuàng)建一個(gè),若存在就不會創(chuàng)建*/
HttpSession session = request.getSession();
session.setAttribute("data", "abc");//寫入數(shù)據(jù)
System.out.println(session.getId());
/*注意:我們要知道之所以我們可以在多次訪問服務(wù)器時(shí)能共享session對象庆猫,是因?yàn)榉?wù)器會用將創(chuàng)建好的
* session對象的ID號以Cookie的形式返回給瀏覽器认轨,就相當(dāng)于返回給瀏覽器了一個(gè)名為JSESSIONID
* 的Cookie,但是瀏覽器默認(rèn)是在其關(guān)閉之后銷毀此Cookie月培,若想在下次訪問的時(shí)候還使用同一個(gè)session
* 則需要設(shè)置有效時(shí)長嘁字。如果沒有下面的設(shè)置有效時(shí)長,我們可以從http抓包中看到每次的ID號不同*/
String sessionid = session.getId();
Cookie cookie = new Cookie("JSESSIONID", sessionid);
cookie.setMaxAge(1*24*3600);
cookie.setPath("/session");
response.addCookie(cookie);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
下面我們讀取上面存入session的數(shù)據(jù)
package cn.itcast.service;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SessionDemo2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
HttpSession session = request.getSession();
System.out.println(session.getAttribute("data"));
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
3.4 案例:使用session完成簡單的購物功能
注意:下面程序中假設(shè)瀏覽器禁用Cookie杉畜,此時(shí)我們只能使用session纪蜒。
package cn.itcast.shopping;
//完成所有書籍的顯示,并提供書籍的購買鏈接
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class IndexServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
out.write("本網(wǎng)站有如下書籍:<br>");
HttpSession session = request.getSession();
//得到所有書籍實(shí)體
Set<Map.Entry<String, Book>> set = DB.getAll().entrySet();
for(Map.Entry<String, Book> me : set){
//從一個(gè)實(shí)體中取得書籍直接使用方法getValue
Book book = me.getValue();
//提供購買書籍的鏈接
String url = "/session/servlet/BuyServlet?id=" + book.getId() ;
//地址重寫
url = response.encodeURL(url);
out.println(book.getName() + "<a href='"+url+"'>購買</a><br>");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
class DB{
private static Map<String, Book> map = new LinkedHashMap<String, Book>();
static {
map.put("1", new Book("1", "java", "張三", "一本好書"));
map.put("2", new Book("2", "web", "李四", "一本好書"));
map.put("3", new Book("3", "spring", "王五", "一本好書"));
map.put("4", new Book("4", "hibernate", "趙六", "一本好書"));
map.put("5", new Book("5", "struts1", "tom", "一本好書"));
}
public static Map getAll(){
return map;
}
}
class Book{
private String id;
private String name;
private String author;
private String description;
public Book(){
}
public Book(String id, String name, String author, String description) {
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;
}
}
注意:程序中我們將瀏覽器的Cookie禁用之后若還想取得之前的session所采用的方式就是地址重寫此叠。但是這就必須在所有的servlet中都將URL地址重寫纯续,我們可以看到在BuyServlet.java中也將URL地址重寫了,若在BuyServlet.java中的URL地址不重寫也是可以購買的,但是買完之后跳到ListCartServlet.java中又會重新創(chuàng)建一個(gè)新的session猬错,也就是說BuyServlet.java和ListCartServlet.java沒有實(shí)現(xiàn)session共享窗看。將URL地址重寫之后就相當(dāng)于在每個(gè)超鏈接的地址后面加上了共享session的ID號。
package cn.itcast.shopping;
//處理用戶的購買工作倦炒,完成session的產(chǎn)生和更新
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class BuyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//得到用戶點(diǎn)擊書籍的id
String id = request.getParameter("id");
Book book = (Book) DB.getAll().get(id);
HttpSession session = request.getSession();
String sessionid = session.getId();
//有可能用戶瀏覽器的Cookie被設(shè)置為禁用
Cookie cookie = new Cookie("JSESSIONID", sessionid);
cookie.setMaxAge(1*3600);
cookie.setPath("/session");
response.addCookie(cookie);
//我們將書籍用戶購買的書籍放在一個(gè)list集合中
List list = (List) session.getAttribute("list");
if(list == null){
list = new ArrayList();
//在session中添加字段list
session.setAttribute("list", list);
}
//向session中添加書籍
list.add(book);
//地址重寫
String url = response.encodeRedirectURL("/session/servlet/ListCartServelt");
System.out.println(url);
response.sendRedirect(url);
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
package cn.itcast.shopping;
//完成購物車中書籍的顯示工作
import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class ListCartServelt extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
HttpSession session = request.getSession();
PrintWriter out = response.getWriter();
List<Book> list = (List) session.getAttribute("list");
if(list == null || list.size() == 0){
out.write("對不起显沈,您還沒有購煤任何商品.<br>");
return;
}
out.write("購物車中的商品如下:<br>");
for(Book book : list){
out.write(book.getName() + "<br>");
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
注意:
(1)如果我們一開始訪問,則會創(chuàng)建一個(gè)session逢唤,如果我點(diǎn)擊相關(guān)的連接拉讯,再返回刷新則可以取得之前的session。但是如果我們第一次訪問之后關(guān)掉瀏覽器鳖藕,再次訪問魔慷,則不會取到之前的session(不使用Cookie),因?yàn)殚_一個(gè)瀏覽器就相當(dāng)于開啟了一次會話著恩,如果關(guān)閉院尔,則表示此次會話結(jié)束了。同時(shí)页滚,如果我們同時(shí)開啟瀏覽器兩個(gè)窗口召边,也表示的是兩次會話,它們之間毫無關(guān)系裹驰。但是在試驗(yàn)的時(shí)候只有在IE8以下的瀏覽器能顯示出來隧熙,IE8和其他高版本的會看不到現(xiàn)象。還有幻林,如果我們在靜態(tài)頁面中寫了一些超鏈接贞盯,當(dāng)點(diǎn)擊超鏈接時(shí),也是屬于在一次會話中沪饺,所以還是使用之前創(chuàng)建好的session躏敢。
(2)我們要想讓瀏覽器在禁用Cookie之后下次訪問還能使用第一次創(chuàng)建的session對象是辦不到的。而只能在一次會話中共享session整葡。同時(shí)如果我們沒有禁用Cookie件余,那么即使我們將設(shè)置了URL地址重寫也是無效的,只有在禁用Cookie的條件下重寫URL地址才是有效的遭居。
(3)session的生命周期是request.getSession()開始到其銷毀為止啼器,一般瀏覽器默認(rèn)在30分鐘后銷毀,當(dāng)然我們也可以自己在其配置文件(web.xml)中配置:
<session-config>
<session-timeout>1</session-timeout>
</session-config>
上面的1表示一分鐘俱萍。此外端壳,我們還可以手工銷毀,調(diào)用session.invalidate銷毀session對象即可枪蘑。在開發(fā)中大多使用Cookie损谦。
(4) 重寫分為兩種岖免。其中response.encodeRedirectURL(java.lang.String url)是用于對sendRedirect方法后的url地址進(jìn)行重寫。而response.encodeURL(java.lang.String url)是用于對表單action和超鏈接的url地址進(jìn)行重寫照捡,上面的例子中有說明颅湘。
3.5 案例:使用session完成用戶登錄
登錄界面
<!DOCTYPE html>
<html>
<head>
<title>登錄界面</title>
<meta name="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<form action="/session/servlet/LoginServlet" method="post">
用戶名:<input type="text" name="username"><br>
密碼:<input type="password" name="password"><br>
<input type="submit" value="登錄">
</form>
</body>
</html>
用戶實(shí)體
package cn.itcast.login;
public class User {
private String username ;
private String password ;
public User(){}
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
后臺處理
package cn.itcast.login;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
System.out.println("test");
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("用戶名或密碼錯(cuò)誤");
return;
}
request.getSession().setAttribute("user", user);
response.sendRedirect("/session/index.jsp");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
class DB{
private static List<User> list = new ArrayList<User>();
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;
}
}
登錄成功后界面
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ page import="cn.itcast.login.User" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>My JSP 'index.jsp' starting page</title>
</head>
<body>
歡迎您:
<%
User user = (User)session.getAttribute("user");
if(user != null) out.write(user.getUsername());
%>
<br>
<a href="/session/login.html">重新登錄</a>
</body>
</html>
注意:此處我們只是簡單的使用jsp,后面再詳細(xì)講解栗精。
3.6防止表單重復(fù)提交
使用js防止表單重復(fù)提交
<!DOCTYPE html>
<html>
<head>
<title>form.html</title>
<meta name="content-type" content="text/html; charset=UTF-8">
<script type="text/javascript">
function dosubmit(){
var submit = document.getElementById("submit");
submit.disable = "disabled";
return true;
}
</script>
</head>
<body>
<form action="/session/servlet/FormServlet" method="post" onsubmit="return dosubmit()">
用戶名:<input type="text" name="username"><br>
<input type="submit" value="提交">
</form>
</body>
</html>
說明:上面我們使用JS在瀏覽器中進(jìn)行檢查栅炒,這種檢查是必須要有的,但是卻不夠术羔。在這種靜態(tài)頁面中,當(dāng)用戶刷新或單擊后退再次提交還是會導(dǎo)致重復(fù)提交乙漓。我們還需要在后臺進(jìn)行檢查级历。
后臺檢查時(shí),如果表單是由servlet程序生成(例子不太好叭披,僅做參考)寥殖,我們可以讓servlet為每次產(chǎn)生的表單分配一個(gè)唯一的隨機(jī)標(biāo)識號,并在form表單的一個(gè)隱藏字段中設(shè)置這個(gè)標(biāo)識號涩蜘,同時(shí)在當(dāng)前用戶的session中保存這個(gè)標(biāo)識號嚼贡。
當(dāng)用戶提交form表單時(shí),負(fù)責(zé)處理表單提交的servlet得到表單提交的標(biāo)識號同诫,并與session中的標(biāo)識號進(jìn)行比較粤策,如果相同則清除標(biāo)識號并處理請求,否則表示重復(fù)提交误窖。
下面程序中叮盘,服務(wù)器將拒絕用戶提交的表單請求:
(1)存儲在session中的標(biāo)識號和表單標(biāo)識號不同;
(2)當(dāng)前用戶的session中不存在標(biāo)識號霹俺;
(3)用戶提交的表單數(shù)據(jù)中沒有標(biāo)識號字段柔吼。
src下
package cn.itcast.form;
import java.io.IOException;
import java.io.PrintWriter;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Random;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import sun.misc.BASE64Encoder;
public class FormServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
PrintWriter out = response.getWriter();
//產(chǎn)生一個(gè)隨機(jī)數(shù),用于校驗(yàn)
String token = TokenProcessor.getInstance().makeToken();
//將隨機(jī)數(shù)保存在session中
request.getSession().setAttribute("token", token);
out.println("<form action='/session/servlet/DoFormServlet' method='pos'>");
//將隨機(jī)數(shù)同時(shí)也發(fā)送給表單的隱藏字段
out.write("<input type='hidden' name='token' value='"+token+"'>");
out.println("用戶名:<input type='text' name='username'>");
out.println("<input type='submit' value='提交'>");
out.println("</form>");
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
class TokenProcessor{
/*
* 這里用到了單態(tài)設(shè)計(jì)模式(即保證類的對象在內(nèi)存中只有一份)
* 1丙唧、把類的構(gòu)造函數(shù)私有化
* 2愈魏、自己創(chuàng)建一個(gè)類的對象
* 3、對外提供一個(gè)公共方法想际,返回類的對象
*
* */
//1培漏、把類的構(gòu)造函數(shù)私有化
private TokenProcessor(){}
//2、自己創(chuàng)建一個(gè)類的對象
private static final TokenProcessor instance = new TokenProcessor();
//3沼琉、對外提供一個(gè)公共方法北苟,返回類的對象
public static TokenProcessor getInstance(){
return instance;
}
//下面是一種產(chǎn)生隨機(jī)碼的方法,用到md5算法
//數(shù)據(jù)指紋:128位長打瘪,16個(gè)字節(jié)的md5友鼻,不管數(shù)據(jù)有多大都是128位長傻昙,一般用于加密
public String makeToken(){
String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
try {
MessageDigest md = MessageDigest.getInstance("md5");
//我們不能將md5指紋直接轉(zhuǎn)換成字符串,因?yàn)橹讣y是一個(gè)任意二進(jìn)制數(shù)彩扔,如果要轉(zhuǎn)換成字符串就需要查碼表妆档,
//而很可能二進(jìn)制表示的字節(jié)在碼表中沒有對應(yīng)的,這樣就會出現(xiàn)亂碼
byte[] md5 = md.digest(token.getBytes());
//base64編碼:任意二進(jìn)制編碼名字字符虫碉,注意其使用方式
BASE64Encoder encoder = new BASE64Encoder();
return encoder.encode(md5);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
package cn.itcast.form;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class DoFormServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
boolean b = isToken(request);
//如果是true表示重復(fù)提交
if(b == true){
System.out.println("請不要重復(fù)提交");
return ;
}
//如果是false表示表單隱藏字段的token和session中一致贾惦,即第一次提交,這里我們先將session中
//的token字段清除再處理用戶請求敦捧。
request.getSession().removeAttribute("token");
System.out.println("處理用戶提交請求");
}
private boolean isToken(HttpServletRequest request){
String client_token = request.getParameter("token");
//表單token為空
if(client_token == null){
return true;
}
//session的token字段為空
String server_token = (String) request.getSession().getAttribute("token");
if(server_token == null){
return true;
}
//表單token和session的token不一致
if(!client_token.equals(server_token)){
return true;
}
return false;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
3.7 簡單一次性校驗(yàn)碼
(1)一次性驗(yàn)證碼的主要目的就是為了限制人們利用工具軟件來暴力猜測密碼须板。
(2)服務(wù)器程序接收到表單數(shù)據(jù)后,首先判斷用戶是否填寫了正確的驗(yàn)證碼兢卵,只有該驗(yàn)證碼與服務(wù)器保存的驗(yàn)證碼匹配時(shí)习瑰,服務(wù)器程序才開始正常的表單處理流程。
(3)密碼猜測工具要逐一嘗試每個(gè)密碼的前提條件是先輸入正確的驗(yàn)證碼秽荤,而驗(yàn)證碼是一次性有效的甜奄,這樣基本上就阻斷了密碼猜測工具的自動地處理過程。
表單頁面
<!DOCTYPE html>
<html>
<head>
<title>登錄界面</title>
<meta name="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<form action="/session/servlet/LoginServlet" method="post">
用戶名:<input type="text" name="username"><br>
密碼:<input type="password" name="password"><br>
認(rèn)證碼:<input type="text" name="checkcode"><img src="/session/servlet/ImageServlet"><br>
<input type="submit" value="登錄">
</form>
</body>
</html>
校驗(yàn)servlet
package cn.itcast.login2;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginServlet2 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String checkcode = request.getParameter("checkcode");
String s_checkcode = (String) request.getSession().getAttribute("checkcode");
if(checkcode == null || s_checkcode == null || !checkcode.equals(s_checkcode)){
System.out.println("驗(yàn)證碼有誤");
return;
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
圖片產(chǎn)生servlet
package cn.itcast.login2;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
import javax.imageio.ImageIO;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class ImageServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//6.設(shè)置頭窃款,控制瀏覽器不要緩存圖片
response.setHeader("Expires", "-1");
response.setHeader("Cache-Control", "no-cache");
response.setHeader("Pragma", "no-cache");
//5.讓瀏覽器以圖片方式打開
response.setHeader("Content-type", "image/jpeg");
//1.在內(nèi)存中創(chuàng)建一副圖片
BufferedImage image = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
//2.得到圖片
Graphics2D g = (Graphics2D) image.getGraphics();
//設(shè)置圖片背景色
g.setColor(Color.RED);
g.fillRect(0, 0, 80, 20);
//3.向圖片上寫數(shù)據(jù)
g.setColor(Color.BLUE);
g.setFont(new Font(null, Font.BOLD, 20));
String checkcode = makeNum();
request.getSession().setAttribute("checkcode", checkcode);
g.drawString(checkcode, 0, 20);
//4.將圖片寫給瀏覽器
ImageIO.write(image, "jpg", response.getOutputStream());
}
private String makeNum(){
Random r = new Random();
String num = r.nextInt(9999999) + "";
StringBuffer sb = new StringBuffer();
//隨機(jī)數(shù)不足7位的補(bǔ)零
for(int i = 0; i < 7-num.length(); i++){
sb.append("0");
}
num = sb.toString() + num;
return num;
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}