22-05-27 西安 javaweb(10) cookie、session、session共享

为什么要学习会话?

http是一个无状态的协议,服务器是不知道俩次请求是同一个浏览器发送过来的。没有任何一个信息可以表示浏览器的身份。

会话:从用户登录到用户退出登录这个过程中所发生的所有请求,其实都是在一次会话范围之内。


cookie是一种客户端的会话技术

会话技术的cookie:在服务器创建并维护,但是保存在浏览器端的会话技术,浏览器每次请求携带cookie,以表明自己的身份

cookie的常用操作 cookie的增删改查

1、创建cookie

new Cookie(); 没有无参构造,必须传一个String类型的键值对。

//写中文就报错
Cookie cookie_username = new Cookie("username", "admin");
Cookie cookie_password = new Cookie("password", "123456");
//将cookie响应到浏览器
response.addCookie(cookie_username);
response.addCookie(cookie_password);

cookie存在于响应报文中的响应头中

此后浏览器再向服务器发送请求,会在请求报文的请求头中会携带这个cookie


2、清除cookie的3种方式

1.因为cookie在浏览器的运行内存中保存,浏览器关闭,浏览器的运行内存会被释放,因此cookie失效
2.在浏览器页面ctrl+shift+delete 清空浏览器缓存 

或者是下面这样,也可以移除cookie

或者是这样,点开检查


3、获取cookie数据

遍历Cookie数组,要防止空指针异常

//获取请求报文中的所有cookie的数组
Cookie[] cookies = request.getCookies();
if(cookies != null){
    for (Cookie cookie : cookies) {
        //获取cookie的name
        String name = cookie.getName();
        //获取cookie的value
        String value = cookie.getValue();
        System.out.println("name:"+name+",value:"+value);
    }
}

控制台打印一下结果:


或许你还想过这么做,来获取cookie中保存的数据

String cookie = request.getHeader("cookie");
System.out.println(cookie);

这样获取的就是个字符串了,还要处理,比较麻烦。所以不推荐这种方式


4、修改cookie数据 

本质就是一个map集合

方式一:(推荐)

创建一个新的cookie(键相同,值不同),响应到浏览器就会将浏览器中所对应的cookie覆盖掉

Cookie cookie = new Cookie("username", "root");
response.addCookie(cookie);

方式二:(不推荐)

先获取cookie,修改后再手动响应到浏览器

Cookie[] cookies = request.getCookies();
if(cookies != null){
    for (Cookie cookie : cookies) {
        if(cookie.getName().equals("username")){
            cookie.setValue("root");
            response.addCookie(cookie);
        }
    }
}

5、设置cookie的有效时间

如果我们不设置cookie的时效性,默认情况下cookie的有效期是一次会话范围内。

cookie.setMaxAge(int second)//设置秒

既然参数是一个整数,那就分为3种情况

情况一:当设置有效时间为一个负整数时:没有任何效果,就跟没有设置一样。
即cookie的有效时间是默认的为一次会话,cookie有效期是从浏览器开启到浏览器关闭

情况二:当设置有效时间为0时,表示此cookie立即失效

 情况三:当设置有效时间为正整数时,又分为2种情况

  1.      有效时间比一次会话短:cookie会在运行内存中删除
  2.      有效时间比一次会话长:浏览器关闭时,cookie会保存到文件中,当浏览器重新开启且cookie仍然有效,此时磁盘中文件会重新加载到浏览器的运行内存中。

6、设置cookie的有效路径

cookie.setPath()设置cookie的有效路径,使得在访问指定资源时,才会携带此cookie,而不是说访问任何服务器资源每一个请求都需要携带这个cookie。

通过setPath()设置的绝对路径为浏览器解析的绝对路径,因此要添加上下文路径

//这里的绝对路径是由浏览器解析的
cookie.setPath(request.getContextPath()+"/GetCookieServlet")

7、cookie的3个缺点

1.cookie保存在浏览器端,因此不安全
2.cookie只能存储字符串类型的键值对,在存储比较复杂的数据时(比如一个对象),就会产生大量的cookie,而session存储的值是Object
3.每次浏览器发送请求到服务器都会携带大量的cookie,因此会造成网络负担。


Session是服务器端的会话技术

会话技术的session:在服务器创建维护并保存在服务器端的会话技术

1、获取Session

在一次会话中,第一次访问getSession(),浏览器中的请求报文中没有JSESSIONID的cookie,就在服务器内部会创建JSESSIONID的cookie并响应到浏览器。

//会自动创建cookie,并响应到浏览器,值是随机序列uuid
HttpSession session=request.getSession();//通过小的域对象获得大的域对象

2.浏览器会保存jsessionid的cookie到运存中。此后,浏览器再发送请求请求头就会携带JSESSIONID的cookie,服务器中也不会再创建JSESSIONID的cookie.


2、Session和Cookie的关系

session和cookie的关系?为什么在一次会话中获取的session都是同一个

当执行request.getSession();服务器会获取请求报文中键为JSESSIONID的cookie.这时候分为这个cookie不存在和存在2种情况。

执行request.getSession()获取session的时候:

1.若键为JSESSIONID的cookie不存在,说明当前会话刚刚开始。

在服务器内部没有创建Httpsession对象。此时在服务器内部先创建Httpsession对象,并创建一个键为JSESSIONID值为随机序列的uuid 的cookie.

然后将Httpsession对象存储在由服务器所维护的map<String,HttpSession>中,以此随机序列uuid为键,以Httpsession对象为值。最终将键为JSESSIONID的cookie响应到浏览器。

2.若键为JSESSIONID的cookie存在,则获取此cookie的值即uuid随机序列,然后再将uuid作为键,获取服务器所维护的map集合的值。也就是Httpsession对象,因此在一次会话中,获取的Httpsession对象都是同一个。


3、Session相关的操作

①操作域对象数据

  • void setAttribute(String name, Object value):设置共享数据
  • Object getAttribute(String name):获取共享数据
  • void removeAttribute(String name):删除共享数据

②设置Session的最大不活动时间

session时效:在一次会话中,超过指定时间未操作session中的任何数据,则session自动失效。

设置session时效的俩种方式:

方式一:在web.xml中设置,这里设置的session时效单位是分钟

<session-config>
    <session-timeout>30</session-timeout>
</session-config>

注意在tomcat的web.xml中已经有这个配置,默认30分钟。

方式二:通过setMaxInactiveInterval()设置,这里设置的session时效单位是秒

HttpSession session = request.getSession();
session.setMaxInactiveInterval(3600);

③强制使session失效

用于(注销,退出登录)

注销的时候也可以用这个session.removeAttribute("user");为什么要session.invalidate()它呢

答:退出登录之后应该要和之前的用户就没有关系了,不能只能把session中的用户信息删除了。比如登录成功的用户信息和购物车信息

session.invalidate()

④session的钝化和活化

钝化指会话没有结束,但是服务器关闭,此时session中的数据会序列化到磁盘文件中

理论上tomcat的work目录下放的是就是jsp所翻译的servlet和session的钝化文件,实际上session的钝化文件保存在tomcat的镜像,Sessions.ser(文件后缀ser是序列化的意思)

要想实体类对象也能钝化,得实现序列化接口Serializable

若要实现钝化,在idea中,tomcat实例必须选择Preserve sessions across restarts and redeploys

 session的钝化文件保存的位置

session活化:指会话没有结束,服务器开启的情况下,钝化到磁盘文件中的数据会重新加载到服务器的session中,同时这个钝化文件也会消失


4、验证码

验证码是防止暴力提交的 ,注册验证的时候,先验验证码。

引入包kaptcha

这个就是我们注册页面中的那个验证码,我们让他每次去访问KaptchaServlet去获取一个验证码的图片。

 1、在web.xml中这么设置:

    <servlet>
        <servlet-name>KaptchaServlet</servlet-name>
        <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
    </servlet>   
    <servlet-mapping>
        <servlet-name>KaptchaServlet</servlet-name>
        <url-pattern>/code.jpg</url-pattern>
    </servlet-mapping>

2、则每次访问http://localhost:8080/bookstore/code.jpg  就会返回不一样的验证码图片


我们做这样的一个效果:

在点击验证码图片的时候,让验证码刷新,也即再次向服务器发送一个请求,获取一个新的验证码图片。

 在注册页面对应的代码是这样子的

  <img @click="refreshCode()" :src="imgPath" alt="" style="height: 40px; width: 98px;" />

因为浏览器存在缓存功能所以要加一个Math.random()作为请求参数,让每次发送请求不一样

imgPath:"code.jpg",
refreshCode(){
   this.imgPath="code.jpg?y="+Math.random();
},

测试一下验证码刷新功能


验证码图片变着花样设置

比如设置验证码中验证码图片的边框、字符的数量、验证码中使用的字符、验证码的字体、干扰线颜色等等。这些所有关于验证码的配置:都要在servlet的初始化参数配置

    <servlet>
        <servlet-name>KaptchaServlet</servlet-name>
        <servlet-class>com.google.code.kaptcha.servlet.KaptchaServlet</servlet-class>
        <init-param>
            <param-name>kaptcha.textproducer.char.string</param-name>
            <param-value>小羊上山吃草</param-value>
        </init-param>
        <init-param>
            <param-name>kaptcha.textproducer.font.names</param-name>
            <param-value>微软雅黑</param-value>
        </init-param>
        <init-param>
            <param-name>kaptcha.session.key</param-name>
            <param-value>codeInSession</param-value>
        </init-param>
    </servlet>

我测了4次结果如下:挺好玩的!


Session共享问题

啥叫session共享,为什么需要session共享?

session共享是在集群环境中保证多个服务器节点中的session数据一致

1、session共享方案

1.session复制


2.一致性hash(保证一个客户端它连接的集群里面的服务器节点永远是同一个)
缺点:假如这个服务器节点挂掉了呢?客户端会把请求发给另外一个服务器节点,会出问题

3.不用session,而采用cookie
缺点:比如受cookie大小的限制,能记录的信息有限;
每次请求响应都需要传递cookie,影响性能,
如果用户关闭cookie,访问就不正常

4.用session服务器
可以使用redis作session服务器
特点:多个服务器节点都是共享session服务器中的session数据

利用独立部署的session服务器(集群)统一管理session,服务器每次读写session时,都访问session服务器。

2、session共享原理

1、用户第一次访问应用时,应用会创建一个新的 Session,并且会将 Session 的 ID 作为 Cookie 缓存在浏览器,下一次访问时请求的头部中带着该 Cookie,应用通过获取的 Session ID 进行查找,如果该 Session 存在且有效,则继续该请求,如果 Cookie 无效或者 Session 无效,则会重新生成一个新的 Session

2、在普通的 JavaEE 应用中,Session 信息放在内存中,当容器(如 Tomcat)关闭后,内存中的 Session 被销毁;重启后如果当前用户再去访问对应的是一个新的 Session ,在多实例中无法共享,一个用户只能访问指定的实例才能使用相同的 Session;

3、Session 共享实现的原理是将原来内存中的 Session 放在一个需要共享 Session 的服务器都可以访问到的位置,如数据库,Redis 等等,从而实现多实例 Session 共享 ,只要浏览器的 Cookie 中的 Session ID 没有改变,多个实例中的任意一个被销毁不会影响用户访问

3、Spring Session

Spring给我们提供了Spring Session来解决session共享问题

要解决session共享问题:其实就是要改变request.getSession()方法

这个方法中,既有可能创建session、又有可能查找session。
这个方法原本创建的session,是创建在内存中,查找session也是到服务器内存中查找

要做到session共享的话,创建的session应该放到session服务器中。查找session的话,也要在session服务器中查询

要实现session共享,我们就得改变request.getSession()这个方法
在filter中创建request的装饰者对象,改变request的getSession()方法

拦截请求,将之前在服务器内存中进行 session 创建的动作,改成在 redis 中创建。

如果要我去写一个框架实现session共享,我会怎么做呢?
1.创建一个filter,执行在所有的filter之前
2. 在filter中创建request的装饰者,在装饰者中修改getSession()方法,实现将session存储到session服务器,以及从session服务器查找session。
3. 将request的装饰者交给处理器进行请求处理

但是这个框架Spring给我们提供了Spring Session,它底层的原理思路就是这样的。

当请求进来的时候,SessionRepositoryFilter 会先拦截到请求,将 request 和 response 对象转换成 SessionRepositoryRequestWrapper 和 SessionRepositoryResponseWrapper 。

后续当第一次调用 request 的getSession方法时,

会调用到 SessionRepositoryRequestWrapper 的getSession方法。

这个方法是被重写过的,逻辑是先从 request 的属性中查找,如果找不到;再查找一个key值是"JSESSION"的 Cookie,通过这个 Cookie 拿到 SessionId 去 Redis 中查找,如果查不到,就直接创建一个RedisSession 对象,同步到 Redis 中。

在父工程shf-parent中已经有了这个依赖管理(版本1.3.5.RELEASE),

1、web-admin中引入依赖

<!--spring-session 同步-->
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>

2、创建spring-redis.xml

session在内存中的超时时间是多久?

闲置30分钟,如果用户一直在发送请求,session是一定不会超时的

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--Jedis连接池的相关配置-->
    <bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig">
        <!--最大连接数, 默认8个-->
        <property name="maxTotal" value="100"></property>
        <!--最大空闲连接数, 默认8个-->
        <property name="maxIdle" value="50"></property>
        <!--允许借调 在获取连接的时候检查有效性, 默认false-->
        <property name="testOnBorrow" value="true"/>
        <!--允许归还 在return给pool时,是否提前进行validate操作-->
        <property name="testOnReturn" value="true"/>
    </bean>
    <!--配置JedisConnectionFactory-->
    <bean id="connectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
        <!--1. redis服务器的IP地址-->
        <property name="hostName" value="192.168.2.108"/>
        <!--2. redis服务器的端口号-->
        <property name="port" value="6379"/>
        <!--3. redis服务器中的数据库的索引下标-->
        <property name="database" value="0"/>
        <!--4. 指定jedis连接池的配置-->
        <property name="poolConfig" ref="jedisPoolConfig"/>
    </bean>

    <!-- 配置session共享 -->
    <bean id="redisHttpSessionConfiguration"
          class="org.springframework.session.data.redis.config.annotation.web.http.RedisHttpSessionConfiguration">
        <!--
            session的超时时间(session存储在redis中的闲置超时时间)
        -->
        <property name="maxInactiveIntervalInSeconds" value="1800" />
    </bean>
</beans>

web.xml添加session共享过滤器

一定要配置在所有过滤器之前,所有的请求经过该过滤器后执行后续操作

<!-- spring session共享filter -->
<!-- 该过滤器必须是第一个过滤器,所有的请求经过该过滤器后执行后续操作 -->
<filter>
  <filter-name>springSessionRepositoryFilter</filter-name>
  <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
  <filter-name>springSessionRepositoryFilter</filter-name>
  <url-pattern>/*</url-pattern>
</filter-mapping>

4、验证session共享

一、测试方法:观察redis客户端
实现session共享的效果:redis中存储了session信息

通过redis客户端查看redis数据 ,说明session成功同步到redis数据库中。

二、测试方法:重启web-admin

如果session是存储到tomcat内存中,当tomcat重启的之后,肯定要重新登录。
因为session是存储在tomcat内存中,而tomcat重启之后,内存中的数据会全部消失

重启web-admin,发现页面不会跳转到登录页面,而是处于登录状态

说明:如果session没有同步到redis,那么再次重启,session信息已经清空,就会再次跳转登录,当前没有跳转登录,说明我们的session信息保存到redis。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值