java開發web前端cookie session及token會話機制詳解

而web系統開發者就好比一個醫術精湛的醫生,醫生需要十分清楚人體的經絡和血液流向才能對癥下藥,而web系統開發者需要十分清楚cookie、session機制,才能迅速解決疑難BUG,開發出更好的web系統。————胡說

引入:

我們都知道http協議本身是一種無狀態的協議,一個普通的請求大致分為三步:

1、客戶端發送請求給服務器

2、服務器處理該請求

3、服務器將處理結果響應該客戶端。

之後該客戶端再次向該服務區發送請求後,服務器端並不能知道這兩個請求是否是同一個瀏覽器或用戶發出來的。所以作為web服務器必須能夠采用某種方式來唯一識別同一個用戶,並記錄該用戶的狀態。而這同一個客戶端與服務器端的在一段時間內的多次交互,我們就可以稱該客戶端為該服務器端的一個客戶端會話窗口,有瞭會話窗口,我們就能確定哪個請求是哪個用戶發出的瞭,從而可以實現會話跟蹤,並記錄用戶的行為。

概念:

會話:可以理解為用戶打開瀏覽器,訪問該web服務器的多個資源,然後關閉瀏覽器,這中間的一系列過程稱之為一個會話。

有狀態的會話:瀏覽器發送的每一次請求,每一個會話都要有唯一的標識來唯一標識自己,當瀏覽器發送請求的時候就帶上這個標識來讓服務器識別,從而實現有“狀態”的會話。

javaweb中有兩種實現會話的機制:

1)Cookie機制

2)Session機制

PS:cookie和session是瀏覽器與服務器交互的一種規范,有專門的的組織對該規范進行定義,隻要瀏覽器或服務器遵守瞭該規范,我們就能使用cookie和session。其他能做web開發的高級語言也有,隻是實現方式不同罷瞭。

一、cookie機制

1、基本介紹

1)cookie機制采用的是在客戶端保持 HTTP 狀態信息的方案。當瀏覽器訪問WEB服務器的某個資源時,WEB服務器會在HTTP響應頭中添加一個鍵值對傳送給瀏覽器,再由瀏覽器將該cookie放到客戶端磁盤的一個文件中,該文件可理解為cookie域(鍵值對的集合),往後每次訪問某個網站時,都會在請求頭中帶著這個網站的所有cookie值。(至於怎麼區分不同網站的cookie的,很簡單,每個網站都給他一個唯一標識比如網址等,每次打開某網址時,就查詢該網站下的所有cookie值即可。)

2)每一個cookie都有一個name和一個value,且name是唯一的。相同名字時,後者會覆蓋掉前者(類似哈希表的key的效果)。

3)一個WEB瀏覽器也可以存儲多個WEB站點提供的Cookie。瀏覽器一般隻允許存放300個Cookie,每個站點最多存放20個Cookie,每個Cookie的大小限制為4KB。

2、分類

1)會話級別的cookie

默認情況下它是一個會話級別的cookie,存儲在瀏覽器的內存中,用戶退出瀏覽器之後被刪除。

2)持久化的cookie

若希望瀏覽器將該cookie存儲在磁盤上,則需要設置該cookie的生命周期setMaxAge,並給出一個以秒為單位的時間。將最大時效設為0則是命令瀏覽器刪除該cookie。

3、cookie的作用域

cookie的domain和path屬性定義瞭cookie的作用范圍,即訪問哪些網站或url時,會自動的帶著該cookie。domain即域名,默認是當前主機(不包括子域名),path默認是*(所有路徑),即域名後面的的路徑。大部分情況下我們都是使用默認的設置即可。

4、基本原理

當一個瀏覽器訪問某web服務器時,web服務器會調用HttpServletResponse的addCookie()方法,在響應頭中添加一個名叫Set-Cookie的響應字段用於將Cookie返回給瀏覽器,當瀏覽器第二次訪問該web服務器時會自動的將該cookie回傳給服務器,來實現用戶狀態跟蹤。

5、常用API

javax.servlet.http.Cookie類來封裝Cookie信息,它包含有生成Cookie信息和提取Cookie信息的各個屬性的方法。

1)public Cookie(String name,String value)

2)setMaxAge(int longTime)與getMaxAge方法:設置和獲取cookie的最大有效時長。setMaxAge(0) 表示刪除磁盤上的某個cookie。

註意:

cookie沒有提供修改方法,當name一樣時,覆蓋原來的就算是更新瞭。

刪除也是,setMaxAge(0),當name一樣時,原來的會被覆蓋掉,新建的沒有生命周期,也會被立馬刪除。

3)setPath與getPath方法 :設置或讀取Cookie的作用范圍。

4)HttpServletResponse接口中定義瞭一個addCookie(Cookie cookie)方法,它用於在發送給瀏覽器的HTTP響應消息中增加一個Set-Cookie響應頭字段。

5)HttpServletRequest接口中定義瞭一個getCookies方法,它用於從HTTP請求消息的Cookie請求頭字段中讀取所有的Cookie項。

6)getName方法 :獲取到cookie的name。

7)setValue(String value)與getValue方法:設置和獲取cookie的value。

6、基本應用

自動登錄、跟蹤用戶上次訪問站點的時間、顯示最近瀏覽信息等。

這是我之前寫的一個使用cookie進行自動登錄的服務器代碼,很早瞭都。

     //如果cookie中有customer信息,就放到session中
     boolean checkCustomerCookie(HttpServletRequest request) throws UnsupportedEncodingException {
        Cookie[] cookies = request.getCookies();
         if (cookies != null) {
            for (Cookie cookie : cookies) {
                String cookieName = cookie.getName();
                //如果有,解密後拿cookie中的值和數據庫中的值進行比較
                if (Constant.cookieCustomerKey.getName().equals(cookieName)){
                    String cookieValue = cookie.getValue();
                    String decry = EncrypUtils.Base64Util.decry(cookieValue);
                    Customer customer1 = JsonUtils.stringToObject(decry, Customer.class);
                    Customer customer2 = customerService.checkLogin(customer1.getPhoneNumber(), customer1.getPassword());
                    if (customer2 != null){
                        //放入到session中,放行
                        request.getSession().setAttribute("customer",customer2);
                        //自動登錄時,更新用戶的在線狀態
                        Customer onlineCustomer = new Customer();
                        onlineCustomer.setId(customer2.getId());
                        onlineCustomer.setOnlineStatus(String.valueOf(Constant.ONLINESTATUS.getCode()));
                        customerService.updateById(onlineCustomer);
                        return true;
                    }
                }
            }
         }
        return false;
    }

7、Cookie中存儲中文問題

cookie中存儲中文會出現中文亂碼,需要對value進行額外的編碼

1、base64編碼

存儲:Base64.getEncoder().encodeToString(content.getBytes(“utf-8”));

讀取:new String(Base64.getDecoder().decode(cookie.getValue()),”utf-8″)

2、URLEncoder類

存儲:Cookie cookie = new Cookie(“userName”, URLEncoder.encode(“你好世界”, “UTF-8”));

讀取:URLDecoder.decode(cookie.getValue(), “UTF-8”)

二、session機制

1、基本介紹

session機制采用的是在服務器端保持 HTTP 狀態信息的方案。為瞭加速session的讀取和存儲,web服務器中會開辟一塊內存用來保存服務器端所有的session,每個session都會有一個唯一標識sessionid,根據客戶端傳過來的jsessionid(cookie中),找到對應的服務器端的session。為瞭防止服務器端的session過多導致內存溢出,web服務器默認會給每個session設置一個有效期, (30分鐘)若有效期內客戶端沒有訪問過該session,服務器就認為該客戶端已離線並刪除該session。

保存sessionID的方式

1)cookie中

通過一個特殊的cookie,name為JSESSIONID,value為服務器端某個 session的ID,默認的方式。但是當瀏覽器禁用cookie後session就會失效。

2)url重寫

當瀏覽器Cookie被禁時用。

就是把session的id附加在URL路徑的後面。附加的方式也有兩種,一種是作為URL路徑的附加信息,另一種是作為查詢字符串附加在URL後面。

做法:

1、response.encodeURL(String url)用於對表單action和超鏈接的url地址進行重寫

2、response.encodeRedirectURL(String url) 用於對sendRedirect方法後的url地址進行重寫。

這兩個方法很智能,若瀏覽器禁用瞭cookie,就默認會進行url重寫(url中帶上sessionid),當用戶瀏覽器沒有禁用cookie時,就不在URL後附加sessionid。

用法就是代替response.sendRedirect(String url)。、

2、基本原理

當用戶發送一個請求到服務器端時,服務器會先檢查請求中是否含有sessionid(存在cookie中或者在url中),

》》如果不存在sessionid(說明是第一次請求),就會為該請求用戶創建一個session對象,並將該session對象的sessionid(放到響應頭的set-cookie中,格式set-cookie:sessionid,下次再請求時cookie中就會有一個name為jsessionid的cookie,value就是sessionid)響應給客戶端。

》》如果存在sessionid,就會在服務器端查找是否有該sessionid對應的session,如果有就使用,沒有就創建一個。

所以說,服務器端的session和客戶端的cookie是息息相關的,若是沒有瞭cookie,又不做其他處理的話,服務器端的session也沒瞭。

3、常用API

getId()方法:得到sessionid。

invalidate()方法:讓session立刻失效。

getAttribute(String key):根據key獲取該session中的value。

setAttribute(String key,Object value):往session中存放key-value。

removeAttribute(Stringkey):根據key刪除session中的key-value。

getServletContext():得到ServletContext。

setMaxInactiveInterval(long timeout)/getMaxInactiveInterval:設置/獲取session的最大有效時間。

getCreationTime方法:獲取session的創建的時間。

getLastAccessedTime方法:獲取session最後一次訪問的時間。

getSession():從HttpServletRequest中獲取session。

4、基本應用 跨瀏覽器的會話跟蹤

因為cookie在多個瀏覽器之間是共享的(但是不能跨域),所以可以將sessionid存在cookie中,再把cookie存入磁盤中,然後在其他瀏覽器中再次訪問該服務器時,就會讀取到cookie中的sessionid,從而回到上次訪問的頁面瞭。

一段示例代碼:

session.setMaxInactiveInterval(2*3600);//session 保存倆小時
Cookie cookie=new Cookie("JSESSIONID",session.getId());//sessionid放到cookie中
cookie.setMaxAge(2*3600);//客戶端的cookie也保存倆小時 
cookie.setPath("/");//cookie作用范圍設為整個項目
response.addCookie(cookie);//給瀏覽器返回該Cookie

5、常見問題

1、關閉瀏覽器後cookie會消失嗎?

答:看情況。

經過上面關於cookie的分析之後我們知道,cookie默認是存在於瀏覽器內存中的,若此時cookie沒有持久化,瀏覽關閉後cookie會消失;若此時cookie進行瞭持久化,瀏覽器關閉後cookie不會消失。

2、關閉瀏覽器後session會消失嗎?

答:看起來會實際上不會。

這個問題需要從以下兩個方面考慮:

1)從服務器端考慮

我們知道session是存在於服務器端內存中的,和瀏覽器沒有關系,所以瀏覽器關閉後,服務器端的session不會消失。(除非服務器重啟或session達到瞭過期時間)

2)從瀏覽器端考慮

我們知道瀏覽器是根據cookie中的jesessionid值來唯一找到服務器端的session的,此時若cookie沒有持久化,瀏覽器關閉後cookie也跟著消失。所以當用戶再次打開瀏覽器後,由於沒有瞭cookie中的jesessionid,自然也無法唯一找到服務器端的session,對用戶來說,確實是瀏覽器關閉後再次打開就無法找到上次的會話瞭,誤以為是關閉瀏覽器後服務器端的session也跟著消失,其實還在。

三、token

1、token是啥?

token,可以翻譯成”令牌”,本質上它是一個全局唯一的字符串,用來唯一識別一個客戶端。但它不像cookie和session一樣是一種web規范,個人認為他是借鑒瞭cookie和session工作的原理,進而延伸出來的一種維持用戶會話狀態的機制。

2、token解決瞭什麼問題?

token解決瞭session依賴於單個Web服務器的問題。單體應用時用戶的會話信息保存在session中,session存在於服務器端的內存中,由於前前後後用戶隻針對一個web服務器,所以沒啥問題。但是一到瞭web服務器集群的環境下(我們一般都是用Nginx做負載均衡,若是使用瞭輪詢等這種請求分配策略),就會導致用戶小a在A服務器登錄瞭,session存在於A服務器中,但是第二次請求被分配到瞭B服務器,由於B服務器中沒有用戶小a的session會話,導致用戶小a還要再登陸一次,以此類推。這樣用戶體驗很不好。當然解決辦法也有很多種,比如同一個用戶分配到同一個服務處理、使用cookie保持用戶會話信息等。

我們今天討論的是用戶會話信息集中存儲的這種方案。類比之前cookie和session的機制,在請求中根據cookie中的jesessionid來自動找到服務器端的session,從而從session中取出當前用戶的會話信息。

Session session = request.getSession();// 獲取session
session.getAttribute("user") // 獲取session中的用戶信息

3、我們可以模擬cookie和session的這種機制:

①、cookie中是根據jesessionid來找到服務器端的session的,jesessionid就是一個全局唯一的隨機字符串,我們也可以生成一個全局唯一的字符串比如使用UUID或時間戳加隨機字符串的形式;

②、web服務器在內存中存儲所有的session,每個session都有一個唯一的id標識,value就是session本身,session裡面又有好多鍵值對。數據在內存中、key-value形式的、value裡面又有好多鍵值對,這簡直就是對redis的哈希表十分準確的描述啊,所以我們可以使用redis的哈希類型來模型服務器端的session。

③、有瞭客戶端的隨機字符串,有瞭服務器端的會話信息存儲,接下來就讓他們匹配起來就完事瞭,next:

cookie和session機制中是根據cookie中的jesessionid來自動找到服務器端的session,說是自動查找,其實是我們調用瞭他封裝好的方法request.getSession()獲取的,這個request中就包含瞭本次請求的所有信息,而調用的getSession()方法中,肯定做瞭這件事——獲取請求頭中cookie的jesessionid的值,根據這個值到服務器的內存中找到對應的session並返回。所以我們也完全可以模仿它這種機制:我們可以在用戶第一次請求該web服務器時或是用戶登錄該web服務器時,生成一個全局唯一的token返回給前端存儲,同時將該用戶信息存到redis中並設置有效期,之後每次請求中都在請求頭中帶著這個token,服務器端根據這個token到redis中查找對應的用戶信息,即得到瞭我們所說的 “session”。

客戶端的token我們可以這樣傳:

    $.ajax({
           headers:{"token":localStorage.getItem('token')},
           type: 'get',
           url:'/xxx/xxx/xxx',
           dataType: 'json',
           success: function(we) {
                // some code
             });
           },

服務器端的用戶信息我們可以這樣獲取:

 /**
  * 返回當前用戶
  * @return
  */
public User getCurrUser(){
    ServletRequestAttributes servletRequestAttributes = 
    (ServletRequestAttributes)RequestContextHolder.getRequestAttributes();
    String token = servletRequestAttributes.getRequest().getHeader("token");
    String strUser = RedisUtils.get(token);
   return JsonUtils.stringToObject(strUser,User.class);
}

ok,關於cookie、session和token暫時就這麼多,能看到這兒也不容易,點個贊或評論一下再走,順便給自己也加點經驗值。 這種雙贏的事情以後要多做啊~希望大傢以後多多支持WalkonNet!

推薦閱讀: