session共享的幾種方案
方案一
不放在服務器應用中管理,放在第三方統一管理,如redis,數據庫等,現在主流都是放在redis 因為redis高效qps最高能達到10萬+
方案二
session 拷貝,集群情況某一台服務器session發生改變,通知其他服務器,這樣會有個問題,如果集群實例太多,要通知太多的服務器,而且原子性也需要保證(所有服務要么一起成功要么一起失敗)
方案三
粘滯會話,我第一家公司就是這樣處理,根據用戶id nginx都路由到一個實例
自定義
使用spring自帶的實現
通過SpringSessionBackedSessionRegistry 通過關鍵字百度搜搜很多方案
自定義SessionRegistory
可以參考默認的org.springframework.security.core.session.SessionRegistryImpl 因為是基於應用的 sessionCRUD都是操作應用內數據結構我們
1.接口定義
public interface SessionRegistry { //獲得所有會話sessionId List<Object> getAllPrincipals(); //獲得指定用戶的會話列表 List<SessionInformation> getAllSessions(Object userId, boolean var2); //獲得指定sessionId的會話 SessionInformation getSessionInformation(String sessionId); //刷新訪問時間 void refreshLastRequest(String var1); //注冊會話 void registerNewSession(String sessionId, Object var2); //刪除會話 void removeSessionInformation(String var1); }
2.實現
public class RedisSessionRegistryImpl implements SessionRegistry, ApplicationListener<SessionDestroyedEvent> { private RedisTemplate redisTemplate; //維護所有sessionId列表的zset score為過期時間 private final static String SESSION_LIST_KEY = "session:list"; //維護指定用戶sessionId列表的zset score為過期時間 private final static String SESSION_USER_LIST_KEY = "session:list:userId:%s"; //存儲sessionId對應的數據 private final static String SESSION_ITEM_KEY = "session:item:%s"; public RedisSessionRegistryImpl(RedisTemplate redisTemplate) { this.redisTemplate = redisTemplate; } @Override public void onApplicationEvent(SessionDestroyedEvent sessionDestroyedEvent) { String sessionId = sessionDestroyedEvent.getId(); this.removeSessionInformation(sessionId); } /** * 獲得所有會話 * * @return */ @Override public List<Object> getAllPrincipals() { Set<Object> sessionIds = redisTemplate.boundZSetOps(SESSION_LIST_KEY).range(0, -1); if (CollectionUtils.isEmpty(sessionIds)) { return null; } return sessionIds.stream().collect(Collectors.toList()); } /** * 獲得指定用戶列表的會話 * * @param includeExpiredSessions * @return */ @Override public List<SessionInformation> getAllSessions(Object userId, boolean includeExpiredSessions) { Set<String> sessionsUsedByPrincipal = redisTemplate.boundZSetOps(String.format(SESSION_USER_LIST_KEY, userId)).range(0, -1); if (sessionsUsedByPrincipal == null) { return Collections.emptyList(); } else { List<SessionInformation> list = new ArrayList(sessionsUsedByPrincipal.size()); Iterator var5 = sessionsUsedByPrincipal.iterator(); while (true) { SessionInformation sessionInformation; do { do { if (!var5.hasNext()) { return list; } String sessionId = (String) var5.next(); sessionInformation = this.getSessionInformation(sessionId); } while (sessionInformation == null); } while (!includeExpiredSessions && sessionInformation.isExpired()); list.add(sessionInformation); } } } /** * 根據sessionId獲得會話 * * @param sessionId * @return */ @Override public SessionInformation getSessionInformation(String sessionId) { Object jsonValue = redisTemplate.opsForValue().get(String.format(SESSION_ITEM_KEY, sessionId)); if (StringUtils.isEmpty(jsonValue)) { return null; } JSONObject jsonObject= JSON.parseObject(jsonValue.toString()); SessionInformation sessionInformation=new SessionInformation(JSON.parseObject(jsonObject.getString("principal"), UserInfoDto.class),jsonObject.getString("sessionId"),jsonObject.getDate("lastRequest")); if(jsonObject.getBoolean("expired")){ sessionInformation.expireNow(); } return sessionInformation; } /** * 刷新session過期時間 * * @param sessionId */ @Override public void refreshLastRequest(String sessionId) { SessionInformation info = this.getSessionInformation(sessionId); if (info != null) { info.refreshLastRequest(); } registerNewSession(sessionId, info); } /** * 注冊session * * @param sessionId * @param principal */ @Override public void registerNewSession(String sessionId, Object principal) { SessionInformation sessionInformation =new SessionInformation(principal, sessionId, new Date()); redisTemplate.opsForValue().set(String.format(SESSION_ITEM_KEY, sessionId), JSON.toJSONString(sessionInformation)); redisTemplate.boundZSetOps(SESSION_LIST_KEY).add(sessionId, System.currentTimeMillis() + 30000); String username; //因為第一次傳入的是username 登錄成功傳入的是我們的UserDetails對象 if(principal instanceof UserDetails){ username=((UserDetails)principal).getUsername(); }else{ username=principal.toString(); } redisTemplate.boundZSetOps(String.format(SESSION_ITEM_KEY, username)).add(sessionId, System.currentTimeMillis() + 30000); } /** * 從會話中移除 * * @param sessionId */ @Override public void removeSessionInformation(String sessionId) { SessionInformation sessionInformation = getSessionInformation(sessionId); redisTemplate.delete(String.format(SESSION_ITEM_KEY, sessionId)); redisTemplate.boundZSetOps(SESSION_LIST_KEY).add(sessionId, System.currentTimeMillis() + 30000); if (sessionInformation != null) { redisTemplate.boundZSetOps(String.format(SESSION_ITEM_KEY, sessionInformation.getPrincipal())).add(sessionId, System.currentTimeMillis() + 30000); } } }
3.替換
@Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest().authenticated() .and() .rememberMe() .key("system") .and() .formLogin() .authenticationDetailsSource(new MyWebAuthenticationDetailsSource()) .usernameParameter("loginName") .passwordParameter("loginPassword") .defaultSuccessUrl("/hello") .failureForwardUrl("/loginFail") .failureUrl("/login.html") .permitAll()//不攔截 .and() .csrf()//記得關閉 .disable() .sessionManagement() .maximumSessions(1) .maxSessionsPreventsLogin(true) .sessionRegistry(new RedisSessionRegistryImpl(redisTemplate)); }
源碼
回頭看《spring-security使用-同一個賬號只允許登錄一次(五)》的源碼
1.org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy#onAuthentication
public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) throws SessionAuthenticationException { SessionAuthenticationStrategy delegate; //遍歷delegateStrategies 調用onAuthentication方法 for(Iterator var4 = this.delegateStrategies.iterator(); var4.hasNext(); delegate.onAuthentication(authentication, request, response)) { delegate = (SessionAuthenticationStrategy)var4.next(); if (this.logger.isDebugEnabled()) { this.logger.debug("Delegating to " + delegate); } } }
2.org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy#onAuthentication
public class RegisterSessionAuthenticationStrategy implements SessionAuthenticationStrategy { private final SessionRegistry sessionRegistry; public RegisterSessionAuthenticationStrategy(SessionRegistry sessionRegistry) { Assert.notNull(sessionRegistry, "The sessionRegistry cannot be null"); this.sessionRegistry = sessionRegistry; } public void onAuthentication(Authentication authentication, HttpServletRequest request, HttpServletResponse response) { this.sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal()); } }