阿里云个人服务器搭建项目笔记五:分布式扩展之后的分布式session问题解决

项目添加redis的session依赖

将分布式session存储在redis内:
打开项目的pom.xml,导入springboot对reids的依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.session</groupId>
      <artifactId>spring-session-data-redis</artifactId>
    </dependency>

添加如下依赖之后,可以增加一个配置类配置session存活时间:

@Component
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)//session存活一小时
public class RedisConfig {

}

数据库服务器安装redis4.0.1

我这里数据库服务器只有一台,而应用程序服务器是多台的,所以会出现分布式的session获取不到的问题,这里将redis安装到唯一的数据库服务器中。

官网下载redis-4.0.1.tar.gz,放到服务器:
执行:
chmod - R 777 redis-4.0.1.tar.gz
tar -xvzf redis-4.0.1.tar.gz
cd redis-4.0.1
make
make install
cd src
./redis-server &
./redis-cli
安装并启动redis

本地部署测试

修改application.properties:
增加redis配置

#配置springboot对redis的依赖
spring.redis.host=127.0.0.1 # 这里之后还要修改
spring.redis.port=6379
spring.redis.database=10
#spring.redis.password=

#设置jedis连接池
spring.redis.jedis.pool.max-active=50
spring.redis.jedis.pool.min-idle=20

将用户登录的session模型类实现Serializable使redis可以序列化

之后测试登录:
登录成功

踩坑:
但是…坑爹的来了,测试获取验证码失败。
一步一步debug:发现redis内有数据,key也是正确的,但是获取却一直为null。
因为我的验证码的获取和用户输入是两个不同的页面,所以为了安全起见,session默认会被获取为null。
查阅资料得出,如果满足以下几点,session可能获取为null。

  • 使用了springboot2.x,并且前后端资源是跨域的。
  • 获取session的请求接口是post方式发送的
  • 存储session和取得session是不同的两个页面。

满足以上三点,session的获取就会为null;
解决方案,编写配置类

@Configuration
public class SpringSessionConfig {

    public SpringSessionConfig() {
    }

    @Bean
    public CookieSerializer httpSessionIdResolver() {
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        // 取消仅限同一站点设置
        cookieSerializer.setSameSite(null);
        return cookieSerializer;
    }
}

成功完成session跨页获取
至此为止,分布式session的问题被解决。不会出现用户登录失效的问题。

token方式替代基于cookie传递sessionId

项目代码中修改,将传统session方式替代。这样的好处是可以使用多个平台的兼容性。

 //生成登录凭证token,每个用户不重复的uuid
        String uuidToken = UUID.randomUUID().toString();
        uuidToken = uuidToken.replace("-", "");
        //建立token和登陆之后用户模型之间的联系
        redisTemplate.opsForValue().set(uuidToken, userBO);
        //设置超时时间一个小时
        redisTemplate.expire(uuidToken,1, TimeUnit.HOURS);
//        //将登录凭证加入到用户登陆成功的session
//        this.httpServletRequest.getSession().setAttribute("IS_LOGIN", true);
//        this.httpServletRequest.getSession().setAttribute("LOGIN_USER", userBO);
        //下发token
        return CommonReturnType.create(uuidToken);

前端判断登录成功之后存储token的代码:

                        var token = data.data;
                        //html5新出的token方式localStorage,比cookie session安全且没有容量限制
                        window.localStorage["token"]=token;

需要验证用户登录信息时,验证token是否存在:

var token = window.localStorage["token"];
            if(token == null){
                alert("没有登录,不能下单!");
                window.location.href="login.html";
                return false;
            }

后端负责验证token信息是否正确,并从token获取用户模型:

String token = httpServletRequest.getParameterMap().get("token")[0];
        if(StringUtils.isEmpty(token)){
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登录,不能下单");
        }
        //获取用户登录信息
        UserBO userBO = (UserBO) redisTemplate.opsForValue().get(token);
        if(userBO == null){
            throw new BusinessException(EmBusinessError.USER_NOT_LOGIN,"用户还未登录,不能下单");
        }

最后别忘了将token放到ajax请求的url中(参数拼上?token="+token):

//登录成功之后,将用户模型存入前台的localStorage
			var token = data.data;
//html5新出的token方式localStorage,比cookie session安全且没有容量限制
            window.localStorage["token"]=token;
//....................................................................
			var token = window.localStorage["token"];//从前台localStorage获取token
            $.ajax({
                type: "POST",
                url: "http://"+g_host+"/order/createorder?token="+token,
                contentType: "application/x-www-form-urlencoded",

本地调试成功之后,部署到云端的数据库服务器中,因为我这里暂时还没有主从服务器,只有一台数据库服务器,所以把redis安装在了mysql数据库服务器中,两者共用一台服务器。

将两台应用程序服务器的application.properties都修改,增加如下的配置:
spring.redis.host=数据库服务器的私网ip。
之后重新启动两个应用程序jar包,调试,成功。

总结:

这篇文章主要解决的问题是:
用户登录信息的session经过分布式扩展之后可能获取不到的问题,从基于cookie传递sessionID的方式转化为基于token传输类似于sessionID的形式,并且使session迁移至公共的redis数据库中。

解决方式:
通过redis集中式缓存sessionID。并通过redisTemplate.opsForValue().set(uuidToken, userBO);实现session的存取。
UserBO userBO = (UserBO) redisTemplate.opsForValue().get(token);实现session的获取。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值