1. 程式人生 > >springboot中使用spring-session實現共享會話到redis(二)

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