1. 程式人生 > >spring boot security 防止使用者重複登入(原創)

spring boot security 防止使用者重複登入(原創)

原理:在認證成功通過後,在顯示登入成功頁面之前,也就是在SavedRequestAwareAuthenticationSuccessHandler類中操作。

新增一個集合sessionMap 用於儲存認證成功的會話,鍵名為會話ID,

每次有使用者登入認證通過都要判斷一下是否重複登入,如果不是繼續執行,將會話儲存在集合sessionMap裡。

如果是就踢除之前登入過的使用者的會話,將舊的會話從集合sessionMap裡刪除,還有一步就是將舊的會話從sessionRegistry裡刪除(不然你如果要獲取所有認證通過的使用者,還會從sessionRegistry中獲取舊使用者),然後將自己的新會話儲存到集合sessionMap裡。(每次認證通過的使用者會話都會儲存到sessionRegistry裡,所以不必自己再寫一遍)

 

package com.mayocase.handler;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.session.SessionRegistryImpl;
import org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler;
import org.springframework.stereotype.Component;

import com.mayocase.domain.SysUser;
import com.mayocase.util.SessionUtil;

//登入使用者認證通過後,顯示登入成功頁面前,做的操作。
@Component
public class MyAuthenctiationSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {

	private static final Logger logger = LoggerFactory.getLogger(MyAuthenctiationSuccessHandler.class);

	// key為sessionId,value為HttpSession,使用static,定義靜態變數,使之程式執行時,一直存在記憶體中。
	// 儲存所有已經登入使用者的會話(每個瀏覽器一個會話)
	public static HashMap<String, HttpSession> sessionMap = new HashMap<String, HttpSession>();

	@Autowired
	// @Qualifier("sessionRegistry")
	private SessionRegistry sessionRegistry;

	// @Bean(name="sessionRegistry",value="sessionRegistry")
	@Bean
	// @Bean(name="sessionRegistry")
	public SessionRegistry getSessionRegistry() {
		return new SessionRegistryImpl();
	}

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
			Authentication authentication) throws ServletException, IOException {

		// 1.登入認證成功後,獲取使用者名稱
		//(只能在認證成功通過後,才能獲得sc,不然在CustomUserService implements UserDetailsService的loadUserByUsername方法中是第二次才能獲取到)
		SecurityContext sc = SecurityContextHolder.getContext();
		String currentuser = ((SysUser) sc.getAuthentication().getPrincipal()).getUsername();
		logger.info("當前登入使用者:" + currentuser);

		
		// 2.先判斷使用者是否重複登入
		Iterator<Entry<String, HttpSession>> iterator = sessionMap.entrySet().iterator();
		while(iterator.hasNext()) {
			Map.Entry<String, HttpSession> entry = iterator.next();
			HttpSession session = entry.getValue();
			// 2.1 判斷session中所包含的使用者名稱稱是否有當前登入使用者
			String username = SessionUtil.getUserName(session);
			if (currentuser.equals(username)) {
				logger.info("使用者:" + currentuser + "已經在其它地方登入過,將踢除!");
				SessionUtil.expireSession(session);
				logger.info("刪除的會話:"+entry.getKey());
				// 2.2 從sessionMap中踢除會話
				iterator.remove();
				// 2.3 從sessionRegistry中踢除會話
				sessionRegistry.removeSessionInformation(session.getId());
			}
		}
		
		/*//以下這種方法會引起java.util.ConcurrentModificationException: null 錯誤,    HashMap
		// 2.先判斷使用者是否重複登入
		for (Entry<String, HttpSession> entry : sessionMap.entrySet()) {
			HttpSession session = entry.getValue();
			// 2.1 判斷session中所包含的使用者名稱稱是否有當前登入使用者
			String username = SessionUtil.getUserName(session);
			if (currentuser.equals(username)) {
				logger.info("使用者:" + currentuser + "已經在其它地方登入過,將踢除!");
				SessionUtil.expireSession(session);
				logger.info(entry.getKey());
				sessionMap.remove(entry.getKey());//這裡會引起同步錯誤
				sessionRegistry.removeSessionInformation(session.getId());
			}
		}*/

		// 3.將當前session儲存到sessionMap中
		logger.info("將當前會話:" + request.getSession().getId() + ",儲存到sessionMap");
		sessionMap.put(request.getSession().getId(), request.getSession());
		for (Entry<String, HttpSession> entry : sessionMap.entrySet()) {
			logger.info("顯示已經儲存的sessionMap:Key: " + entry.getKey() + " Value: " + entry.getValue());
		}

		// 4.列印所有認證通過的使用者(包含重複登入的,不過上面已經踢除了)
		List<Object> principals = sessionRegistry.getAllPrincipals();
  		List<String> usersNamesList = new ArrayList<String>();
  		for (Object principal: principals) {
  		    if (principal instanceof SysUser) {
  		        usersNamesList.add(((SysUser) principal).getUsername());
  		    }
  		}
  		logger.info("已經認證通過的使用者數:"+usersNamesList.size()+",     已經認證通過使用者:"+usersNamesList.toString());
		
		
		// response.sendRedirect("/");
		super.onAuthenticationSuccess(request, response, authentication);
	}

}
package com.mayocase.util;

import java.util.Enumeration;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.core.userdetails.User;

import com.mayocase.domain.SysUser;

public class SessionUtil {

	private static SecurityContext attribute;
	
	/**
	 * 根據當前session獲取當前登入使用者物件
	 * @param session
	 * @return guser
	 */
	public static SysUser getUser(HttpSession session) {
		try {
			attribute = (SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT");
			SysUser principal = (SysUser) attribute.getAuthentication().getPrincipal();
			return principal;
		} catch (Exception e) {
		}
		return null;
	}
	
	/**
	 * 根據當前session獲取當前登入使用者ID
	 * @param session
	 * @return guser
	 */
	public static Integer getUserId(HttpSession session) {
		try {
			attribute = (SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT");
			SysUser principal = (SysUser) attribute.getAuthentication().getPrincipal();
			return principal.getId();
		} catch (Exception e) {
		}
		return null;
	}
	
	/**
	 * 根據session獲取使用者名稱稱
	 * @param session
	 * @return void
	 */
	public static String getUserName(HttpSession session) {
		try {
			attribute = (SecurityContext) session.getAttribute("SPRING_SECURITY_CONTEXT");
			SysUser principal = (SysUser) attribute.getAuthentication().getPrincipal();
			return principal.getUsername();
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
}
	
	/**
	 * 根據session獲取count
	 * session中包含一個count鍵預設為null,可以用來統計登入次數
	 * @param session
	 * @return void
	 */
	public static void count(HttpSession session) {
		ServletContext context = session.getServletContext();
		
		System.out.println("sessionid:"+session.getId()+",的count是:"+context.getAttribute("count"));
	}
	
	
    /**
     * 辨別使用者是否已經登入,如果已經登入就不能登入了。
     *
     * @param request
     * @param sessionRegistry
     * @param loginedUser
     */
    public static void deleteSameUser(HttpServletRequest request, SessionRegistry sessionRegistry, User loginedUser) {
        SecurityContext sc = (SecurityContext) request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
        List<SessionInformation> sessionsInfo;
        sessionsInfo = sessionRegistry.getAllSessions(sc.getAuthentication().getPrincipal(), true);
        String currentSessionId;
        if (null != sessionsInfo && sessionsInfo.size() == 0) {
            sessionRegistry.registerNewSession(request.getSession().getId(), sc.getAuthentication().getPrincipal());
            sessionsInfo = sessionRegistry.getAllSessions(sc.getAuthentication().getPrincipal(), false);
        }
        currentSessionId = sessionsInfo.get(0).getSessionId();
        List<Object> o = sessionRegistry.getAllPrincipals();
        for (Object principal : o) {
            if (principal instanceof User && (loginedUser.getUsername().equals(((User) principal).getUsername()))) {
                List<SessionInformation> oldSessionsInfo = sessionRegistry.getAllSessions(principal, false);
                if (null != oldSessionsInfo && oldSessionsInfo.size() > 0 && !oldSessionsInfo.get(0).getSessionId().equals(currentSessionId)) {
                    for (SessionInformation sessionInformation : sessionsInfo) {
                        //當前session失效
                        sessionInformation.expireNow();
                        sc.setAuthentication(null);
                        sessionRegistry.removeSessionInformation(currentSessionId);
                        //throw new GeneralServerExistException(ErrorMessage.ALONG_LOGIN_ERROTR.toString());
                    }
                }
            }
        }
    }

    /**
     * 會話銷燬(剔除前一個使用者)
     *
     * @param request
     * @param sessionRegistry
     * @param loginedUser
     * @param , SysMessageService sysMessageService
     */
    public static void expireSession(HttpSession session) {
    	session.invalidate();
    }
    
    
    /**
     * 剔除前一個使用者
     *
     * @param request
     * @param sessionRegistry
     * @param loginedUser
     * @param , SysMessageService sysMessageService
     */
    public static void dropPreviousUser2(HttpServletRequest request, SessionRegistry sessionRegistry, SysUser loginedUser) {
    	List<SessionInformation> sessionsInfo = null;
    	//登入以後session裡才會加入鍵名為"SPRING_SECURITY_CONTEXT"的欄位
        SecurityContext sc = (SecurityContext) request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
        
        if(sc!=null) {
        	System.out.println("!!!!!!!!!!!!"+sc.getAuthentication().getPrincipal().toString());
        	//獲取當前登入使用者的會話資訊集合
        	sessionsInfo = sessionRegistry.getAllSessions(sc.getAuthentication().getPrincipal(), false);
        	if (sessionsInfo.size() > 0) {
        		//當前會話ID
        		String  currentSessionId = sessionsInfo.get(0).getSessionId();
        		//獲取所有已經登入的使用者
        		List<Object> o = sessionRegistry.getAllPrincipals();
        		for (Object principal : o) {
        			//當登入使用者的名字和已經登入使用者的名字相同,也就是登入使用者已經登入過了。
        			if (principal instanceof User && (loginedUser.getUsername().equals(((User) principal).getUsername()))) {
        				//獲取已經登入使用者的會話資訊集合
        				List<SessionInformation> oldSessionsInfo = sessionRegistry.getAllSessions(principal, false);
        				//如果會話資訊不為空且會話資訊的ID不等於當前會話ID
        				if (null != oldSessionsInfo && oldSessionsInfo.size() > 0 && !oldSessionsInfo.get(0).getSessionId().equals(currentSessionId)) {
        					//遍歷已經登入使用者的會話資訊,並設定過期,即刪除session
        					for (SessionInformation sessionInformation : oldSessionsInfo) {
        						//舊使用者的session失效
        						//send message
        						//sysMessageService.sendMessage(((User) principal).getUsername(), new SysMessage(null, Consts.NOTIFICATION_TYPE_HADLOGIN_CONTENT, 5, Consts.NOTIFICATION_ACCEPT_TYPE_HADLOGIN));
        						sessionInformation.expireNow();
        					}
        				}
        			}
        		}
        	}
        }
        
    }

    /**
     * session 失效
     *
     * @param request
     * @param sessionRegistry
     */
    public static void expireSession(HttpServletRequest request, User user, SessionRegistry sessionRegistry) {
        List<SessionInformation> sessionsInfo = null;
        if (null != user) {
            List<Object> o = sessionRegistry.getAllPrincipals();
            for (Object principal : o) {
                if (principal instanceof User && (user.getUsername().equals(((User) principal).getUsername()))) {
                    sessionsInfo = sessionRegistry.getAllSessions(principal, false);
                }
            }
        } else if (null != request) {
            SecurityContext sc = (SecurityContext) request.getSession().getAttribute("SPRING_SECURITY_CONTEXT");
            if (null != sc.getAuthentication().getPrincipal()) {
                sessionsInfo = sessionRegistry.getAllSessions(sc.getAuthentication().getPrincipal(), false);
                sc.setAuthentication(null);
            }
        }
        if (null != sessionsInfo && sessionsInfo.size() > 0) {
            for (SessionInformation sessionInformation : sessionsInfo) {
                //當前session失效
                sessionInformation.expireNow();
                sessionRegistry.removeSessionInformation(sessionInformation.getSessionId());
            }
        }
    }
    
    public void showsession(HttpServletRequest request) {
    	//獲取session  
    	HttpSession   session   =   request.getSession();    
    	// 獲取session中所有的鍵值  
    	Enumeration<String> attrs = session.getAttributeNames();  
    	// 遍歷attrs中的
    	while(attrs.hasMoreElements()){
    	// 獲取session鍵值  
    	    String name = attrs.nextElement().toString();
    	    // 根據鍵值取session中的值  
    	    Object vakue = session.getAttribute(name);
    	    // 列印結果 
    	    System.out.println("--sessionID"+session.getId());
    	    System.out.println("--名字:" + name +"-----\n");
    	    System.out.println("--值:" + vakue +"--------\n");
    	}
    	
    }
}

 SessionRegistry引入的方法

package com.mayocase.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ImportResource;

@Configuration
@ImportResource(locations={"classpath:spring-security-context.xml"})
public class XMLConfigClass {

}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:security="http://www.springframework.org/schema/security"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
						   http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

	

    
    
    <bean id="mySessionAuthenticationStrategy" 
           class="org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy">
       <constructor-arg  name="sessionRegistry"  ref="sessionRegistry"/>
       <property name="maximumSessions" value="1" /> 
    </bean>
    <bean id="sessionRegistry" class="org.springframework.security.core.session.SessionRegistryImpl" />
    
    
</beans>

新增一個監聽器,用於控制session過期的情況,

package com.mayocase.listener;

import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;

import com.mayocase.domain.SysUser;
import com.mayocase.handler.MyAuthenctiationSuccessHandler;

//啟動類加上註解@ServletComponentScan,這樣才能掃描到監聽器
@WebListener
public class MySessionListner    implements HttpSessionListener {

	private static final Logger logger = LoggerFactory.getLogger(MySessionListner.class);
	
	/**
	 * 新建session時(開啟瀏覽器訪問登入頁面時,伺服器會建立一個新的session)
	 */
	@Override
	public void sessionCreated(HttpSessionEvent httpSessionEvent) {
		
		
	}
	/**
	 * 刪除session時(退出系統)
	 */
	@Override
	public void sessionDestroyed(HttpSessionEvent httpSessionEvent) {
		logger.info("銷燬session時");
		MyAuthenctiationSuccessHandler.sessionMap.remove(httpSessionEvent.getSession().getId());
	}
	
}