cookie机制 + java 案例

简介: cookie机制 + java 案例


为什么会有cookie??

       cookie是浏览器在本地存储数据的一种机制首先我们的数据很多都是存放在服务器上的,但是服务端也想这能不能在客户端也存放一些数据。但是又要保证本地的安全,不能让浏览器直接控制本地的存储设备,所以就做了一个中立的做法,也就是说让浏览器开一个口子,使用某些特殊的键值对,来限定数据的存储保证安全,同时又可以让浏览器往本地存储数据。

cookie从哪里来的??

       cookie是从服务器来的。服务器在响应中会带有Set-Cookie字段,通过这个字段就可以把要保存在浏览器本地的数据给返回回去。

cookie到哪里去??

       后续浏览器访问服务器的时候,就会把当前本地的所有cookie都通过http带给服务端

cookie有啥用??

       最典型的场景就是使用cookie保存当前用户的登录状态。就例如我们经常访问b站的时候,只登录了一次,后面关闭页面再次打开,任然保持这上次的登录状态。

       在cookie保存用户身份标识,这样的应用场景之中,此时身份标识该如何分配,以及身份信息该如何存储,都是需要服务器的支持的。这个就要利用一个session会话机制。

session

       session给当前用户分配一个sessionId。同时记录下当前用户的身份信息(可以自定义的),这个id就会被返回到浏览器的cookie中,后续浏览器访问服务器都会带着这个。从而能让服务器识别到当前用户的身份。

下面我们结合代码,来了解一下cookie的机制。

HttpServletRequest类中的相关方法

方法 描述
HttpSession  getSession() 在服务器中获取会话,如果参数为true,则当前不存在会话时会创建新会话,如果参数为false,则当不存在的时候会返回null
Cookie[ ] getCookies() 返回一个数组,包含客户端发送该请求的所有cookie对象,会自动把cookie中的格式解析成为键值对

       下面我们来解析一下这个getSession的用法,首先getSession的参数如果为false,那么就会触发以下流程:

  1. 读取cookie中的sessionid
  2. 然后再服务器这边根据服务器存储的数据和sessionid一起,查询Session对象
  3. 如果查到了就会直接返回这个session对象
  4. 否则返回null

       如果为true的话,与false唯一不同就只有第四步不一样,如下:

  1. 读取cookie中的sessionid
  2. 然后再服务器这边根据服务器存储的数据和sessionid一起,查询Session对象
  3. 如果查到了就会直接返回这个session对象
  4. 否则返回创建一个新的session对象,同时生成一个sessionid,同时以sessionid为key ,以session对象为value构成一个键值对,然后把这个键值对存储到服务器的hash表里面
  5. 同时把sessionid以setcookie 的方式返回给浏览器

       这个对我们实现登录功能非常实用。

简单的实现cookie登录功能

        接下来我们基于httpServlet来实现一个简单的登录功能。

       首先需要提供两个页面:

  1. 登录页(输入用户id,输入密码,然后还有一个登录按钮,点击登录就会发起一个http请求,服务器就会处理这个http请求,服务器处理这个请求的时候就会验证用户名和密码,如果用户名和密码都ok,就会跳转主页)
  2. 主页(不存在任何业务逻辑,仅仅只是展示一个欢迎页面)

       其中的登陆页面就只是单纯的html页面,还需要写一个servlet,实现登录的时候用户名密码校验,还要写一个servlet来生成主页

实现登录页面

       首先先来回忆一下我们传输键值对的方式,一说到传输键值对,那么肯定就要提到html里面的form表单

    <form action="login" method="post">
        <input type="text" name="username"> <!--用户输入的用户名-->
        <br>
        <input type="password" name="password">  <!--用户输入的密码-->
        <br>
        <input type="submit" value="登录">
    </form>

       form会组织这里的数据,以键值对的形式提交给服务器,其中key就是input的name属性,value就是input中用户输入的内容,最终会构造post请求,在body中以键值对的形式,进行组织。服务器通过getParameter方法来获取指定key的value,具体getParameter的方法可以参看我前面的servelet的内容。

       完整的登录页面的代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="login" method="post">
        <input type="text" name="username"> <!--用户输入的用户名-->
        <br>
        <input type="password" name="password">  <!--用户输入的密码-->
        <br>
        <input type="submit" value="登录">
    </form>
</body>
</html>

实现servlet逻辑

        新建一个类,为loginServlet类来处理上述的登录请求。

       登录的请求形式如下:

  • POST/login
  • Content-Type:application/x-www-form-urlencoded
  • username=zhangshan&password=1233

        一般像登录这样的请求都是post

构造好的登录的处理逻辑如下:

package Login;
 
import javax.jws.WebService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
 
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        resp.setContentType("text/html;charset=utf8");
        // 首先通过getParameter方法来获取对应key的value值
        String username = req.getParameter("name");
        String password = req.getParameter(("password"));
        // 然后来验证这个用户和密码是否正确
        if (username == null || password == null || username.equals("") || password.equals("")){
            resp.getWriter().write("当前用户名和密码不能为空");
            return;
        }
        // 假设此处的密码和用户名只能是zhangsan和1234
        // 逻辑上此处应该是在服务器的数据库里面去查找验证用户名和密码,此处简化步骤
        if (username.equals("zhangsan") || username.equals("lisi")) {
            if (password.equals("1234")) {
                // 此时用户输入密码正确
            } else {
                // 用户密码有误
                resp.getWriter().write();
            }
        } else {
            // 用户名错误
        }
    }
}

这是简单的登录逻辑。

       接下来,如果登录的用户名和密码验证正确的话,就会触发session会话,如下:

        // 此时用户属于未登录的状态,所以请求的cookie中没有sessionid,所以无法从服务器的hash表中获取到对应的session对象
        // 所以此处吧getSession中的参数设置为true,(在查询不到的时候创建新的sesion会话和session对象并存储到hash表中)
        // 同时会返回这个session对象,并且接下来会将新的sessionid通过响应http返回给客户端浏览器。
        HttpSession session = req.getSession(true);
 
        // 然后让刚刚创建好的session对象存储我们自定义的数据,就可以在这个对象中存储用户的身份信息
        session.setAttribute("username",username);

       session对象本身就可以看作一个hash表,通过setAttribute方法来存储键值对(setAttribute方法里面有两个参数,一个是String(key),一个是Object(value)),后续就可以通过getAttribute根据key来获取value。

       最后登录成功之后跳转到主页:

// 登录成功之后 需要自动跳转到 主页欢迎页
        resp.sendRedirect("index");

       下面是loginServlet的代码:

package Login;
 
import javax.jws.WebService;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
 
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        req.setCharacterEncoding("utf8");
        resp.setContentType("text/html;charset=utf8");
        // 首先通过getParameter方法来获取对应key的value值
        String username = req.getParameter("name");
        String password = req.getParameter(("password"));
        // 然后来验证这个用户和密码是否正确
        if (username == null || password == null || username.equals("") || password.equals("")){
            resp.getWriter().write("当前用户名和密码不能为空");
            return;
        }
        // 假设此处的密码和用户名只能是zhangsan和1234
        // 逻辑上此处应该是在服务器的数据库里面去查找验证用户名和密码,此处简化步骤
        if (username.equals("zhangsan") || username.equals("lisi")) {
            if (password.equals("1234")) {
                // 此时用户输入密码正确
            } else {
                // 用户密码有误
                resp.getWriter().write();
            }
        } else {
            // 用户名错误
        }
        // 此时用户属于未登录的状态,所以请求的cookie中没有sessionid,所以无法从服务器的hash表中获取到对应的session对象
        // 所以此处吧getSession中的参数设置为true,(在查询不到的时候创建新的sesion会话和session对象并存储到hash表中)
        // 同时会返回这个session对象,并且接下来会将新的sessionid通过响应http返回给客户端浏览器。
        HttpSession session = req.getSession(true);
 
        // 然后让刚刚创建好的session对象存储我们自定义的数据,就可以在这个对象中存储用户的身份信息
        session.setAttribute("username",username);
 
        // 登录成功之后 需要自动跳转到 主页欢迎页
        resp.sendRedirect("index");
    }
}

实现生成主页

        首先需要验证身份信息:

HttpSession session = req.getSession(false);
        if (session == null ) {
            // 当前是未登录状态
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前用户未登录");
            return;
        }

       此处的查询到的sessiom对象应该是和刚才登录的session对象是同一个对象,因为是同一个sessionid。刚才登录成功,sessionid就会通过Set-Cookie返回给浏览器,浏览器下次访问IndexServlet的时候,就会带上这个同一个sessionid。

package Login;
 
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.io.IOException;
 
@WebServlet("/index")
public class IndexServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 此处禁止创建新的会话,如果没有找到,那么就认为此用户为未登录状态
        // 如果找到了才认为是登录状态
        HttpSession session = req.getSession(false);
        if (session == null ) {
            // 当前是未登录状态
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前用户未登录");
            return;
        }
        String username = (String)session.getAttribute("username");
        // 在服务器这边维护了一个全局的hash表,key就是sessionid,value就是session对象
        if (username == null) {
            resp.setContentType("text/html;charset=utf8");
            resp.getWriter().write("当前用户未登录");
            return;
        }
        // 如果都ok就生成动态主页
        resp.setContentType("text/html;charset=utf8");
        resp.getWriter().write("欢迎你!!"+ username);
    }
}

部署tomcat服务器

        选择edit configuration:

       添加smart tomcat选项:

        设置端口8080(默认),重新命名,然后启动:

访问登录页面

       访问: http//127.0.0.1:8080/login/login.html

       输入“zhangsan”,1234

        就会收到浏览器请求保存cookie的请求。点击明白即可,我们点击编辑可以查看到我们的用户名和密码都已经保存到本地了。

       下面使用费德勒来抓包看看我们信息传递的过程,如下:

       输入用户名和密码之后就会构造一个http请求访问login的页面,随后访问index页面

        抓包的结果可以看出来,第一次访问的时候是没有cookie的,后面setCookie之后就有了。

       第二次请求index的请求如下:

       可以看到最后的存在一个Cookie字段

         

       下次无论你怎么刷新,都是显示“欢迎你!!zhangsan” 。

问题

       上面的sessionid也不一定会一直存在,比如说服务器重新启动,session的hash表就会清除,此时如果再访问就可能出现sessionid无法查询到,此时还是未登录状态,需要重新登录。

       服务器默认保存会话是再内存中的,一但重启服务器会话服务就会销毁,但是smart tomcat为了方便程序员方便调试程序,会在停止服务器的时候,把会话持久化保存,下次启动的时候自动恢复到内存中,这个时候会话还是不会丢失的(这个时候真的有效,跟tomcat版本有关)。



目录
相关文章
|
1天前
|
Java 数据安全/隐私保护
深入剖析:Java Socket编程原理及客户端-服务器通信机制
【6月更文挑战第21天】Java Socket编程用于构建网络通信,如在线聊天室。服务器通过`ServerSocket`监听,接收客户端`Socket`连接请求。客户端使用`Socket`连接服务器,双方通过`PrintWriter`和`BufferedReader`交换数据。案例展示了服务器如何处理每个新连接并广播消息,以及客户端如何发送和接收消息。此基础为理解更复杂的网络应用奠定了基础。
|
1天前
|
Java
并发编程的艺术:Java线程与锁机制探索
【6月更文挑战第21天】**并发编程的艺术:Java线程与锁机制探索** 在多核时代,掌握并发编程至关重要。本文探讨Java中线程创建(`Thread`或`Runnable`)、线程同步(`synchronized`关键字与`Lock`接口)及线程池(`ExecutorService`)的使用。同时,警惕并发问题,如死锁和饥饿,遵循最佳实践以确保应用的高效和健壮。
8 2
|
4天前
|
监控 算法 Java
深入理解Java虚拟机:垃圾收集机制的奥秘
【6月更文挑战第17天】在Java的世界,垃圾收集(GC)是保持内存健康不可或缺的一环。本文将揭开JVM垃圾收集的神秘面纱,探索其原理、算法及调优策略,帮助开发者更好地理解和掌握这一关键技术,确保Java应用的性能与稳定性。
19 5
|
6天前
|
存储 安全 Java
深入探讨 Java 封装机制:为何它是面向对象编程的核心?
【6月更文挑战第16天】Java的封装是OOP核心,它将数据和操作数据的方法打包在类中,隐藏实现细节并提供公共接口。例如,`Student`类封装了私有属性`name`和`age`,通过`get/set`方法安全访问。封装提升代码稳定性、可维护性和复用性,防止外部直接修改导致的错误,确保数据安全。它是面向对象编程优于传统编程的关键,促进高效、可靠的开发。
|
7天前
|
Java 开发者 UED
Java中的异常处理机制深度解析
在Java编程中,异常处理是确保软件健壮性的关键因素。本文将深入探讨Java的异常处理机制,包括异常的类型、如何捕获和处理异常,以及最佳实践。我们将通过实例学习如何优雅地处理异常,避免常见的陷阱,并提升代码的可维护性和用户体验。
|
3天前
|
Java 开发者
Java中的异常处理机制
Java中的异常处理机制
14 2
|
3天前
|
Java 程序员
Java中实现动态性的原理和机制
Java中实现动态性的原理和机制
6 1
|
4天前
|
安全 Java 程序员
Java并发编程中的锁机制与优化策略
【6月更文挑战第17天】在Java并发编程的世界中,锁是维护数据一致性和线程安全的关键。本文将深入探讨Java中的锁机制,包括内置锁、显式锁以及读写锁的原理和使用场景。我们将通过实际案例分析锁的优化策略,如减少锁粒度、使用并发容器以及避免死锁的技巧,旨在帮助开发者提升多线程程序的性能和可靠性。
|
7天前
|
Java 程序员 数据库连接
深入理解Java异常处理机制
在Java编程中,异常处理是维护程序稳定性和健壮性的关键。本文将深入探讨Java的异常处理机制,包括异常的分类、捕获异常的方法以及自定义异常的使用。我们将通过实例代码来展示如何有效管理异常,并讨论最佳实践以避免常见的陷阱。
|
7天前
|
Java
Java中的异常处理机制
【6月更文挑战第14天】本文将探讨Java中的异常处理机制,包括异常的概念、类型以及如何进行异常捕获和处理。我们将通过示例代码来演示如何在Java程序中使用try-catch语句来捕获和处理异常。