2023-09-19  阅读(2)
原文作者:墨家巨子@俏如来 原文地址: https://blog.csdn.net/u014494148/article/details/109918914

Session不共享问题

对于登录而言,通常情况下我们喜欢把登录信息存储到服务器的Session中,这种存储方式在单体应用中没有问题,但是在分布式/集群环境中会存在Session丢失问题,如下图:

202309192313293461.png
解决方案也有很多,在《微服务认证授权方案》一文中有相关的解决方案分析,这里不在重复赘述,本文章的目的是使用SpringSession+Redis来解决分布式系统中的单点登问题

SpringSession的认证方案

这种认证方案还是使用的是session,只不过是将Session统一存储到Redis中实现session共享,各个服务可以从Redis中获取Session得到认证信息,然后做身份检查,权限校验等工作,当然整个流程我们可以自己实现,但是如何把session存储到Redis以及从Redis中取出Session是一个麻烦的事情,Spring提供了解决方案SpringSession

202309192313297902.png
SpringSession干的工作就是修改getSession和setSession方法基于Redis存取Session,同时要注意的是Session是通过Cookie传递sessionid的,如果要让多个服务都能接受到sessionid拿到session,需要把cookie的存储域扩大,如有三个系统“auth.mall.com”,“goods.maill.com”,“stock.mall.com”,那我们需要把domian设置成“mall.com”,这样一来浏览器带着cookie请求任何一个系统都可以获取到sessionid,然后从Redis中获取Session。

ps:如果您还不知道session和cookie的执行流程,请先去查查资料

SpringSession入门

官网文档在这

这里我们使用SpringBoot应用为例,我们需要导入spring-session-data-redis依赖,Spring Boot 会自动创建了一个名为springSessionRepositoryFilter的Filter,它负责更换原生的HttpSession为自定义实现,比如基于Redis实现

1.搭建项目,集成SpringSession

这里是以SpringBoot为例,你需要搭建一个SpringBoot的应用,然后导入SpringSession整合Redis的依赖

        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.0.5.RELEASE</version>
        </parent>
        <dependencies>
     		<dependency>
                <groupId>org.springframework.session</groupId>
                <artifactId>spring-session-data-redis</artifactId>
                <exclusions>
                    <exclusion>
                        <groupId>io.lettuce</groupId>
                        <artifactId>lettuce-core</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
            <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
            </dependency>
    	    <dependency>
    	     	 <groupId>org.springframework.boot</groupId>
    	         <artifactId>spring-boot-starter-web</artifactId>
    	    </dependency>
        </dependencies>

data-redis默认基于lettuce实现,我这里更换成了jedis

2.配置SpringSession存储方式

然后把session配置存储类型为Redis,以及配置Redis链接信息

    #server.servlet.session.timeout =#会话超时。如果未指定持续时间后缀,则使用秒。
    spring:
      session:
        store-type: redis
      redis:
        host: localhost
        port: 6379
        # 选择redis的数据库的分库
        database: 0
        password: 123456
        #redis连接池配置
        jedis:
          pool:
            max-idle: 10
            min-idle: 5
            max-active: 100
            max-wait: 3000
            timeout: 6005

3.SpringSession的Java配置

最后编写SpringSesson配置类,通过@EnableRedisHttpSession 注解开启SpringSession ,通过该注解,SpringSession会创建一个Servlet过滤器,该过滤器用Spring 定义的Session替换HttpSession 实现Redis存储。

    //开启SpringSession,基于Redis存储,maxInactiveIntervalInSeconds是session失效时间
    @EnableRedisHttpSession(maxInactiveIntervalInSeconds=1800)
    public class SpringSessionConfig {
    
    }

注意:该配置类需要被@SpringBootApplication扫描到

4.Session存储实体类测试

实体类,需要实现Serializable接口

    public class VipUser implements Serializable  {
    	 private Long id;
    	 private String password;
    	 private String nickName;
    	 ...省略...
    }

使用sesson存取一个User对象

    @RestController
    public class LoginController {
    
        @RequestMapping("/login")
        public void login(HttpSession session){
            VipUser user = new VipUser();
            user.setId(1L);
            user.setNickName("王大锤");
            user.setPassword("123456");
            String key = "user";
            //添加属性到Session
            session.setAttribute(key,user);
            //从sesson中取出属性值
            VipUser userFromSession = (VipUser)session.getAttribute(key);
    
            System.out.println(userFromSession.getId()+" , "+userFromSession.getNickName()+" , "+userFromSession.getPassword());
        }
    }

上面的代码很简单,使用session存储一个user , 启动好Redis服务器,使用Postmain访问 /login ,观察Redis中的数据

202309192313303733.png
Redis效果如下:

202309192313308224.png

看到这个效果,说明session确实存储到了Redis,并且可以正常的完成存取操作。

SpringSession的自定义配置

1.定义Redis序列化方式

SpringSession把值存储到Redis中默认情况下使用的是JDK的序列化方式,它要求我们存储的实体类需要实现Serializable接口,我们希望Redis以一种更友好更通用的方式去存储数据,即:JSON,我们可以通过定义序列化器来实现JSON方式的数据存储

    //开启SpringSession,基于Redis存储,maxInactiveIntervalInSeconds是session失效时间
    @EnableRedisHttpSession(maxInactiveIntervalInSeconds=1800)
    public class SpringSessionConfig {
    
    	/**
    	 * 更换序列化器,使用JSON的序列化器
    	 */
    	//@Bean("springSessionDefaultRedisSerializer")
    	public RedisSerializer setSerializer(){
    		return new GenericJackson2JsonRedisSerializer();
    	}
    }

2.定义cookie的序列化器

在前面我们讨论过,在分布式环境中,一个系统由多个子系统组成,如果要实现单点登录就要实现cookie跨域共享,否则在不同的子系统中是获取不到Session的,我们可以通过设置cookie的domain来扩大cookie的作用域,比如:有三个子系统分别是“auth.mall.com”,“goods.mall.com”,“stock.mall.com”,那么我们把cookie的domain设置为“mall.com”,那三个子系统就都可以获取到cookie,从而三个子系统都可以获取到相同的session了。

定义CookieSerializer 序列化器,如下:

    //开启SpringSession,基于Redis存储,maxInactiveIntervalInSeconds是session失效时间
    @EnableRedisHttpSession(maxInactiveIntervalInSeconds=1800)
    public class SpringSessionConfig {
    
    	/**
    	 * 更换序列化器
    	 */
    	//@Bean("springSessionDefaultRedisSerializer")
    	public RedisSerializer setSerializer(){
    		return new GenericJackson2JsonRedisSerializer();
    	}
    
    	/**
    	 * 设置cookie域
    	 */
    
    	@Bean
    	public CookieSerializer cookieSerializer(){
    		DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
    		//cookieSerializer.setCookieMaxAge();
    		cookieSerializer.setCookieName("mysession");	//cookie的名字
    		cookieSerializer.setDomainName("mall.com");		//cookie的域
    		cookieSerializer.setUseHttpOnlyCookie(false);	//只是支持http
    		return cookieSerializer;
    	}
    }

3.测试

202309192313312375.png

文章结束,最后附上一张相对完整的分布式应用登录的流程图,希望对你有所帮助

202309192313316326.png


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文