springboot中使用spring-session實現共享會話到redis(二)
上篇文章介紹了springboot中整合spring-session實現了將session分散式存到redis中。這篇在深入介紹一些spring-session的細節。
1、session超時:
在tomcat中,如果要設定session的超時,我們可以在web.xml或者springboot的application.properties中直接配置即可,例如在springboot中設定:
server.session.timeout=1800
但引入了spring-session後,這個配置將不再起作用, 我們需要寫一個如下的配置類:import org.springframework.context.annotation.Configuration; import org.springframework.session.data.redis.config.annotation.web.http.EnableRedisHttpSession; @Configuration //maxInactiveIntervalInSeconds 預設是1800秒過期,這裡測試修改為60秒 @EnableRedisHttpSession(maxInactiveIntervalInSeconds=60) public class RedisSessionConfig{ }
注:如果不修改session超時,可以不用該配置類。
2、在springboot中使用spring-session完成登入、登出等功能:
1)定義User實體類:
public class User implements Serializable { private static final long serialVersionUID = 1629629499205758251L; private Long id; private String name; private String pwd; private String note; private Integer dateAuth; private Integer tableAuth; //set/get 方法
注:該類需要序列化,因為spring-session會將該物件序列化後儲存到redis中。
2)UserController:
@RequestMapping("/user") @Controller public class UserController { private static final Logger logger = LoggerFactory.getLogger(UserController.class); @Autowired private UserService userService; /** * 退出 * @param request * @return */ @RequestMapping("/loginOut") @ResponseBody public ResponseMessage loginOut(HttpServletRequest request, HttpServletResponse response) { HttpSession session = request.getSession(); if (session != null) { session.setAttribute(session.getId(), null); } return ResponseMessage.ok(Constants.CODE_SUCCESS,null); } /** * 登入驗證 * @param request * @return */ @RequestMapping("/login") public ModelAndView login(HttpServletRequest request,Model model) { String name = request.getParameter("username"); String password = request.getParameter("password"); //TODO校驗 Map<String,String> map = new HashMap<>(); map.put("name",name); map.put("pwd",password); User user = null; try { user = userService.login(map); } catch (Exception e) { logger.error("user login is error...",e); } if (user != null) { HttpSession session = request.getSession(); session.setAttribute(session.getId(),user); model.addAttribute("user", user); logger.info("user login is success,{}",name); return new ModelAndView("redirect:/index"); } else { request.setAttribute("errorInfo", "驗證失敗"); return new ModelAndView("login/login"); } } }
注:spring-session會通過攔截器的方式往session物件中存放、移除sessionId(session.getId()),所以我們在登入、登出、攔截器中會呼叫session.setAttribute(session.getId(),user);來判斷。
3)session攔截器:
public class SessionInterceptor extends HandlerInterceptorAdapter {
private static String[] IGNORE_URI = {"/login.jsp", "/login/","/login","/loginIndex", "/error"};
private static Logger log = LoggerFactory.getLogger(SessionInterceptor.class);
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
boolean flag = false;
String url = request.getRequestURL().toString();
/*String currentURL = request.getRequestURI(); // 取得根目錄所對應的絕對路徑:
String targetURL = currentURL.substring(currentURL.lastIndexOf("/"), currentURL.length());// 擷取到當前檔名用於比較
String currentURLTemp = currentURL.replaceAll("/iis/", "");*/
for (String s : IGNORE_URI) {
if (url.contains(s)) {
flag = true;
break;
}
}
if (!flag) {
HttpSession session = request.getSession();
Object obj = session.getAttribute(session.getId());//Constants.SESSION_USER
if (null == obj) {//未登入
String servletPath = request.getServletPath();
log.error("session失效,當前url:" + url+";module Paht:"+servletPath);
if (request.getHeader("x-requested-with") != null &&
request.getHeader("x-requested-with").equalsIgnoreCase("XMLHttpRequest")){
response.setHeader("sessionstatus", "timeout");//在響應頭設定session狀態
response.setCharacterEncoding("UTF-8");
response.setContentType("text/html;charset=UTF-8");
response.getWriter().print("error");
} else {
response.sendRedirect(request.getContextPath()+"/user/loginIndex");
}
return false;
} else {
/*User user = (User)obj;
if(!RightUtil.hasRight(currentURLTemp, request)){
if(!"iisAdminTmp".equals(user.getName()) && !"/index".equals(targetURL)){
//response.sendRedirect(request.getContextPath()+"/login/login");//應該返回到沒有許可權的頁面
//request.getRequestDispatcher("/login/login").forward(request, response);
return false;
}
}*/
}
}
return super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
super.postHandle(request, response, handler, modelAndView);
}
}
說明:
我們知道spring-session會自動注入springSessionRepositoryFilter過濾器,每一次的請求都由他來過濾,其本質是:對每一個請求的request進行了一次封裝。那麼,在Controller裡面拿出的request實際上是封裝後的request,
呼叫request.getSession()的時候,實際上拿到是Spring封裝後的session。這個session則儲存在redis資料庫中。
應用通過 getSession(boolean create) 方法來獲取 session 資料,引數 create 表示 session 不存在時是否建立新的 session 。 getSession 方法首先從請求的 “.CURRENT_SESSION” 屬性來獲取 currentSession ,沒有 currentSession ,則從 request 取出 sessionId ,然後讀取 spring:session:sessions:[sessionId] 的值,同時根據 lastAccessedTime 和 MaxInactiveIntervalInSeconds 來判斷這個 session 是否過期。如果 request 中沒有 sessionId ,說明該使用者是第一次訪問,會根據不同的實現,如 RedisSession ,MongoExpiringSession ,GemFireSession 等來建立一個新的 session 。 另外, 從 request 取 sessionId 依賴具體的 HttpSessionStrategy 的實現,spring session 給了兩個預設的實現 CookieHttpSessionStrategy 和 HeaderHttpSessionStrategy ,即從 cookie 和 header 中取出 sessionId 。
3、spring-session在redis中的儲存結構:
spring:session是預設的Redis HttpSession字首(redis中,我們常用’:’作為分割符)。如上圖,每一個session都會建立3組資料:
1)spring:session:sessions:6e4fb910-34f7-453d-a8c6-2b3cd192e051
hash結構,儲存了session資訊(實體類的序列化資料)、maxInactiveInterval、建立時間、lastAccessedTime四部分資訊。
2)spring:session:sessions:expires:6e4fb910-34f7-453d-a8c6-2b3cd192e051
string結構,value為空。
3)spring:session:expirations:1529395440000:
set結構,儲存過期時間記錄
注:在spring-session中提到,由於redis的ttl刪除key是一個被動行為,所以才會引入了expirations這個key,來主動進行session的過期行為判斷。
springsession相關參考:
https://segmentfault.com/a/1190000011091273#articleHeader14
https://blog.csdn.net/lxhjh/article/details/78048201
http://www.infoq.com/cn/articles/Next-Generation-Session-Management-with-Spring-Session
https://qbgbook.gitbooks.io/spring-boot-reference-guide-zh/IV.%20Spring%20Boot%20features/38.%20Spring%20Session.html