HTTP协议--cookie的应用

HTTP协议–cookie

1.我们为什么要使用cookie?

我们做一个实验:(这个例子比较简单,在局域网下实现,大家了解即可)

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;
import java.util.Enumeration;

@WebServlet("/status")
public class HttpStatusServlet extends HttpServlet {

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        String method = req.getMethod();
        String requestURI = req.getRequestURI();
        String protocol = req.getProtocol();
        String remoteAddr = req.getRemoteAddr();
        int remotePort = req.getRemotePort();
        System.out.println("位于" + remoteAddr + "的客户端使用了端口号" + remotePort + "访问了当前的服务器,对应的请求报文为");
        System.out.println(method + " " + requestURI + " " + protocol);

        Enumeration<String> headerNames = req.getHeaderNames();
        while (headerNames.hasMoreElements()){
            String key = headerNames.nextElement();
            String value = req.getHeader(key);
            System.out.println(key + ":" + value);
        }
    }
}

我们通过抓包打印日志发现,不同客户端访问服务器发送的报文基本上没什么区别。那这样的话,按理说服务器应该是不能区分不同客户端发送过来的报文。但是实际上,我们在京东或者其他网站购买东西的时候,当我们将购物车页面的地址给你的小伙伴,让他访问,发现他看不到商品,京东会提示他需要进行登录。

所以我们得到结论:服务器是可以识别出不同的客户端的,根据不同的客户端返回不同的数据
由此我们就可以引出会话技术

2.cookie

会话技术就是为了解决HTTP协议的无状态性,会话技术一共可以分为两大类,一类是客户端技术,一类是服务请技术。这决定了数据存储在客户端还是服务器。我们今天先来介绍客户端技术的典型代表cookie

cookie的本质就是在HTTP请求报文和HTTP响应报文中分别引用了Cookie请求头和set-Cookie响应头。利用头信息来传递状态信息

cookie以字符串的形式保存在浏览器中。常见的几种属性:

key=value;
expires=失效时间;
path=路径;
domain= 域名;
8secure;(secure表安全级别)
raw: 默认值:false。 默认情况下,读取和写入 cookie 的时候自动进行编码和解码(使用encodeURIComponent 编码, decodeURIComponent 解码)
setMaxAge:设置存活时间

2.1设置cookie的存活时间

场景:用户登录商城,将商品加入购物车然后查看购物车。

用户登录时,客户端将用户的信息发送到服务器,服务器在数据库中对该用户的用户名和密码进行比对,比对通过后服务器使用能够唯一标识该用户的数据用户名(这里拿用户名举例),放进HTTP的set-Cookie响应头中返回给客户端。当客户端执行商品加入购物车操作之后然后去查看购物车,此时页面需要跳转访问,此时客户端携带cookie信息向服务器发送访问购物车的请求,服务器根据cooke信息来判断是哪个用户要访问购物车,以此来返回正确的购物车信息。

在此场景下,我们希望登录状态能够持续久一点,在一定时间内,我们再去查看我们的购物车的时候仍然可以看到,不希望看到反复的登录页面。这就可以设置cookie的存活时间来解决。如果cookie没有设置存活时间,表示是存活在浏览器对应的内存区域当中,关闭浏览器cooki就会失效如果希望cookie能够持久化保存,则可以设置一个正数存活时间,表示在硬盘上存活多少秒。当然另外一个场景就不需要我们持久的保持登录状态比如银行app

setMaxAge:设置正数表示硬盘存活多少秒,设置负数表示存在于浏览器内存中,设置0表示删除cookie

2.2设置路径

如果没有设置路径,那么仅当访问当前路径以及当前路径的子路径时才会携带cookie

举个例子:

比如当前的servlet地址是 /a/b/c/servlet1
那么当访问 /a/b/c/servlet2时会携带cookie
/a/b/c/servlet1时会携带cookie
/a/b/c/e…同理

当访问 /a/b/servlet3时不会携带cookie

应用场景:设置访问html页面时携带cookie,访问js、css等资源文件时不携带cookie

2.3设置域名

浏览器不允许设置和当前域名无关的域名的cookie
比如当前的代码运行在localhost域名下,但是希望设置一个jd.com域名的cookie,浏览器会屏蔽该行为。如果希望设置某个域名的cookie,就必须在相关域名之下设置才可以。

在这里介绍的设置域名是指如果设置了一个父域名的cookie,那么当访问相关的子域名网页时,会自动携带cookie

举个例子:

设置了一个cookie的域名为jd.com,当访问search.jd.com、passport.jd.com时浏览器会默认携带cookie
jd.com ------父域名
search.jd.com
passport.jd.com-------子域名

2.4cookie的优缺点

优点:小巧、轻便、存储在客户端,减轻服务器的压力;父子域名之间可以共享

缺点:只可以存储字符类型;只可以存储一些非敏感数据;大小有限制

3.session

服务器技术的典型代表有session、HttpSession
我们今天来介绍一下session的原理:在某些场景下,服务器会给当前的客户端在服务器上开辟一块内存,也可以理解为对象,该对象和对应的客户端做到一一绑定。之后只要是该客户端发送过来的请求,如果希望去共享数据,那么就可以使用当前对象来进行共享。

session对象是如何和一个客户端对应起来的呢?

session对象会生成一个唯一的编号,对象产生之后,会把该唯一的编号利用cookie传输给客户端,客户端会将该cookie信息保存到本地,当客户端下次访问服务器的时候,就会在此将cookie再次携带回来,通过取出里面的session编号,那么就可以定位到响应的session对象

如何得知当前请求头有没有关联的session对象呢?getSession()执行逻辑是怎样的

HttpSession getSession()
Returns the current session associated with this request, or if the request does not have a session, creates one.

getSession()方法会去查看当前的请求中又没有有效的Cookie:JSESSIONID=xxxx,如果有携带一个有效的JSESSIONDI,那么就可以利用该编号获取到对应的session对象;如果没有携带有效的编号,就再创建一个新的session对象

3.1常见问题

1.关闭浏览器,session对象会不会销毁,数据会不会丢失?

session对象没有销毁。数据会丢失
浏览器关闭之后,你会发现session对象以及编号发生了改变,发生改变并不是因为session对象被销毁了。而是凭证丢了,关闭服务器之后再打开,Cookie:JSESSIONID=xxxx不会再携带了。所以request.getSession()会重新创建一个新的session对象。

2.关闭服务器,session对象会不会销毁,数据会不会丢失?

服务器关闭之后,session域里面的数据仍然可以访问到。但是session的地址已经发生了变化。
原因:应用在关闭之前,会把session域里面的数据序列化到本地磁盘上。应用重启的时候,会重新读取该序列化文件,并且创建新的session对象来接收原先session的编号以及session域里面的数据。

3.session常用方法

1、setAttribute(String name,String value) 设定指定名字的属性的值,并将它添加到session会话范围内,如果这个属性是会话范围内存在,则更改该属性的值。
2、getAttribute(String name) 在会话范围内获取指定名字的属性的值,返回值类型为object,如果该属性不存在,则返回null。
3、removeAttribute(String name),删除指定名字的session属性,若该属性不存在,则出现异常。
4、invalidate(),使session失效。可以立即使当前会话失效,原来会话中存储的所有对象都不能再被访问。
5、getId( ),获取当前的会话ID。每个会话在服务器端都存在一个唯一的标示sessionID,session对象发送到浏览器的唯一数据就是sessionID,它一般存储在cookie中。
6、setMaxInactiveInterval(int interval) 设置会话的最大持续时间,单位是秒,负数表明会话永不失效。
7、getMaxInActiveInterval(),获取会话的最大持续时间,使用时候需要一些处理
8、getSession() 返回与此请求关联的当前会话,如果请求没有会话,则创建一个。

3.2三个域的比较

context域、session域、request域

对象里都有一个类似与map的数据结构,只要获取到同一个对象,就可以共享该对象里的数据
contest域:每个应用里有且只有一个ServletContext对象,只要是同一个应用下面的资源,那么均可以获取到同一个servletContext对象。共享context域。一般用来存储全局性的数据、配置等。比如当前系统的主机、端口号;当前商城卖的商品分类。

session域:正常情况下来说,只有请求中没有携带有效的Cookie:JSESSIONID=xxxx时,执行request.getSession()代码时会创建一个新的session对象。一般情况下来说,如果没有特殊设置,**正常情况下一个浏览器对应一个session对象。只要使用同一个浏览器来访问当前应用下的所有资源时,那么均可以共享session域。**可以用来存放用户相关的数据。登录状态、购物车、浏览记录

request域:只有转发的两个组件之间可以共享request域。一般情况下存储一些仅当前请求需要用到的数据。

4.JWT(JSON Web Token)

介绍JWT认证:在用户注册或登录后,我们想记录用户的登录状态,或者为用户创建身份认证的凭证。我们不再使用Session认证机制,而使用Json Web Token(本质就是token)认证机制

4.1JWT构成

JWT就是一段字符串,由三部分信息组成,这三部分信息之间用 . 来连接,这样就构成了Jwt字符串。

第一部分信息:我们称之为头部(header)
第二部分信息:我们称之为载荷(payload)
第三部分信息:签证(signature)

jwt头部

头部承载两部分信息

声明类型
声明机密算法,通常使用HMAC SHA256

将头部进行base64加密,构成了第一部分

jwt载荷

载荷是盛放有效信息的地方,包括三部分信息

标准中注册的声明

公共的声明:公共的声明可以添加任何的信息,一般添加用户的相关信息或其他业务需要的必要信息.但不建议添加敏感信息,因为该部分在客户端可解密.

私有的声明:私有声明是提供者和消费者所共同定义的声明,一般不建议存放敏感信息,因为base64是对称解密的,意味着该部分信息可以归类为明文信息

然后进行base64加密得到第二部分

jwt签证

签证信息包含三部分

header(base64加密之后)

payload(base64加密之后)

secret

这个部分需要base64加密后的header和base64加密后的payload使用.连接组成的字符串,然后通过header中声明的加密方式进行加盐secret组合加密,然后就构成了jwt的第三部分。

secret是保存在服务器端的,jwt的签发生成也是在服务器端的,secret就是用来进行jwt的签发和jwt的验证,所以,它就是你服务端的私钥,在任何场景都不应该流露出去。一旦客户端得知这个secret, 那就意味着客户端是可以自我签发jwt了

4.2JWT认证开发流程

(1)使用账号密码进行登录,登录接口中调用签发token的接口,得到token,返回给客户端,客户端自己将token信息存储在本地浏览器

public Account login(Account account) {
        Account dbAdmin = adminMapper.selectByUsername(account.getUsername());
        if (ObjectUtil.isNull(dbAdmin)) {
            throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR);
        }
        if (!account.getPassword().equals(dbAdmin.getPassword())) {
            throw new CustomException(ResultCodeEnum.USER_ACCOUNT_ERROR);
        }
        // 生成token
        String tokenData = dbAdmin.getId() + "-" + RoleEnum.ADMIN.name();
        String token = TokenUtils.createToken(tokenData, dbAdmin.getPassword());
        dbAdmin.setToken(token);
        return dbAdmin;
    }
     /**
     * 生成token
     */
    public static String createToken(String data, String sign) {
        return JWT.create().withAudience(data) // 将 userId-role 保存到 token 里面,作为载荷
                .withExpiresAt(DateUtil.offsetHour(new Date(), 2)) // 2小时后token过期
                .sign(Algorithm.HMAC256(sign)); // 以 password 作为 token 的密钥
    }

前端拿到后端返回的token之后,将数据存储在本地浏览器

this.$request.post('/login', this.form).then(res => {
            if (res.code === '200') {
              let user = res.data;
              localStorage.setItem("xm-user", JSON.stringify(user))  // 存储用户数据

之后每次发送请求时,将token信息放置在请求头

request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';        // 设置请求头格式
    let user = JSON.parse(localStorage.getItem("xm-user") || '{}')  // 获取缓存的用户信息
    config.headers['token'] = user.token  // 设置请求头

    return config
}, error => {
    console.error('request error: ' + error) // for debug
    return Promise.reject(error)
});

后端对前端发送的请求进行验证

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
        // 1. 从http请求的header中获取token
        String token = request.getHeader(Constants.TOKEN);
        if (ObjectUtil.isEmpty(token)) {
            // 如果没拿到,从参数里再拿一次
            token = request.getParameter(Constants.TOKEN);
        }
        // 2. 开始执行认证
        if (ObjectUtil.isEmpty(token)) {
            throw new CustomException(ResultCodeEnum.TOKEN_INVALID_ERROR);
        }
        Account account = null;
        try {
            // 解析token获取存储的数据
            String userRole = JWT.decode(token).getAudience().get(0);
            String userId = userRole.split("-")[0];
            String role = userRole.split("-")[1];
            // 根据userId查询数据库
            if (RoleEnum.ADMIN.name().equals(role)) {
                account = adminService.selectById(Integer.valueOf(userId));
            }
            if (RoleEnum.MERCHANT.name().equals(role)) {
                account = merchantService.selectById(Integer.valueOf(userId));
            }
            if (RoleEnum.USER.name().equals(role)) {
                account = userService.selectById(Integer.valueOf(userId));
            }
        } catch (Exception e) {
            throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR);
        }
        if (ObjectUtil.isNull(account)) {
            throw new CustomException(ResultCodeEnum.USER_NOT_EXIST_ERROR);
        }
        try {
            // 用户密码加签验证 token
            JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(account.getPassword())).build();
            jwtVerifier.verify(token); // 验证token
        } catch (JWTVerificationException e) {
            throw new CustomException(ResultCodeEnum.TOKEN_CHECK_ERROR);
        }
        return true;
    }

如果验证成功则返回请求资源,如果验证失败则抛出ResultCodeEnum.TOKEN_CHECK_ERROR异常
在这个过程中,前端拿到token信息之后,再次访问后端的时候,token的信息是保存在了header中,当然这个信息也可以保存在cookie中,然后携带cookie去访问后端,后端在cookie中取出token信息进行验证即可。

  • 28
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值