10.会话管理(我的JavaEE笔记)

一、会话

1.1什么是会话

会话可以理解为:用户开启一个浏览器,点击多个超链接,访问服务器多个web资源,然后关闭浏览器,整个过程称之为一个会话。(浏览器关闭了,一个会话也就结束了)

1.2会话过程中要解决的一些问题

每个用户在使用浏览器与服务器进行会话的过程中,不可避免各自会产生一些数据,程序要想办法为每个用户保存这些数据。之前我们讲了两种方式保存数据。一种是将数据保存在request对象中,但是我们知道用户没访问一次servlet就会创建一个request和response对象,这样在如网上购物这样的情景下是不能够使用的。还有另一种方式是使用servletContext,但是此容器是针对整个web应用的,如果多个用户访问同一个web应用,那么显然会造成数据混乱。

1.3保存会话数据的两种技术

1.3.1 Cookie

Cookie是客户端技术,程序把每个用户的数据以Cookie的形式写给用户各自的浏览器。当用户使用浏览器再去访问服务器中的web资源时,就会带着各自的数据去。这样,web资源处理的就是用户各自的数据了。

1.3.2 Session

Session是服务器端技术,利用这个技术,服务器在运行时可以为每一个用户的浏览器创建一个其独享的session对象,由于session为用户浏览器独享,所以用户在访问服务器的web资源时,可以把各自的数据放在各自的session中,当用户再去访问服务器中的其它web资源时,其它web资源再从用户各自的session中取出数据为用户服务。

二、Cookie的应用(工程cookie)

2.1显示用户上次访问时间

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("这是网站首页!<br>");
        out.write("上次访问的时间: ");
        //从request域中获得Cookie
        Cookie[] cookies = request.getCookies();
        //注意for循环中的两个条件不能互换位置,必须先判断是否为空
        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中设置一个键值对lastAccessTime=时间值。
        Cookie cookie = new Cookie("lastAccessTime", System.currentTimeMillis() + "");
        //设置Cookie保存的最长时间,单位是s
        cookie.setMaxAge(10000);
        //设置Cookie的路径,即本工程的路径
        cookie.setPath("/cookie");
        response.addCookie(cookie);
    }

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

说明:从程序中可以看到Cookie中就是一些键值对,我们可以自己进行设定相关的信息。

2.2 Cookie的细节

(1)一个Cookie只能标识一种信息,它至少含有一个标识该信息的名称(NAME)和设置值(VALUE)。

(2)一个WEB站点可以给一个web浏览器发送多个Cookie,一个web浏览器也可以存储多个web站点提供多个Cookie。

(3)浏览器一般只允许存放300个Cookie,每个站点最多存放20个Cookie,每个Cookie的大小限制为4kb。

(4)如果创建了一个cookie,并将他发送到浏览器,默认情况下它是一个会话级别的cookie(即存储在浏览器的内存中),用户退出浏览器之后即被删除。若希望浏览器将该Cookie存储在磁盘上,则需要使用maxAge,并给出一个以秒为单位的时间。将最大时效设为0则是命令浏览器删除该Cookie。

(5)注意:删除Cookie时,path路径必须一致,否则不会删除。同时现在的浏览器一般都自带开发人员工具,可以进行抓包。

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;
//显示所有书籍,同时显示浏览过的书籍
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("本网站有如下商品: <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='//www.greatytc.com/cookie/servlet/CookieDemo3?id="+book.getId()+"'target='_blank'>"+book.getName()+"</a>");
            out.write("<br>");
        }
        
        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();
                //以下划线分割,"\\_"这样表示的目的是不管下划线在正则表达式中有没有定义都可以确保以下划线分割
                //获得所有的id号,然后进行显示
                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)显示用户选中的书籍详细信息并产生和处理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;
//显示用户选中书籍的详细信息,并产生cookie发给浏览器
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.根据用户带过来的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.getDiscription() + "<br>");
        
        //得到浏览过所有的书籍信息
        String bookHistory = makeHistory(request, id);
        //new一个cookie
        Cookie cookie = new Cookie("bookHistory", bookHistory);
        cookie.setMaxAge(1*30*24*3600);
        //向浏览器中发送一个cookie
        response.addCookie(cookie);
        
    }
    private String makeHistory(HttpServletRequest request, String id){
        String bookHistory = null;
        Cookie cookies[] = request.getCookies();
        //从cookie中得到相关浏览过的书籍信息
        for(int i = 0; cookies != null && i < cookies.length; i++){
            if(cookies[i].getName().equals("bookHistory")){
                bookHistory = cookies[i].getValue();
            }
        }
        
        //下面是从浏览器中传过来的cookie的四种情况,这里我们假设以前浏览过的商品最多只能有三个,多的
        //必须去掉
        /*
         * 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,那么直接将最后一个删掉,将此id插入到最前面
            if(list.size() >= 3){
                list.removeLast();
                list.addFirst(id);
            }else {
                //bookHistory = 3_2直接插入到最前面
                list.addFirst(id);
            }
        }
        //然后将id组合成一个字符串
        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的应用(工程session)

3.1在web开发中

服务器可以为每个用户浏览器创建一个会话对象(Session对象),注意:一个浏览器独占一个session对象(默认情况下)。因此,在需要保存用户数据时,服务器程序可以把用户数据写到用户浏览器独占的session中,当用户使用浏览器访问其他程序时,其他程序可以从用户的session中取出该用户的数据,为用户服务。

3.2 Session和Cookie的主要区别在于

Cookie把数据写到用户的浏览器中。Session技术是把用户的数据写到用户独占的session中,是在服务器中。

3.3 Session对象由服务器创建

开发人员可以调用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对象,服务器在用户第一次访问此web时若发现没有相应的session就会
         * 创建一个,若存在就不会创建*/
        HttpSession session = request.getSession();
        session.setAttribute("data", "abc");//写入数据
        System.out.println(session.getId());
        /*注意:我们要知道之所以我们可以在多次访问服务器时能共享session对象,是因为服务器会用将创建好的
         * session对象的ID号以Cookie的形式返回给浏览器,就相当于返回给浏览器了一个名为JSESSIONID
         * 的Cookie,但是浏览器默认是在其关闭之后销毁此Cookie,若想在下次访问的时候还使用同一个session
         * 则需要设置有效时长。如果没有下面的设置有效时长,我们可以从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的数据

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完成简单的购物功能

注意:下面程序中假设浏览器禁用Cookie,此时我们只能使用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("本网站有如下书籍:<br>");
        HttpSession session = request.getSession();
        //得到所有书籍实体
        Set<Map.Entry<String, Book>> set = DB.getAll().entrySet();
        for(Map.Entry<String, Book> me : set){
            //从一个实体中取得书籍直接使用方法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中又会重新创建一个新的session,也就是说BuyServlet.java和ListCartServlet.java没有实现session共享。将URL地址重写之后就相当于在每个超链接的地址后面加上了共享session的ID号。

package cn.itcast.shopping;
//处理用户的购买工作,完成session的产生和更新
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 {
        //得到用户点击书籍的id
        String id = request.getParameter("id");
        Book book = (Book) DB.getAll().get(id);
        HttpSession session = request.getSession();
        String sessionid = session.getId();
        //有可能用户浏览器的Cookie被设置为禁用
        Cookie cookie = new Cookie("JSESSIONID", sessionid);
        cookie.setMaxAge(1*3600);
        cookie.setPath("/session");
        response.addCookie(cookie);
        
        //我们将书籍用户购买的书籍放在一个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)如果我们一开始访问,则会创建一个session,如果我点击相关的连接,再返回刷新则可以取得之前的session。但是如果我们第一次访问之后关掉浏览器,再次访问,则不会取到之前的session(不使用Cookie),因为开一个浏览器就相当于开启了一次会话,如果关闭,则表示此次会话结束了。同时,如果我们同时开启浏览器两个窗口,也表示的是两次会话,它们之间毫无关系。但是在试验的时候只有在IE8以下的浏览器能显示出来,IE8和其他高版本的会看不到现象。还有,如果我们在静态页面中写了一些超链接,当点击超链接时,也是属于在一次会话中,所以还是使用之前创建好的session。

(2)我们要想让浏览器在禁用Cookie之后下次访问还能使用第一次创建的session对象是办不到的。而只能在一次会话中共享session。同时如果我们没有禁用Cookie,那么即使我们将设置了URL地址重写也是无效的,只有在禁用Cookie的条件下重写URL地址才是有效的。

(3)session的生命周期是request.getSession()开始到其销毁为止,一般浏览器默认在30分钟后销毁,当然我们也可以自己在其配置文件(web.xml)中配置:

<session-config>
    <session-timeout>1</session-timeout>
</session-config>

上面的1表示一分钟。此外,我们还可以手工销毁,调用session.invalidate销毁session对象即可。在开发中大多使用Cookie。

(4) 重写分为两种。其中response.encodeRedirectURL(java.lang.String url)是用于对sendRedirect方法后的url地址进行重写。而response.encodeURL(java.lang.String url)是用于对表单action和超链接的url地址进行重写,上面的例子中有说明。

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>

用户实体

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("用户名或密码错误");
            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,后面再详细讲解。

3.6防止表单重复提交

使用js防止表单重复提交

<!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在浏览器中进行检查,这种检查是必须要有的,但是却不够。在这种静态页面中,当用户刷新或单击后退再次提交还是会导致重复提交。我们还需要在后台进行检查。

后台检查时,如果表单是由servlet程序生成(例子不太好,仅做参考),我们可以让servlet为每次产生的表单分配一个唯一的随机标识号,并在form表单的一个隐藏字段中设置这个标识号,同时在当前用户的session中保存这个标识号。
当用户提交form表单时,负责处理表单提交的servlet得到表单提交的标识号,并与session中的标识号进行比较,如果相同则清除标识号并处理请求,否则表示重复提交。

下面程序中,服务器将拒绝用户提交的表单请求:
(1)存储在session中的标识号和表单标识号不同;
(2)当前用户的session中不存在标识号;
(3)用户提交的表单数据中没有标识号字段。

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();
        //产生一个随机数,用于校验
        String token = TokenProcessor.getInstance().makeToken();
        //将随机数保存在session中
        request.getSession().setAttribute("token", token);
        out.println("<form action='/session/servlet/DoFormServlet' method='pos'>");
        //将随机数同时也发送给表单的隐藏字段
        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{
    /*
     * 这里用到了单态设计模式(即保证类的对象在内存中只有一份)
     * 1、把类的构造函数私有化
     * 2、自己创建一个类的对象
     * 3、对外提供一个公共方法,返回类的对象
     * 
     * */
    //1、把类的构造函数私有化
    private TokenProcessor(){}
    
    //2、自己创建一个类的对象
    private static final TokenProcessor instance = new TokenProcessor();
    
    //3、对外提供一个公共方法,返回类的对象
    public static TokenProcessor getInstance(){
        return instance;
    }
    //下面是一种产生随机码的方法,用到md5算法
    //数据指纹:128位长,16个字节的md5,不管数据有多大都是128位长,一般用于加密
    public String makeToken(){
        String token = (System.currentTimeMillis() + new Random().nextInt(999999999)) + "";
        try {
            MessageDigest md = MessageDigest.getInstance("md5");
            //我们不能将md5指纹直接转换成字符串,因为指纹是一个任意二进制数,如果要转换成字符串就需要查码表,
            //而很可能二进制表示的字节在码表中没有对应的,这样就会出现乱码
            byte[] md5 = md.digest(token.getBytes());
            //base64编码:任意二进制编码名字字符,注意其使用方式
            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表示重复提交
        if(b == true){
            System.out.println("请不要重复提交");
            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 简单一次性校验码

(1)一次性验证码的主要目的就是为了限制人们利用工具软件来暴力猜测密码。

(2)服务器程序接收到表单数据后,首先判断用户是否填写了正确的验证码,只有该验证码与服务器保存的验证码匹配时,服务器程序才开始正常的表单处理流程。

(3)密码猜测工具要逐一尝试每个密码的前提条件是先输入正确的验证码,而验证码是一次性有效的,这样基本上就阻断了密码猜测工具的自动地处理过程。

表单页面

<!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="text" name="checkcode"><img src="/session/servlet/ImageServlet"><br>
        <input type="submit" value="登录">
      </form>
  </body>
</html>

校验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("验证码有误");
            return;
        }
    }

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

图片产生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.设置头,控制浏览器不要缓存图片
        response.setHeader("Expires", "-1");
        response.setHeader("Cache-Control", "no-cache");
        response.setHeader("Pragma", "no-cache");
        //5.让浏览器以图片方式打开
        response.setHeader("Content-type", "image/jpeg");
        
        //1.在内存中创建一副图片
        BufferedImage image = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);
        //2.得到图片
        Graphics2D g = (Graphics2D) image.getGraphics();
        //设置图片背景色
        g.setColor(Color.RED);
        g.fillRect(0, 0, 80, 20);
        
        //3.向图片上写数据
        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();
        //随机数不足7位的补零
        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);
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容