spring-security使用-session共享(六)


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());
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM