美文网首页
Spring Session

Spring Session

作者: laochonger | 来源:发表于2021-01-28 21:08 被阅读0次

    Spring Session API

    我们知道Cookie放在客户端,可以存储用户登录信息,主要用于辨别用户身份。但如果真的把用户ID、登录状态等重要信息放入cookie,会带来安全隐患。
    采用Session会话机制可以解决这个问题,将这些重要信息存在服务端,从而避免安全隐患。


    image.png

    使用会话机制时,Cookie作为session id的载体与客户端通信。

    • 名字为JSESSIONID 的 cookie,是专门用来记录用户session的。JSESSIONID 是标准的、通用的名字。

    在了解 Session 与 Cookie 之间的关系后,我们来学习如何使用 Session,也分为读、写两种操作。

    读操作

    与 cookie 相似,从 HttpServletRequest 对象中取得 HttpSession 对象,使用的语句是 request.getSession()

    但不同的是,返回结果不是数组,是对象。在 attribute 属性中用 key -> value 的形式存储多个数据。

    假设存储登录信息的数据 key 是 userLoginInfo,那么语句就是 session.getAttribute("userLoginInfo")。(一个映射)

    登录信息类

    登录信息实例对象因为要在网络上传输,就必须实现序列化接口 Serializable ,否则不实现的话会报错。

    登录信息类需要根据具体的需要设计属性字段。下列代码的两个属性仅供演示。

    import java.io.Serializable;
    
    public class UserLoginInfo implements Serializable {
      private String userId;
      private String userName;
    }
    

    操作代码

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    @RequestMapping("/songlist")
    public Map index(HttpServletRequest request, HttpServletResponse response) {
      Map returnData = new HashMap();
      returnData.put("result", "this is song list");
    
      // 取得 HttpSession 对象
      HttpSession session = request.getSession();
      // 读取登录信息
      UserLoginInfo userLoginInfo = (UserLoginInfo)session.getAttribute("userLoginInfo");
      if (userLoginInfo == null) {
        // 未登录
        returnData.put("loginInfo", "not login");
      } else {
        // 已登录
        returnData.put("loginInfo", "already login");
      }
    
      return returnData;
    }
    

    写操作

    假设登录成功,怎么记录登录信息到 Session 呢?

    既然从 HttpSession 对象中读取登录信息用的是 getAttribute() 方法,那么写入登录信息就用 setAttribute() 方法。

    下列代码演示了使用 Session 完成登录的过程,略去了校验用户名和密码的步骤(实际项目中需要):

    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    @RequestMapping("/loginmock")
    public Map loginMock(HttpServletRequest request, HttpServletResponse response) {
      Map returnData = new HashMap();
    
      // 假设对比用户名和密码成功
      // 仅演示的登录信息对象
      UserLoginInfo userLoginInfo = new UserLoginInfo();
      userLoginInfo.setUserId("12334445576788");
      userLoginInfo.setUserName("ZhangSan");
      // 取得 HttpSession 对象
      HttpSession session = request.getSession();
      // 写入登录信息
      session.setAttribute("userLoginInfo", userLoginInfo);
      returnData.put("message", "login successfule");
    
      return returnData;
    }
    
    • PS:
      Cookie 存放在客户端,一般不能超过 4kb ,要特别注意,放太多的内容会导致出错;而 Session 存放在服务端,没有限制,不过基于服务端的性能考虑也不能放太多的内容。

    Spring Session 配置

    • CookCookie 作为 session id 的载体,也可以修改属性。

    前置知识点:配置

    application.properties 是 SpringBoot 的标准配置文件,配置一些简单的属性。同时,SpringBoot 也提供了编程式的配置方式,主要用于配置 Bean 。

    基本格式:

    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    @Configuration
    public class SpringHttpSessionConfig {
      @Bean
      public TestBean testBean() {
        return new TestBean();
      }
    }
    

    在类上添加@Configuration 注解,就表示这是一个配置类,系统会自动扫描并处理。

    在方法上添加 @Bean 注解,表示把此方法返回的对象实例注册成 Bean。

    • @Service 等写在类上的注解一样,都表示注册 Bean

    Session 配置

    依赖库

    先在 pom.xml 文件中增加依赖库:

    <!-- spring session 支持 -->
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-core</artifactId>
    </dependency>
    

    配置类

    在类上额外添加一个注解:@EnableSpringHttpSession ,开启 session 。然后,注册两个 bean

    • CookieSerializer:读写 Cookies 中的 SessionId 信息
    • MapSessionRepository:Session 信息在服务器上的存储仓库。
    import org.springframework.session.MapSessionRepository;
    import org.springframework.session.config.annotation.web.http.EnableSpringHttpSession;
    import org.springframework.session.web.http.CookieSerializer;
    import org.springframework.session.web.http.DefaultCookieSerializer;
    
    import java.util.concurrent.ConcurrentHashMap;
    
    @Configuration
    @EnableSpringHttpSession
    public class SpringHttpSessionConfig {
      @Bean
      public CookieSerializer cookieSerializer() {
        DefaultCookieSerializer serializer = new DefaultCookieSerializer();
        serializer.setCookieName("JSESSIONID");
            // 用正则表达式配置匹配的域名,可以兼容 localhost、127.0.0.1 等各种场景
        serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$");
        serializer.setCookiePath("/");
        serializer.setUseHttpOnlyCookie(false);
        // 最大生命周期的单位是分钟
        serializer.setCookieMaxAge(24 * 60 * 60);
        return serializer;
      }
    
      // 当前存在内存中
      @Bean
      public MapSessionRepository sessionRepository() {
        return new MapSessionRepository(new ConcurrentHashMap<>());
      }
    }
    

    想必大家已经了解了 Cookie 各属性值的作用,这里就不赘述了。

    代码有些长,想探究为什么这么用,可以 点此阅读官方文档


    Spring Request 拦截器

    实际的项目中,会有大量的页面功能是需要判断用户是否登录的。让每个页面都判断是否登录过于繁琐,不利于维护。
    所以需要一种统一处理相同逻辑的机制,Spring提供了HandlerInterceptor(拦截器)满足这种场景的需求。
    实现拦截器有三个步骤

    一、创建拦截器

    HandlerInterceptor 接口。可以在三个点进行拦截:

    • 1.Controller方法执行之前。这是最常用的拦截点。例如是否登录的验证就要在 preHandle()方法中处理。
    • 2.Controller方法执行之后。例如记录日志、统计方法执行时间等,就要在 postHandle() 方法中处理。
    • 3.整个请求完成后。不常用的拦截点。例如统计整个请求的执行时间的时候用,在 afterCompletion 方法中处理。
      请看下列示例代码:
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    public class InterceptorDemo implements HandlerInterceptor {
    
      // Controller方法执行之前
      @Override
      public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    
        // 只有返回true才会继续向下执行,返回false取消当前请求
        return true;
      }
    
      //Controller方法执行之后
      @Override
      public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
          ModelAndView modelAndView) throws Exception {
    
      }
    
      // 整个请求完成后(包括Thymeleaf渲染完毕)
      @Override
      public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
    
      }
    }
    

    preHandle()方法的参数中有 HttpServletRequestHttpServletResponse,可以像 control 中一样使用 Session

    二、实现 WebMvcConfigurer

    创建一个类实现 WebMvcConfigurer,并实现 addInterceptors() 方法。这个步骤用于管理拦截器。

    • 注意:实现类要加上 @Configuration 注解,让框架能自动扫描并处理。

    管理拦截器,比较重要的是为拦截器设置拦截范围。常用 addPathPatterns("/**") 表示拦截所有的 URL 。

    当然也可以调用 excludePathPatterns() 方法排除某些 URL,例如登录页本身就不需要登录,需要排除。

    import org.springframework.context.annotation.Configuration;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    
    @Configuration
    public class WebAppConfigurerDemo implements WebMvcConfigurer {
    
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
        // 多个拦截器组成一个拦截器链
        // 仅演示,设置所有 url 都拦截
        registry.addInterceptor(new UserInterceptor()).addPathPatterns("/**");
      }
    }
    

    这样拦截器就添加完毕了。

    *学习拦截器,要注意理解和体会 拦截器 与 管理拦截器 分开的思想。

    • 思考一下:如果不分开处理,由拦截器本身决定在什么情况下进行拦截,是否更好?

    通常拦截器,会放在一个包(例如interceptor)里。而用于管理拦截器的配置类,会放在另一个包(例如config)里。

    这种按功能划分子包的方式,可以让阅读者比较直观、清晰的了解各个类的作用。

    相关文章

      网友评论

          本文标题:Spring Session

          本文链接:https://www.haomeiwen.com/subject/odyuaktx.html