目录
1.前置知识——HTTP协议
1.1.HTTP 的主要特点有以下 5 个:
1.2.HTTP 组成
1.3.为什么会有Cookie、Session、Token?
2.Cookie
3.Session
PS:Cookie 和 Session 的联系与区别
4.Token
4.1.token的组成
4.2.token是如何生成的?
4.3.token认证流程
5.小结
6.核心方法
6.1.HttpServletRequest 类中的相关方法
6.2.HttpServletResponse 类中的相关方法
6.3.HttpSession 类中的相关方法
6.4.Cookie 类中的相关方法
②session写入和读取
1.前置知识——HTTP协议
HTTP(Hyper Text Transfer Protocol)超文本传输协议,下文简称 HTTP,它的作用是用于实现服务器端和客户端的数据传输的。它可以传输任意的数据类型,如文本、HTML、图片、文件、声音等类型。 简单来说,HTTP 的作用就像一个“快递”一样,用来承载客户端(浏览器)和服务器端的数据传输,如下图所示:
1.1.HTTP 的主要特点有以下 5 个:
- 简单快速:客户端向服务器端发送请求时,只需传递请求方法、路径和请求参数,因为协议简单,所以使得 HTTP 服务器的程序规模小,因而通信速度很快。
- 无连接:所谓的无连接指的是,每次连接只处理一个请求。服务器处理完客户的请求后,会立即断开连接。
- 无状态:HTTP 不会记录每次请求的身份信息,因此前一次请求和后一次请求相互“不认识”。
- 可传递任意数据类型:HTTP 允许传输任意数据类型,只需要在请求头中标识数据类型 Content-Type 即可。
- 一对一通讯:每次 HTTP 请求,都是一个客户端对应一个服务器端。
1.2.HTTP 组成
- 请求对象 Request(客户端请求)。
- 响应对象 Response(服务器响应)。
每次 HTTP 请求都是由一次请求和一次响应构成的,如下图所示:
1.3.为什么会有Cookie、Session、Token?
HTTP协议是无状态协议,所谓的无状态是指:默认情况下 HTTP 协议的客户端和服务器之间的这次通信,和下次通信之间没有直接的联系。客户端每次想要与服务端通信,都必须重新与服务端链接。
但是实际开发中,很多时候是需要知道请求之间的关联关系的,需要判断两次请求是否同一个人。
例如登陆⽹站成功后,第⼆次访问的时候服务器就能知道该请求是否是已经登陆过了。
图中的 "令牌" 通常就存储在 Cookie 字段中。
为了解决这个问题,就迫切需要一种方式知道发起请求的客户端是谁?此时就有了Cookie、Session、Token,它们就可以解决客户端标识的问题。
2.Cookie
cookie是保存在客户端或者浏览器端的一小块数据,大小限制在4KB左右。
那么如何通过cookie来实现用户确定或者权限确定呢?以网站用户登录操作为例:
看到大致可以分为以下几个步骤:
- 客户端发送请求到服务端(比如登录请求)。
- 服务端收到请求后生成一个session会话。
- 服务端响应客户端,并在响应头中设置Set-Cookie。
- 客户端收到请求后,如果服务器给了Set-Cookie,那么下次浏览器就会在请求头中自动携带cookie。
- 客户端发送其它请求,自动携带cookie,cookie中携带有用户信息等。
- 服务端接收到请求,验证cookie信息。比如通过sessionId来判断是否存在会话,存在则正常响应。
举个例子:
- 到了医院先挂号,挂号时候需要提供身份证,同时得到了⼀张 "就诊卡",这个就诊卡就相当于患者的 "令牌"。
- 后续去各个科室进⾏检查,诊断,开药等操作,都不必再出示身份证了,只要凭就诊卡即可识别出当前患者的身份。
- 看完病了之后,不想要就诊卡了,就可以注销这个卡。此时患者的身份和就诊卡的关联就销毁了。 (类似于⽹站的注销操作)
- ⼜来看病,可以办⼀张新的就诊卡,此时就得到了⼀个新的 "令牌"。
3.Session
在上⾯的例⼦中,就诊卡就是⼀张 "令牌",要想让这个令牌能够生效,就需要医院这边通过系统记录每个就诊卡和患者信息之间的关联关系。
类似于服务器同⼀时刻收到的请求是很多的,服务器需要区分清楚每个请求是从属于哪个⽤户,就需要在服务器这边记录每个⽤户的令牌以及⽤户的信息对应关系。
这个就是 Session 会话机制所做的⼯作。会话的本质就是⼀个 "哈希表",存储了⼀些键值对结构。key 就是令牌的 ID(token/sessionId),value 就是⽤户信息。(⽤户信息可以根据需求灵活设计)
- session 由服务端创建,当一个请求发送到服务端时,服务器会检索该请求里面有没有包含 sessionId 标识。
- 如果包含了 sessionId,则代表服务端已经和客户端创建过 session,然后就通过这个 sessionId 去查找真正的 session。
- 如果没找到,则为客户端创建一个新的 session,并生成一个新的 sessionId 与 session 对应。
- 然后在响应的时候将 sessionId 给客户端,通常是存储在 cookie 中。
- 如果在请求中找到了真正的 session,验证通过,正常处理该请求。
总之每一个客户端与服务端连接,服务端都会为该客户端创建一个 session,并将 session 的唯一标识 sessionId 通过设置 Set-Cookie 头的方式响应给客户端,客户端将 sessionId 存到 cookie 中。
sessionId 是由服务器⽣成的⼀个 "唯⼀性字符串"。
从 session 机制的⻆度来看,这个唯⼀性字符串称为 "sessionId"。
但是站在整个登录流程中看待,也可以把这个唯⼀性字符串称为 "token"。
sessionId 和 token 就可以理解成是同⼀个东⻄的不同叫法。(不同视⻆的叫法)
servlet 的 Session 默认是保存在内存中的,如果重启服务器则 Session 数据就会丢失。
通常情况下,cookie 和 session 都是结合着来用,当然也可以单独只使用 cookie 或者单独只使用 session,这里就将 cookie 和 session 结合着来用。过程图如下:
PS:Cookie 和 Session 的联系与区别
一、联系:
从前面可以看出,cookie和session之间主要是通过sesssionId关联起来的,所以:sessionId是cookie和session之间的桥梁。换个说法,session是基于cookie实现的。
二、区别:
- cookie 是客户端的机制,session 是服务器端的机制。所以session比cookie更加安全。
- cookie只支持存储字符串数据,session可以存储任意数据。
- cookie的有效期可以设置较长时间,session有效期都比较短。
- session存储空间很大,cookie有限制。
- cookie 和 session 经常会在⼀起配合使用(session会将会话id存储到cookie中)。但是不是必须配合。
- 完全可以⽤ cookie 来保存⼀些数据在客户端,这些数据不⼀定是⽤户身份信息,也不⼀定是 token / sessionId。
- Session 中的 token / sessionId 也不需要⾮得通过 cookie / Set-Cookie 传递。
4.Token
前面说的 sessionId 可以叫做令牌,令牌顾名思义就是确认身份的意思,服务端可以通过令牌来确认身份。
使用cookie和session有以下缺点:
- 增加请求体积,浪费性能,因为每次请求都会携带 cookie。
- 增加服务端资源消耗,因为每个客户端连接进来都需要生成 session,会占用服务端资源的。
- 容易遭受 CSRF 攻击,即跨站域请求伪造。
- 所以就有了民间的认证方式,token方式。
4.1.token的组成
token 其实就是一串字符串而已,只不过它是被加密后的字符串,它通常使用 uid(用户唯一标识)、时间戳、签名以及一些其它参数加密而成。将 token 进行解密就可以拿到诸如 uid 这类的信息,然后通过 uid 来进行接下来的鉴权操作。
4.2.token是如何生成的?
前面说 cookie 是服务端设置了 set-cookie 响应头之后,浏览器会自动保存 cookie,然后下一次发送请求的时候会自动把 cookie 携带上。但是说 cookie 算是一种民间的实现方式,所以说浏览器自然不会对它进行成么处理。token 主要是由服务器生成,然后返回给客户端,客户端手动把 token 存下来,比如利用 localstorage 或者直接存到 cookie 当中也行。
4.3.token认证流程
- 客户端发送请求,比如用户输入用户名和密码后登录。
- 服务器端校验用户名和密码后,将用户id和其它信息进行加密,生成token。
- 服务端将token响应给客户端。
- 客户端收到响应后将token存储下来。
- 下一次发送请求后需要将token携带上,比如放在请求头中或者其他地方。
- 服务端取到token后校验,校验通过后则正常返回数据。
用图进行显示,如下:
5.小结
6.核心方法
6.1.HttpServletRequest 类中的相关方法
6.2.HttpServletResponse 类中的相关方法
6.3.HttpSession 类中的相关方法
⼀个 HttpSession 对象⾥⾯包含多个键值对,我们可以往 HttpSession 中存任何我们需要的信息。
6.4.Cookie 类中的相关方法
每个 Cookie 对象就是⼀个键值对。
- HTTP 的 Cooke 字段中存储的实际上是多组键值对,每个键值对在 Servlet 中都对应了⼀个 Cookie 对象。
- 通过 HttpServletRequest.getCookies() 获取到请求中的⼀系列 Cookie 键值对。
- 通过 HttpServletResponse.addCookie() 可以向响应中添加新的 Cookie 键值对。
PS:代码实战
①cookie写入和读取
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.LocalTime;
@WebServlet("/cookie")
public class CookieServlet extends HttpServlet {
/**
* 写cookie到客户端
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie cookie = new Cookie("myCookie", "Bite:" + LocalTime.now());
//将cookie发送到前端
resp.addCookie(cookie);
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().println("cookie添加成功!");
}
/**
* cookie读取
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Cookie[] cookies = req.getCookies();
StringBuilder builder = new StringBuilder();
if(cookies != null && cookies.length > 0) {
for (Cookie item : cookies) {
builder.append(item.getName() + ":" + item.getValue() + "<br>");
}
}
//将cookie发送到前端
resp.setContentType("text/html; charset=utf-8");
resp.getWriter().println(builder.toString());
}
}
当更换浏览器后,cookie就没了。因为cookie是写入到当前的客户端的,不同的客户端,cookie不同。
②session写入和读取
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Setter
@Getter
@ToString
public class User {
private int id;
private String name;
private String password;
private int age;
//...
}
import model.User;
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("/sess")
public class SessionServlet extends HttpServlet {
private static final String SESSION_USER_KEY = "SESSION_USER_KEY";
/**
* 读session
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
//1.得到session对象
HttpSession session = req.getSession(false);
//false表示有会话,返回session对象,没有会话,返回null
//true表示有会话,返回session对象,没有会话,创建一个会话,会话在程序中就代表是否登录
if(null == session) {
//没有登录
resp.getWriter().println("抱歉:尚未登录!");
} else {
//已登录,打印用户的信息
//2.从session中得到关联的对象
User user = (User) session.getAttribute(SESSION_USER_KEY); //①得到所有的cookie;②匹配你的token;③根据value匹配映射的对象
resp.getWriter().println("登录成功 | " + user);
}
}
/**
* 写session
*
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html; charset=utf-8");
String result = "操作失败!";
//1.得到session
HttpSession session = req.getSession(true);
if(null != session) { //得到(或创建)会话
//2.将用户对象存储到session
User user = new User();
user.setName("admin");
user.setPassword("admin");
session.setAttribute(SESSION_USER_KEY, user); //①生成token(sessionId);②token关联到用户对象;③发送存储cookie信息到客户端(token)
result = "session 写入成功!";
}
resp.getWriter().println(result);
}
}
注意观察cookies由1变为了2。因为session是依靠了cookie存储会话id的。
此处也可以在其他浏览器上进行伪造:
当重启服务器后,会话就没了,浏览器重新访问:
再次用Postman发送请求: