分布式session

分布式session

(1) session 原理

session存储在服务端,jsessionId存在客户端,每次通过jsessionid取出保存的数据

问题:但是正常情况下session不可跨域,它有自己的作用范围

这个session被sessionManager管理着

JsessionId列 说明
Value XXXXXX…
Domain gulimall.com要放大域名作用域
Path /
Expires/Max-Age 40

JsessionId列 说明
Value XXXXXX…
Domain gulimall.com要放大域名作用域
Path /
Expires/Max-Age 40

(2) 分布式session解决方案

session要能在不同服务和同服务的集群的共享

1) session复制

用户登录后得到session后,服务把session也复制到别的机器上,显然这种处理很不好

2) hash一致性

根据用户,到指定的机器上登录。但是远程调用还是不好解决

3) redis统一存储

最终的选择方案,把session放到redis中

3) SpringSession整合redis

https://spring.io/projects/spring-session-data-redis

https://docs.spring.io/spring-session/docs/2.4.2/reference/html5/#modules

通过SpringSession修改session的作用域

  1. 环境搭建
<dependency>
    <groupId>org.springframework.session</groupId>
    <artifactId>spring-session-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

修改配置

spring.session.store-type=redis
server.servlet.session.timeout=30m
spring.redis.host=192.168.56.10

添加注解


@EnableRedisHttpSession //创建了一个springSessionRepositoryFilter ,负责将原生HttpSession 替换为Spring Session的实现
public class GulimallAuthServerApplication {

但是现在还有一些问题:

序列化的问题
cookie的domain的问题

  1. 扩大session作用域

由于默认使用jdk进行序列化,通过导入RedisSerializer修改为json序列化

并且通过修改CookieSerializer扩大session的作用域至**.gulimall.com

@Configuration
public class GulimallSessionConfig {

    @Bean // redis的json序列化
    public RedisSerializer<Object> springSessionDefaultRedisSerializer() {
        return new GenericJackson2JsonRedisSerializer();
    }

    @Bean // cookie
    public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("GULISESSIONID"); // cookie的键
        serializer.setDomainName("gulimall.com"); // 扩大session作用域,也就是cookie的有效域
        return serializer;
    }
}

(4) SpringSession核心原理 - 装饰者模式

网上百度一下:https://blog.csdn.net/m0_46539364/article/details/110533408
就是分析@EnableRedisHttpSession,


@Import({RedisHttpSessionConfiguration.class})
@Configuration( proxyBeanMethods = false)
public @interface EnableRedisHttpSession {
public class RedisHttpSessionConfiguration 
    extends SpringHttpSessionConfiguration // 继承
    implements 。。。{
    
    // 后面SessionRepositoryFilter会构造时候自动注入他
    @Bean // 操作session的方法,如getSession()  deleteById()
    public RedisIndexedSessionRepository sessionRepository() {

SessionRepositoryFilter,每个请求都要经过该filter

public class SpringHttpSessionConfiguration 
    implements ApplicationContextAware {

    @Bean
    public <S extends Session> SessionRepositoryFilter<? extends Session> springSessionRepositoryFilter(SessionRepository<S> sessionRepository) { // 注入前面的bean
        SessionRepositoryFilter<S> sessionRepositoryFilter = new SessionRepositoryFilter(sessionRepository);
        sessionRepositoryFilter.setHttpSessionIdResolver(this.httpSessionIdResolver);
        return sessionRepositoryFilter;
    }

前面我们@Bean注入了sessionRepositoryFilter,他是一个过滤器,那我们需要知道他过滤做了什么事情:

原生的获取session时是通过HttpServletRequest获取的
这里对request进行包装,并且重写了包装request的getSession()方法

@Override // SessionRepositoryFilter.java
protected void doFilterInternal(HttpServletRequest request,
                                HttpServletResponse response, 
                                FilterChain filterChain) {
    
    request.setAttribute(SESSION_REPOSITORY_ATTR, this.sessionRepository);

    //对原生的request、response进行包装
    // SessionRepositoryRequestWrapper.getSession()
    SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(
        request, response, this.servletContext);
    SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(
        wrappedRequest, response);

    try {
        filterChain.doFilter(wrappedRequest, wrappedResponse);
    }
    finally {
        wrappedRequest.commitSession();
    }
}

绣花前面的代码,controller层加参数HttpSession,直接session.setAttribute(“user”,user)即可

前端页面的显示可以用

  • (5)session的保存

    @GetMapping({"/login.html","/","/index","/index.html"}) // auth
    public String loginPage(HttpSession session){
        // 从会话从获取loginUser
        Object attribute = session.getAttribute(AuthServerConstant.LOGIN_USER);// "loginUser";
        System.out.println("attribute:"+attribute);
        if(attribute == null){
            return "login";
        }
        System.out.println("已登陆过,重定向到首页");
        return "redirect:http://gulimall.com";
    }
    
    
    @PostMapping("/login") // auth
    public String login(UserLoginVo userLoginVo,
                        RedirectAttributes redirectAttributes,
                        HttpSession session){
        // 远程登录
        R r = memberFeignService.login(userLoginVo);
        if(r.getCode() == 0){
            // 登录成功
            MemberRespVo respVo = r.getData("data", new TypeReference<MemberRespVo>() {});
            // 放入session  // key为loginUser
            session.setAttribute(AuthServerConstant.LOGIN_USER, respVo);//loginUser
            log.info("\n欢迎 [" + respVo.getUsername() + "] 登录");
            // 登录成功重定向到首页
            return "redirect:http://gulimall.com";
        }else {
            HashMap<String, String> error = new HashMap<>();
            // 获取错误信息
            error.put("msg", r.getData("msg",new TypeReference<String>(){}));
            redirectAttributes.addFlashAttribute("errors", error);
            return "redirect:http://auth.gulimall.com/login.html";
        }
    }
    
    
  • posted @ 2021-05-07 15:05  刚刚好。  阅读(307)  评论(0)    收藏  举报