Servlet第六篇【Session介紹、API链沼、生命周期默赂、應(yīng)用、與Cookie區(qū)別】

什么是Session

Session 是另一種記錄瀏覽器狀態(tài)的機(jī)制忆植。不同的是Cookie保存在瀏覽器中放可,Session保存在服務(wù)器中谒臼。用戶使用瀏覽器訪問服務(wù)器的時(shí)候朝刊,服務(wù)器把用戶的信息以某種的形式記錄在服務(wù)器,這就是Session

如果說Cookie是檢查用戶身上的”通行證“來確認(rèn)用戶的身份蜈缤,那么Session就是通過檢查服務(wù)器上的”客戶明細(xì)表“來確認(rèn)用戶的身份的拾氓。Session相當(dāng)于在服務(wù)器中建立了一份“客戶明細(xì)表”。

為什么要使用Session技術(shù)底哥?

Session比Cookie使用方便咙鞍,Session可以解決Cookie解決不了的事情【Session可以存儲(chǔ)對象,Cookie只能存儲(chǔ)字符串趾徽⌒蹋】。

Session API

  • long getCreationTime();【獲取Session被創(chuàng)建時(shí)間】
  • String getId();【獲取Session的id】
  • long getLastAccessedTime();【返回Session最后活躍的時(shí)間】
  • ServletContext getServletContext();【獲取ServletContext對象】
  • void setMaxInactiveInterval(int var1);【設(shè)置Session超時(shí)時(shí)間】
  • int getMaxInactiveInterval();【獲取Session超時(shí)時(shí)間】
  • Object getAttribute(String var1);【獲取Session屬性
  • Enumeration
  • void setAttribute(String var1, Object var2);【設(shè)置Session屬性】
  • void removeAttribute(String var1);【移除Session屬性】
  • void invalidate();【銷毀該Session】
  • boolean isNew();【該Session是否為新的】

Session作為域?qū)ο?/h1>

從上面的API看出孵奶,Session有著request和ServletContext類似的方法疲酌。其實(shí)Session也是一個(gè)域?qū)ο?/strong>。Session作為一種記錄瀏覽器狀態(tài)的機(jī)制,只要Session對象沒有被銷毀朗恳,Servlet之間就可以通過Session對象實(shí)現(xiàn)通訊

  • 我們來試試吧湿颅,在Servlet4中設(shè)置Session屬性

        //得到Session對象
        HttpSession httpSession = request.getSession();

        //設(shè)置Session屬性
        httpSession.setAttribute("name", "看完博客就要點(diǎn)贊!粥诫!");
  • 在Servlet5中獲取到Session存進(jìn)去的屬性

        //獲取到從Servlet4的Session存進(jìn)去的值
        HttpSession httpSession = request.getSession();
        String value = (String) httpSession.getAttribute("name");
        System.out.println(value);
  • 訪問Servlet4油航,再訪問Servlet5
image
  • 一般來講,當(dāng)我們要存進(jìn)的是用戶級(jí)別的數(shù)據(jù)就用Session怀浆,那什么是用戶級(jí)別呢谊囚?只要瀏覽器不關(guān)閉,希望數(shù)據(jù)還在揉稚,就使用Session來保存秒啦。

Session的生命周期和有效期

  • Session在用戶第一次訪問服務(wù)器Servlet,jsp等動(dòng)態(tài)資源就會(huì)被自動(dòng)創(chuàng)建搀玖,Session對象保存在內(nèi)存里余境,這也就為什么上面的例子可以直接使用request對象獲取得到Session對象

  • 如果訪問HTML,IMAGE等靜態(tài)資源Session不會(huì)被創(chuàng)建灌诅。

  • Session生成后芳来,只要用戶繼續(xù)訪問,服務(wù)器就會(huì)更新Session的最后訪問時(shí)間猜拾,無論是否對Session進(jìn)行讀寫即舌,服務(wù)器都會(huì)認(rèn)為Session活躍了一次

  • 由于會(huì)有越來越多的用戶訪問服務(wù)器挎袜,因此Session也會(huì)越來越多顽聂。為了防止內(nèi)存溢出,服務(wù)器會(huì)把長時(shí)間沒有活躍的Session從內(nèi)存中刪除盯仪,這個(gè)時(shí)間也就是Session的超時(shí)時(shí)間紊搪。

  • Session的超時(shí)時(shí)間默認(rèn)是30分鐘,有三種方式可以對Session的超時(shí)時(shí)間進(jìn)行修改

  • 第一種方式:在tomcat/conf/web.xml文件中設(shè)置全景,時(shí)間值為20分鐘耀石,所有的WEB應(yīng)用都有效


            <session-config>
                <session-timeout>20</session-timeout>
            </session-config>   
image
  • 第二種方式:在單個(gè)的web.xml文件中設(shè)置,對單個(gè)web應(yīng)用有效爸黄,如果有沖突滞伟,以自己的web應(yīng)用為準(zhǔn)

            <session-config>
                <session-timeout>20</session-timeout>
            </session-config>   
  • 第三種方式:通過setMaxInactiveInterval()方法設(shè)置

        //設(shè)置Session最長超時(shí)時(shí)間為60秒炕贵,這里的單位是秒
        httpSession.setMaxInactiveInterval(60);

        System.out.println(httpSession.getMaxInactiveInterval());
image
  • Session的有效期與Cookie的是不同的
image

使用Session完成簡單的購物功能

  • 我們還是以書籍為例梆奈,所以可以copy“顯示瀏覽過的商品“例子部分的代碼。

        response.setContentType("text/html;charset=UTF-8");
        PrintWriter printWriter = response.getWriter();

        printWriter.write("網(wǎng)頁上所有的書籍:" + "<br/>");

        //拿到數(shù)據(jù)庫所有的書
        LinkedHashMap<String, Book> linkedHashMap = DB.getAll();
        Set<Map.Entry<String, Book>> entry = linkedHashMap.entrySet();

        //顯示所有的書到網(wǎng)頁上
        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/>");
        }
  • 在購物車頁面上称开,獲取到用戶想買的書籍【用戶可能不單想買一本書亩钟,于是乎,就用一個(gè)List容器裝載書籍】,有了:先遍歷Cookie径荔,再判斷是否是第一次訪問Servlet的邏輯思路督禽,我們就可以先獲取到Session的屬性,如果Session的屬性為null总处,那么就是還沒有該屬性

        //得到用戶想買書籍的id
        String id = request.getParameter("id");

        //根據(jù)書籍的id找到用戶想買的書
        Book book = (Book) DB.getAll().get(id);

        //獲取到Session對象
        HttpSession httpSession = request.getSession();

        //由于用戶可能想買多本書的狈惫,所以我們用一個(gè)容器裝著書籍
        List list = (List) httpSession.getAttribute("list");
        if (list == null) {

            list = new ArrayList();

            //設(shè)置Session屬性
            httpSession.setAttribute("list",list);
        }
        //把書籍加入到list集合中
        list.add(book);
  • 按我們正常的邏輯思路:先創(chuàng)建一個(gè)ArrayList對象,把書加到list集合中鹦马,然后設(shè)置Session的屬性胧谈。這樣是行不通的。每次Servlet被訪問的時(shí)候都會(huì)創(chuàng)建一個(gè)ArrayList集合荸频,書籍會(huì)被分發(fā)到不同的ArrayList中去菱肖。所以下面的代碼是不行的!

        //得到用戶想買書籍的id
        String id = request.getParameter("id");

        //根據(jù)書籍的id找到用戶想買的書
        Book book = (Book) DB.getAll().get(id);

        //獲取到Session對象
        HttpSession httpSession = request.getSession();

        //創(chuàng)建List集合
        List list = new ArrayList();
        list.add(book);

        httpSession.setAttribute("list", list);
  • 既然用戶已經(jīng)購買了書籍旭从,那么也應(yīng)該給提供頁面顯示用戶購買過哪些書籍

        //得到用戶想買書籍的id
        String id = request.getParameter("id");

        //根據(jù)書籍的id找到用戶想買的書
        Book book = (Book) DB.getAll().get(id);

        //獲取到Session對象
        HttpSession httpSession = request.getSession();

        //由于用戶可能想買多本書的稳强,所以我們用一個(gè)容器裝著書籍
        List list = (List) httpSession.getAttribute("list");
        if (list == null) {

            list = new ArrayList();

            //設(shè)置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/>");
            }
        }
  • 效果如下
image

Session的實(shí)現(xiàn)原理

  • 用現(xiàn)象說明問題退疫,我在Servlet4中的代碼設(shè)置了Session的屬性

        //得到Session對象
        HttpSession httpSession = request.getSession();

        //設(shè)置Session屬性
        httpSession.setAttribute("name", "看完博客就要點(diǎn)贊!鸽素!");
  • 接著在Servlet7把Session的屬性取出來

        String value = (String) request.getSession().getAttribute("name");

        printWriter.write(value);
  • 自然地褒繁,我們能取到在Servlet4中Session設(shè)置的屬性
image
  • 接著,我在瀏覽器中新建一個(gè)會(huì)話馍忽,再次訪問Servlet7
image
  • 發(fā)現(xiàn)報(bào)了空指針異常的錯(cuò)誤
image
  • 現(xiàn)在問題來了:服務(wù)器是如何實(shí)現(xiàn)一個(gè)session為一個(gè)用戶瀏覽器服務(wù)的棒坏?換個(gè)說法:為什么服務(wù)器能夠?yàn)椴煌挠脩魹g覽器提供不同session?

  • HTTP協(xié)議是無狀態(tài)的遭笋,Session不能依據(jù)HTTP連接來判斷是否為同一個(gè)用戶坝冕。于是乎:服務(wù)器向用戶瀏覽器發(fā)送了一個(gè)名為JESSIONID的Cookie,它的值是Session的id值坐梯。其實(shí)Session依據(jù)Cookie來識(shí)別是否是同一個(gè)用戶徽诲。

  • 簡單來說:Session 之所以可以識(shí)別不同的用戶刹帕,依靠的就是Cookie

  • 該Cookie是服務(wù)器自動(dòng)頒發(fā)給瀏覽器的吵血,不用我們手工創(chuàng)建的。該Cookie的maxAge值默認(rèn)是-1偷溺,也就是說僅當(dāng)前瀏覽器使用蹋辅,不將該Cookie存在硬盤中

  • 我們來捋一捋思路流程:當(dāng)我們訪問Servlet4的時(shí)候,服務(wù)器就會(huì)創(chuàng)建一個(gè)Session對象挫掏,執(zhí)行我們的程序代碼侦另,并自動(dòng)頒發(fā)個(gè)Cookie給用戶瀏覽器

image
  • 當(dāng)我們用同一個(gè)瀏覽器訪問Servlet7的時(shí)候,瀏覽器會(huì)把Cookie的值通過http協(xié)議帶過去給服務(wù)器,服務(wù)器就知道用哪一Session褒傅。
image
  • 而當(dāng)我們使用新會(huì)話的瀏覽器訪問Servlet7的時(shí)候弃锐,該新瀏覽器并沒有Cookie,服務(wù)器無法辨認(rèn)使用哪一個(gè)Session殿托,所以就獲取不到值

瀏覽器禁用了Cookie霹菊,Session還能用嗎?

上面說了Session是依靠Cookie來識(shí)別用戶瀏覽器的支竹。如果我的用戶瀏覽器禁用了Cookie了呢旋廷?絕大多數(shù)的手機(jī)瀏覽器都不支持Cookie,那我的Session怎么辦礼搁?

image
  • 好的饶碘,我們來看看情況是怎么樣的。用戶瀏覽器訪問Servlet4的時(shí)候馒吴,服務(wù)器向用戶瀏覽器頒發(fā)了一個(gè)Cookie
image
  • 但是呢扎运,當(dāng)用戶瀏覽器訪問Servlet7的時(shí)候,由于我們禁用了Cookie饮戳,所以用戶瀏覽器并沒有把Cookie帶過去給服務(wù)器绪囱。
image
  • 一看,Session好像不能用了莹捡。但是Java Web提供了解決方法:URL地址重寫

  • HttpServletResponse類提供了兩個(gè)URL地址重寫的方法:

    • encodeURL(String url)
    • encodeRedirectURL(String url)
  • 需要值得注意的是:這兩個(gè)方法會(huì)自動(dòng)判斷該瀏覽器是否支持Cookie鬼吵,如果支持Cookie,重寫后的URL地址就不會(huì)帶有jsessionid了【當(dāng)然了篮赢,即使瀏覽器支持Cookie齿椅,第一次輸出URL地址的時(shí)候還是會(huì)出現(xiàn)jsessionid(因?yàn)闆]有任何Cookie可帶)】

  • 下面我們就以上面“購物”的例子來做試驗(yàn)吧!首先我們來看看禁用掉Cookie對原來的小例子有什么影響启泣。

  • 訪問Servlet1涣脚,隨便點(diǎn)擊一本書籍購買

image
  • 無論點(diǎn)擊多少次,都會(huì)直接提示我們有買過任何商品
image
  • 原因也非常簡單寥茫,沒有Cookie傳遞給服務(wù)器遣蚀,服務(wù)器每次創(chuàng)建的時(shí)候都是新的Session,導(dǎo)致最后獲取到的List集合一定是空的纱耻。

  • 不同Servlet獲取到的Session的id號(hào)都是不同的芭梯。

image
  • 下面我們就對URL進(jìn)行重寫,看看能不能恢復(fù)沒有禁掉Cookie之前的效果弄喘。

  • 原則:把Session的屬性帶過去【傳遞給】另外一個(gè)Servlet玖喘,都要URL地址重寫

  • 在跳轉(zhuǎn)到顯示購買過商品的Servlet的時(shí)候,URL地址重寫蘑志。


        String url = "/ouzicheng/Servlet7";

        response.sendRedirect(response.encodeURL(url));
  • 再次訪問Servlet1累奈,當(dāng)我點(diǎn)擊javaweb的時(shí)候贬派,已經(jīng)能夠成功出現(xiàn)我買過的商品了。并且Session的id通過URL地址重寫澎媒,使用的是同一個(gè)Session
image
image
  • URL地址重寫的原理:將Session的id信息重寫到URL地址中搞乏。服務(wù)器解析重寫后URL,獲取Session的id戒努。這樣一來查描,即使瀏覽器禁用掉了Cookie,但Session的id通過服務(wù)器端傳遞柏卤,還是可以使用Session來記錄用戶的狀態(tài)冬三。

Session禁用Cookie

  • Java Web規(guī)范支持通過配置禁用Cookie

  • 禁用自己項(xiàng)目的Cookie

    • 在META-INF文件夾下的context.xml文件中修改(沒有則創(chuàng)建)
    image
    
            <?xml version='1.0' encoding='utf-8'?>
    
            <Context path="/ouzicheng" cookies="false">
            </Context>
    
  • 禁用全部web應(yīng)用的Cookie

    • 在conf/context.xml中修改
    image

注意:該配置只是讓服務(wù)器不能自動(dòng)維護(hù)名為jsessionid的Cookie,并不能阻止Cookie的讀寫缘缚。


Session案例

使用Session完成用戶簡單登陸

  • 先創(chuàng)建User類

    private String username = null;
    private String password = null;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    ....各種set勾笆、get方法
  • 使用簡單的集合模擬一個(gè)數(shù)據(jù)庫

    private static List<User> list = new ArrayList<>();

    //裝載些數(shù)據(jù)進(jìn)數(shù)據(jù)庫
    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;
    }
  • 表單提交的工作我就在jsp寫了,如果在Servlet寫太麻煩了桥滨!

<form action="/ouzicheng/LoginServlet" method="post">
    用戶名:<input type="text" name="username"><br/>
    密碼:<input type="password" name="password"><br/>
    <input type="submit" value="提交">

</form>

  • 獲取到表單提交的數(shù)據(jù)窝爪,查找數(shù)據(jù)庫是否有相對應(yīng)的用戶名和密碼。如果沒有就提示用戶名或密碼出錯(cuò)了齐媒,如果有就跳轉(zhuǎn)到另外一個(gè)頁面

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

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

        //如果找不到蒲每,就是用戶名或密碼出錯(cuò)了。
        if (user == null) {
            response.getWriter().write("you can't login");
            return;
        }

        //標(biāo)記著該用戶已經(jīng)登陸了喻括!
        HttpSession httpSession = request.getSession();
        httpSession.setAttribute("user", user);

        //跳轉(zhuǎn)到其他頁面邀杏,告訴用戶成功登陸了。
        response.sendRedirect(response.encodeURL("index.jsp"));
  • 我們來試試下數(shù)據(jù)庫沒有的用戶名和密碼唬血,提示我不能登陸望蜡。
image
image
  • 試試數(shù)據(jù)庫存在的用戶名和密碼
image
image

利用Session防止表單重復(fù)提交

  • 重復(fù)提交的危害:

    • 在投票的網(wǎng)頁上不停地提交,實(shí)現(xiàn)了刷票的效果拷恨。
    • 注冊多個(gè)用戶脖律,不斷發(fā)帖子,擾亂正常發(fā)帖秩序腕侄。
  • 首先我們來看一下常見的重復(fù)提交小泉。

    • 在處理表單的Servlet中刷新。
    • 后退再提交
    • 網(wǎng)絡(luò)延遲冕杠,多次點(diǎn)擊提交按鈕
  • 下面的gif是后退再提交微姊,在處理提交請求的Servlet中刷新

    image

  • 下面的gif是網(wǎng)絡(luò)延遲,多次點(diǎn)擊提交按鈕

    image

  • 對于網(wǎng)絡(luò)延遲造成的多次提交數(shù)據(jù)給服務(wù)器拌汇,其實(shí)是客戶端的問題柒桑。于是弊决,我們可以使用javaScript來防止這種情況

  • 要做的事情也非常簡單:當(dāng)用戶第一次點(diǎn)擊提交按鈕時(shí)噪舀,把數(shù)據(jù)提交給服務(wù)器魁淳。當(dāng)用戶再次點(diǎn)擊提交按鈕時(shí),就不把數(shù)據(jù)提交給服務(wù)器了与倡。

  • 監(jiān)聽用戶提交事件界逛。只能讓用戶提交一次表單!


<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>表單提交</title>

    <script type="text/javascript">

        //定義一個(gè)全局標(biāo)識(shí)量:是否已經(jīng)提交過表單數(shù)據(jù)
        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>
  • 好的息拜,我們來試一下是不是真的可以解決網(wǎng)絡(luò)延遲所造成的多次提交表單數(shù)據(jù),注意鼠標(biāo)净响,我已經(jīng)點(diǎn)擊過很多次的了少欺!
image
  • 由于網(wǎng)絡(luò)延遲造成的多次提交數(shù)據(jù)給服務(wù)器,我們還可以使用javaScript代碼這樣解決:當(dāng)我點(diǎn)擊過一次提交按鈕時(shí)馋贤,我就把提交的按鈕隱藏起來赞别。不能讓用戶點(diǎn)擊了

  • 想要讓按鈕隱藏起來配乓,也很簡單仿滔。只要獲取到按鈕的節(jié)點(diǎn),就可以控制按鈕的隱藏或顯示了犹芹!


    <script type="text/javascript">
        function doSubmit() {
            var button = document.getElementById("button");

            button.disabled = disabled;

            return true;
        }
    </script>

  • 我們再來看一下效果
image
  • 在處理表單的Servlet中刷新后退再提交這兩種方式不能只靠客戶端來限制了崎页。也就是說javaScript代碼無法阻止這兩種情況的發(fā)生。

  • 于是乎腰埂,我們就想得用其他辦法來阻止表單數(shù)據(jù)重復(fù)提交了飒焦。我們現(xiàn)在學(xué)了Session,Session可以用來標(biāo)識(shí)一個(gè)用戶是否登陸了屿笼。Session的原理也說了:不同的用戶瀏覽器會(huì)擁有不同的Session荒给。而request和ServletContext為什么就不行呢?request的域?qū)ο笾荒苁且淮蝖ttp請求刁卜,提交表單數(shù)據(jù)的時(shí)候request域?qū)ο蟮臄?shù)據(jù)取不出來志电。ServletContext代表整個(gè)web應(yīng)用,如果有幾個(gè)用戶瀏覽器同時(shí)訪問蛔趴,ServletContext域?qū)ο蟮臄?shù)據(jù)會(huì)被多次覆蓋掉挑辆,也就是說域?qū)ο蟮臄?shù)據(jù)就毫無意義了。

  • 可能到這里孝情,我們會(huì)想到:在提交數(shù)據(jù)的時(shí)候鱼蝉,存進(jìn)Session域?qū)ο蟮臄?shù)據(jù),在處理提交數(shù)據(jù)的Servlet中判斷Session域?qū)ο髷?shù)據(jù)????箫荡。究竟判斷Session什么媳友?判斷Session域?qū)ο蟮臄?shù)據(jù)不為null骤视?沒用呀,既然已經(jīng)提交過來了著洼,那肯定不為null。

  • 此時(shí),我們就想到了,在表單中還有一個(gè)隱藏域,可以通過隱藏域把數(shù)據(jù)交給服務(wù)器呈野。

    • 判斷Session域?qū)ο蟮臄?shù)據(jù)和jsp隱藏域提交的數(shù)據(jù)是否對應(yīng)
    • 判斷隱藏域的數(shù)據(jù)是否為空【如果為空印叁,就是直接訪問表單處理頁面的Servlet
    • 判斷Session的數(shù)據(jù)是否為空【servlet判斷完是否重復(fù)提交被冒,最好能立馬移除Session的數(shù)據(jù),不然還沒有移除的時(shí)候轮蜕,客戶端那邊兒的請求又來了昨悼,就又能匹配了,產(chǎn)生了重復(fù)提交跃洛。如果Session域?qū)ο髷?shù)據(jù)為空幔戏,證明已經(jīng)提交過數(shù)據(jù)了!
  • 我們向Session域?qū)ο蟮拇嫒霐?shù)據(jù)究竟是什么呢税课?簡單的一個(gè)數(shù)字闲延?好像也行啊。因?yàn)?strong>只要Session域?qū)ο蟮臄?shù)據(jù)和jsp隱藏域帶過去的數(shù)據(jù)對得上號(hào)就行了呀韩玩,反正在Servlet上判斷完是否重復(fù)提交垒玲,會(huì)立馬把Session的數(shù)據(jù)移除掉的。更專業(yè)的做法是:向Session域?qū)ο蟠嫒氲臄?shù)據(jù)是一個(gè)隨機(jī)數(shù)【Token--令牌】找颓。

  • 生成一個(gè)獨(dú)一無二的隨機(jī)數(shù)


/*
* 產(chǎn)生隨機(jī)數(shù)就應(yīng)該用一個(gè)對象來生成合愈,這樣可以避免隨機(jī)數(shù)的重復(fù)。
* 所以設(shè)計(jì)成單例
* */
public class TokenProcessor {

    private TokenProcessor() {
    }

    private final static TokenProcessor TOKEN_PROCESSOR = new TokenProcessor();

    public static TokenProcessor getInstance() {
        return TOKEN_PROCESSOR;
    }

    public static String makeToken() {

        //這個(gè)隨機(jī)生成出來的Token的長度是不確定的
        String token = String.valueOf(System.currentTimeMillis() + new Random().nextInt(99999999));

        try {
            //我們想要隨機(jī)數(shù)的長度一致击狮,就要獲取到數(shù)據(jù)指紋
            MessageDigest messageDigest = MessageDigest.getInstance("md5");
            byte[] md5 = messageDigest.digest(token.getBytes());

            //如果我們直接 return  new String(md5)出去佛析,得到的隨機(jī)數(shù)會(huì)亂碼。
            //因?yàn)殡S機(jī)數(shù)是任意的01010101010彪蓬,在轉(zhuǎn)換成字符串的時(shí)候寸莫,會(huì)查gb2312的碼表,gb2312碼表不一定支持該二進(jìn)制數(shù)據(jù)档冬,得到的就是亂碼

            //于是乎經(jīng)過base64編碼成了明文的數(shù)據(jù)
            BASE64Encoder base64Encoder = new BASE64Encoder();
            return base64Encoder.encode(md5);

        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }

        return null;

    }

}

  • 創(chuàng)建Token隨機(jī)數(shù)膘茎,并跳轉(zhuǎn)到j(luò)sp頁面

        //生出隨機(jī)數(shù)
        TokenProcessor tokenProcessor = TokenProcessor.getInstance();
        String token = tokenProcessor.makeToken();

        //將隨機(jī)數(shù)存進(jìn)Session中
        request.getSession().setAttribute("token", token);

        //跳轉(zhuǎn)到顯示頁面
        request.getRequestDispatcher("/login.jsp").forward(request, response);

  • jsp隱藏域獲取到Session的值

<form action="/ouzicheng/Servlet7" >

    用戶名:<input type="text" name="username">
    <input type="submit" value="提交" id="button">

    <%--使用EL表達(dá)式取出session中的Token--%>
    <input type="hidden" name="token" value="${token}" >

</form>

  • 在處理表單提交頁面中判斷:jsp隱藏域是否有值帶過來,Session中的值是否為空酷誓,Session中的值和jsp隱藏域帶過來的值是否相等

        String serverValue = (String) request.getSession().getAttribute("token");
        String clientValue = request.getParameter("token");

        if (serverValue != null && clientValue != null && serverValue.equals(clientValue)) {

            System.out.println("處理請求");

            //清除Session域?qū)ο髷?shù)據(jù)
            request.getSession().removeAttribute("token");

        }else {

            System.out.println("請不要重復(fù)提交數(shù)據(jù)披坏!");
        }
  • 下面我們再來看一下,已經(jīng)可以解決表單重復(fù)提交的問題了!
image

實(shí)現(xiàn)原理是非常簡單的:

  • 在session域中存儲(chǔ)一個(gè)token
  • 然后前臺(tái)頁面的隱藏域獲取得到這個(gè)token
  • 在第一次訪問的時(shí)候盐数,我們就判斷seesion有沒有值棒拂,如果有就比對。對比正確后我們就處理請求玫氢,接著就把session存儲(chǔ)的數(shù)據(jù)給刪除了
  • 等到再次訪問的時(shí)候帚屉,我們session就沒有值了谜诫,就不受理前臺(tái)的請求了!

一次性校驗(yàn)碼

  • 一次性校驗(yàn)碼其實(shí)就是為了防止暴力猜測密碼

  • 在講response對象的時(shí)候涮阔,我們使用response對象輸出過驗(yàn)證碼猜绣,但是沒有去驗(yàn)證灰殴!

  • 驗(yàn)證的原理也非常簡單:生成驗(yàn)證碼后敬特,把驗(yàn)證碼的數(shù)據(jù)存進(jìn)Session域?qū)ο笾校袛嘤脩糨斎腧?yàn)證碼是否和Session域?qū)ο蟮臄?shù)據(jù)一致牺陶。

  • 生成驗(yàn)證碼圖片伟阔,并將驗(yàn)證碼存進(jìn)Session域中

        //在內(nèi)存中生成圖片
        BufferedImage bufferedImage = new BufferedImage(80, 20, BufferedImage.TYPE_INT_RGB);

        //獲取到這張圖片
        Graphics2D graphics2D = (Graphics2D) bufferedImage.getGraphics();

        //設(shè)置背景色為白色
        graphics2D.setColor(Color.white);
        graphics2D.fillRect(0, 0, 80, 20);

        //設(shè)置圖片的字體和顏色
        graphics2D.setFont(new Font(null, Font.BOLD, 20));
        graphics2D.setColor(Color.BLUE);

        //生成隨機(jī)數(shù)
        String randomNum = makeNum();

        //往這張圖片上寫數(shù)據(jù),橫坐標(biāo)是0,縱坐標(biāo)是20
        graphics2D.drawString(randomNum, 0, 20);

        //將隨機(jī)數(shù)存進(jìn)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());
  • 生成隨機(jī)數(shù)的方法:

    private String makeNum() {

        Random random = new Random();

        //生成0-6位的隨機(jī)數(shù)
        int num = random.nextInt(999999);

        //驗(yàn)證碼的數(shù)位全都要6位數(shù)掰伸,于是將該隨機(jī)數(shù)轉(zhuǎn)換成字符串皱炉,不夠位數(shù)就添加
        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();

    }
  • jsp顯示頁面

<form action="/ouzicheng/Login2Servlet">

    用戶名:<input type="text" name="username"><br>
    密碼:<input type="password" name="password"><br>
    驗(yàn)證碼:<input type="text" name="randomNum">
    <img src="/ouzicheng/ImageServlet" ><br><br>

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

</form>
  • 處理提交表單數(shù)據(jù)的Servlet,判斷用戶帶過來驗(yàn)證碼的數(shù)據(jù)是否和Session的數(shù)據(jù)相同狮鸭。

        //獲取用戶輸入驗(yàn)證碼的數(shù)據(jù)
        String client_randomNum = request.getParameter("randomNum");

        //獲取Session中的數(shù)據(jù)
        String session_randomNum = (String) request.getSession().getAttribute("randomNum");

        //判斷他倆數(shù)據(jù)是否相等合搅,用戶是否有輸入驗(yàn)證碼,Session中是否為空
        if (client_randomNum == null || session_randomNum == null || !client_randomNum.equals(session_randomNum)) {
            System.out.println("驗(yàn)證碼錯(cuò)誤了F缃丁T植俊!");
            return ;
        }

        //下面就是驗(yàn)證用戶名和密碼...................
  • 顯示頁面是這樣子的
image
  • 我們來看一下效果惯退!
image

對于校驗(yàn)碼實(shí)現(xiàn)思路是這樣子的:

  • 使用awt語法來描寫一張驗(yàn)證碼赌髓,生成隨機(jī)數(shù)保存在seesion域中,我們讓驗(yàn)證碼不能緩存起來【做到驗(yàn)證碼都不一樣】
  • 頁面直接訪問Servlet來獲取我們的驗(yàn)證碼催跪,于是我們驗(yàn)證碼的值就會(huì)改變【同時(shí)session的值也會(huì)被改變】
  • 當(dāng)用戶驗(yàn)證的時(shí)候锁蠕,就是session內(nèi)的值的驗(yàn)證了。

Session和Cookie的區(qū)別

  • 從存儲(chǔ)方式上比較
    • Cookie只能存儲(chǔ)字符串懊蒸,如果要存儲(chǔ)非ASCII字符串還要對其編碼荣倾。
    • Session可以存儲(chǔ)任何類型的數(shù)據(jù),可以把Session看成是一個(gè)容器
  • 從隱私安全上比較
    • Cookie存儲(chǔ)在瀏覽器中骑丸,對客戶端是可見的逃呼。信息容易泄露出去。如果使用Cookie者娱,最好將Cookie加密
    • Session存儲(chǔ)在服務(wù)器上抡笼,對客戶端是透明的。不存在敏感信息泄露問題黄鳍。
  • 從有效期上比較
    • Cookie保存在硬盤中推姻,只需要設(shè)置maxAge屬性為比較大的正整數(shù),即使關(guān)閉瀏覽器框沟,Cookie還是存在的
    • Session的保存在服務(wù)器中藏古,設(shè)置maxInactiveInterval屬性值來確定Session的有效期增炭。并且Session依賴于名為JSESSIONID的Cookie,該Cookie默認(rèn)的maxAge屬性為-1拧晕。如果關(guān)閉了瀏覽器隙姿,該Session雖然沒有從服務(wù)器中消亡,但也就失效了厂捞。
  • 從對服務(wù)器的負(fù)擔(dān)比較
    • Session是保存在服務(wù)器的输玷,每個(gè)用戶都會(huì)產(chǎn)生一個(gè)Session,如果是并發(fā)訪問的用戶非常多靡馁,是不能使用Session的欲鹏,Session會(huì)消耗大量的內(nèi)存。
    • Cookie是保存在客戶端的臭墨。不占用服務(wù)器的資源赔嚎。像baidu、Sina這樣的大型網(wǎng)站胧弛,一般都是使用Cookie來進(jìn)行會(huì)話跟蹤尤误。
  • 從瀏覽器的支持上比較
    • 如果瀏覽器禁用了Cookie,那么Cookie是無用的了结缚!
    • 如果瀏覽器禁用了Cookie损晤,Session可以通過URL地址重寫來進(jìn)行會(huì)話跟蹤。
  • 從跨域名上比較
    • Cookie可以設(shè)置domain屬性來實(shí)現(xiàn)跨域名
    • Session只在當(dāng)前的域名內(nèi)有效掺冠,不可夸域名

Cookie和Session共同使用

  • 如果僅僅使用Cookie或僅僅使用Session可能達(dá)不到理想的效果沉馆。這時(shí)應(yīng)該嘗試一下同時(shí)使用Session和Cookie

  • 那么,什么時(shí)候才需要同時(shí)使用Cookie和Session呢德崭?

  • 在上一篇博客中斥黑,我們使用了Session來進(jìn)行簡單的購物,功能也的確實(shí)現(xiàn)了∶汲現(xiàn)在有一個(gè)問題:我在購物的途中锌奴,不小心關(guān)閉了瀏覽器。當(dāng)我再返回進(jìn)去瀏覽器的時(shí)候憾股,發(fā)現(xiàn)我購買過的商品記錄都沒了B故瘛!為什么會(huì)沒了呢服球?原因也非常簡單:服務(wù)器為Session自動(dòng)維護(hù)的Cookie的maxAge屬性默認(rèn)是-1的茴恰,當(dāng)瀏覽器關(guān)閉掉了,該Cookie就自動(dòng)消亡了斩熊。當(dāng)用戶再次訪問的時(shí)候往枣,已經(jīng)不是原來的Cookie了。

  • 我們現(xiàn)在想的是:即使我不小心關(guān)閉了瀏覽器了,我重新進(jìn)去網(wǎng)站分冈,我還能找到我的購買記錄圾另。

  • 要實(shí)現(xiàn)該功能也十分簡單,問題其實(shí)就在:服務(wù)器為Session自動(dòng)維護(hù)的Cookie的maxAge屬性是-1雕沉,Cookie沒有保存在硬盤中集乔。我現(xiàn)在要做的就是:把Cookie保存在硬盤中,即使我關(guān)閉了瀏覽器坡椒,瀏覽器再次訪問頁面的時(shí)候扰路,可以帶上Cookie,從而服務(wù)器識(shí)別出Session肠牲。

  • 第一種方式:只需要在處理購買頁面上創(chuàng)建Cookie幼衰,Cookie的值是Session的id返回給瀏覽器即可


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

  • 第二種方式: 在server.xml文件中配置靴跛,將每個(gè)用戶的Session在服務(wù)器關(guān)閉的時(shí)候序列化到硬盤或數(shù)據(jù)庫上保存缀雳。但此方法不常用,知道即可梢睛!

  • 下面看一下效果

image

如果文章有錯(cuò)的地方歡迎指正肥印,大家互相交流。習(xí)慣在微信看技術(shù)文章的同學(xué)绝葡,可以關(guān)注微信公眾號(hào):Java3y

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末深碱,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子藏畅,更是在濱河造成了極大的恐慌敷硅,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,126評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件愉阎,死亡現(xiàn)場離奇詭異绞蹦,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)榜旦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評論 2 382
  • 文/潘曉璐 我一進(jìn)店門幽七,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人溅呢,你說我怎么就攤上這事澡屡。” “怎么了咐旧?”我有些...
    開封第一講書人閱讀 152,445評論 0 341
  • 文/不壞的土叔 我叫張陵驶鹉,是天一觀的道長。 經(jīng)常有香客問我铣墨,道長室埋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,185評論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮词顾,結(jié)果婚禮上八秃,老公的妹妹穿的比我還像新娘。我一直安慰自己肉盹,他們只是感情好昔驱,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著上忍,像睡著了一般骤肛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上窍蓝,一...
    開封第一講書人閱讀 48,970評論 1 284
  • 那天腋颠,我揣著相機(jī)與錄音,去河邊找鬼吓笙。 笑死淑玫,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的面睛。 我是一名探鬼主播絮蒿,決...
    沈念sama閱讀 38,276評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼叁鉴!你這毒婦竟也來了土涝?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,927評論 0 259
  • 序言:老撾萬榮一對情侶失蹤幌墓,失蹤者是張志新(化名)和其女友劉穎但壮,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體常侣,經(jīng)...
    沈念sama閱讀 43,400評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡蜡饵,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了袭祟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片验残。...
    茶點(diǎn)故事閱讀 37,997評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖巾乳,靈堂內(nèi)的尸體忽然破棺而出您没,到底是詐尸還是另有隱情,我是刑警寧澤胆绊,帶...
    沈念sama閱讀 33,646評論 4 322
  • 正文 年R本政府宣布氨鹏,位于F島的核電站,受9級(jí)特大地震影響压状,放射性物質(zhì)發(fā)生泄漏仆抵。R本人自食惡果不足惜跟继,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望镣丑。 院中可真熱鬧舔糖,春花似錦、人聲如沸莺匠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,204評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽趣竣。三九已至摇庙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間遥缕,已是汗流浹背卫袒。 一陣腳步聲響...
    開封第一講書人閱讀 31,423評論 1 260
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留单匣,地道東北人夕凝。 一個(gè)月前我還...
    沈念sama閱讀 45,423評論 2 352
  • 正文 我出身青樓,卻偏偏與公主長得像封孙,于是被迫代替她去往敵國和親迹冤。 傳聞我的和親對象是個(gè)殘疾皇子讽营,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 從三月份找實(shí)習(xí)到現(xiàn)在虎忌,面了一些公司,掛了不少橱鹏,但最終還是拿到小米膜蠢、百度、阿里莉兰、京東挑围、新浪、CVTE糖荒、樂視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,184評論 11 349
  • 一杉辙、cookie機(jī)制和session機(jī)制的區(qū)別**********************************...
    __Lex閱讀 1,703評論 0 13
  • 2017.12.18日天氣晴星期一,第二十二篇捶朵。 夜在繼續(xù)降臨蜘矢,前天和昨天的日記剛剛補(bǔ)完,今天繼續(xù)把今天的趕上综看,昨...
    二六班王佳欣閱讀 199評論 0 0
  • 一朵花 長在陽光之陰 光與影 遮到了腰上 即使跌入谷底 也不會(huì)有一滴眼淚 當(dāng)綠瘦紅肥 鮮艷了花瓣 繽紛飄飄 零落成...
    花霧醉秋閱讀 134評論 0 1
  • 大家都知道斐波那契數(shù)列品腹,現(xiàn)在要求輸入一個(gè)整數(shù)n,請你輸出斐波那契數(shù)列的第n項(xiàng)红碑。n<=39 一只青蛙一次可以跳上1級(jí)...
    極速魔法閱讀 351評論 0 0