场景
前面我们学习了springboot整合redis实现分布式session,对 spring session 有了一个最基本的认识。
有时候我们希望登陆的时候设置对应的 session 信息,然后结合拦截器进行是否登陆校验。
本文就给出一个结合拦截器使用的例子,让我们进一步感受下 spring session 的魅力吧。

实战
maven 依赖
org.springframework.boot spring-boot-starter-parent 1.5.9.RELEASE org.springframework.session spring-session org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-data-redis
类结构
│ Application.java│├─config│ WebConfig.java│├─controller│ ExampleController.java│ UserController.java│├─interceptor│ SessionInterceptor.java│└─util SessionUtil.java
基本方法
- Application.java
package com.github.houbb.spring.session.learn.boot.redis;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession;/** * @author binbin.hou * @since 1.0.0 */@SpringBootApplication@EnableRedisHttpSessionpublic class Application { public static void main(String[] args) { SpringApplication.run(Application.class, args); }}
通过 @EnableRedisHttpSession 注解启用 redis http session 的功能。
ExampleController.java 类和入门案例中一样,此处不再赘述。
- SessionUtil.java
一个 session 构建的基础工具类。
package com.github.houbb.spring.session.learn.boot.redis.util;/** * @author binbin.hou * @since 1.0.0 */public final class SessionUtil { private SessionUtil(){} /** * session key 前缀 */ private static final String SESSION_KEY_PREFIX = "SESSION-KEY-"; public static String buildSessionKey(final String sessionId) { return SESSION_KEY_PREFIX + sessionId; }}
UserController.java
这里我们主要模拟一下用户的登陆和登出。
package com.github.houbb.spring.session.learn.boot.redis.controller;import com.github.houbb.spring.session.learn.boot.redis.util.SessionUtil;import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;import javax.servlet.http.HttpServletRequest;import java.util.UUID;@RestController@RequestMappingpublic class UserController { @RequestMapping("/loginIndex") public String loginIndex(HttpServletRequest req) { return "http://localhost:8080/login?username=guest"; } @RequestMapping("/login") public String login(HttpServletRequest req) { //1. 获取用户名密码 final String username = req.getParameter("username"); //2. 登陆校验 if("guest".equals(username)) { //3. 设置对应的信息 String sessionId = UUID.randomUUID().toString(); String sessionKey = SessionUtil.buildSessionKey(sessionId); // value 可以设置对应的权限值等等 String value = username; req.getSession().setAttribute(sessionKey, value); // 将 token 返回给前端 // 可以设置再页面的隐藏域中 return "sessionId: " + sessionId; } return "login failed"; } @RequestMapping("/logout") public String logout(HttpServletRequest req) { String sessionId = req.getParameter("sessionId"); req.getSession().removeAttribute(SessionUtil.buildSessionKey(sessionId)); return "登出成功"; }}
登陆首页
loginIndex 为登陆的首页,为了简单,我们直接显示需要请求的地址即可:
http://localhost:8080/login?username=guest
登陆
校验对应的 username,并且设置对应的 session 信息。
并且将这个 token 返回给前端,用户在登出的时候需要指定这个 token 做校验。
登出
清空对应的 session 信息。
拦截器
SessionInterceptor.java
这里我们是通过对应的 token 获取。
当然,对于 spring session 也可以直接根据 JSESSIONID 这种更加优雅的方式设置和获取。
如果信息不存在,则直接返回到登陆页面。
package com.github.houbb.spring.session.learn.boot.redis.interceptor;import com.github.houbb.spring.session.learn.boot.redis.util.SessionUtil;import org.springframework.stereotype.Component;import org.springframework.util.StringUtils;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.ServletException;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;import java.io.Writer;/** * @author binbin.hou * @since 1.0.0 */@Componentpublic class SessionInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { String sessionId = httpServletRequest.getParameter("sessionId"); if(StringUtils.isEmpty(sessionId)) { handleForbidden(httpServletRequest, httpServletResponse, "请重新登陆"); return false; } // 获取登陆信息 String sessionKey = SessionUtil.buildSessionKey(sessionId); String username = (String) httpServletRequest.getSession().getAttribute(sessionKey); if(StringUtils.isEmpty(username)) { handleForbidden(httpServletRequest, httpServletResponse, "请重新登陆"); return false; } return true; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { } private void handleForbidden(HttpServletRequest request, HttpServletResponse response, String info){ // 有这个值是异步请求,没有是同步请求。 String xRequest = request.getHeader("X-Requested-With"); if(!StringUtils.isEmpty(xRequest)){ //异步处理 response.setCharacterEncoding("UTF-8"); try { Writer writer = response.getWriter(); writer.write(info); response.setStatus(403); } catch (IOException e) { e.printStackTrace(); } }else{ try { // 可以跳转到登陆页面 request.getRequestDispatcher("loginIndex") .forward(request, response); } catch (ServletException | IOException e) { e.printStackTrace(); } } }}
拦截器设置
我们定义了拦截器,当然需要指定对应的生效范围:
package com.github.houbb.spring.session.learn.boot.redis.config;import com.github.houbb.spring.session.learn.boot.redis.interceptor.SessionInterceptor;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.core.Ordered;import org.springframework.web.filter.CharacterEncodingFilter;import org.springframework.web.servlet.config.annotation.InterceptorRegistry;import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;/** * @author binbin.hou * @since 1.0.0 */@Configurationpublic class WebConfig extends WebMvcConfigurerAdapter { @Autowired private SessionInterceptor sessionInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(sessionInterceptor) .addPathPatterns("/**") .excludePathPatterns("/loginIndex") .excludePathPatterns("/login"); } @Bean public CharacterEncodingFilter initializeCharacterEncodingFilter() { CharacterEncodingFilter filter = new CharacterEncodingFilter(); filter.setEncoding("UTF-8"); filter.setForceEncoding(true); return filter; } @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("forward:/loginIndex"); registry.setOrder(Ordered.HIGHEST_PRECEDENCE); super.addViewControllers(registry); }}
我们排除 /loginIndex 和 /login 这两个请求地址的 session 拦截校验,当然也可以根据实际情况排除静态文件。
registry.addViewController("/").setViewName("forward:/loginIndex"); 这句话可以将默认的首页重定向到登陆页面。
测试验证
首页
浏览器访问 http://localhost:8080/
页面显示:
http://localhost:8080/login?username=guest
登陆
我们直接输入 http://localhost:8080/login?username=guest
页面返回对应的 token:
sessionId: 5251161a-d946-41e5-98fc-eae2e5c01960
登出
我们登出的时候,带上这个参数:http://localhost:8080/logout?sessionId=5251161a-d946-41e5-98fc-eae2e5c01960
页面提示:
登出成功
如果我们再次请求,页面就会跳转到登陆首页,因为这个时候的 session 已经被清空了。
小结
这一节我们展示了如何结合拦截器使用 spring session,这也是目前比较主流的做法。
对于很多讲解到这里一般就结束了,不过老马实际上是为了引入接下来的内容。
spring session 底层的实现原理是怎样的?
下一节将和各位小伙伴们一起学习下 spring session 的源码。
希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。
我是老马,期待与你的下次相遇。
