Spring Session

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)里。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,825评论 4 377
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,887评论 2 308
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,425评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,801评论 0 224
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,252评论 3 299
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,089评论 1 226
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,216评论 2 322
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 31,005评论 0 215
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,747评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,883评论 2 255
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,354评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,694评论 3 265
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,406评论 3 246
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,222评论 0 9
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,996评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,242评论 2 287
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 36,017评论 2 281

推荐阅读更多精彩内容