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()
方法的参数中有 HttpServletRequest
和 HttpServletResponse
,可以像 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)里。
这种按功能划分子包的方式,可以让阅读者比较直观、清晰的了解各个类的作用。
网友评论