我想要的效果是不关闭浏览器就不会断直到失效时间,而一旦关闭浏览器就退出了。
前半句可以做到,但需要用类似 AJAX 的方法反复向服务器发请求,以保持 session 的活性,避免服务器端 session 超时。
后半句比较困难,你可以在 onunload 事件里向服务器发个请求来关闭 session,但这样做问题很多,比如,当有多个窗口打开时,关闭其中某一个,怎么办?总不能这样就关闭 session 了吧?可是你怎么判断要关闭的是“最后一个窗口”呢?……
其实以上说的这些都不是关键的。你要解决的关键问题是:如果客户端的窗口都关闭了,服务器上的 session 还未失效,此时同一个用户账号通过另外一个 session 登录进来了,怎么办?
方法1:不管它,允许登录,继续工作。这样处理的问题是,同一个用户账号可能同时登录多个 session,也许你的应用系统不希望这样使用(比如希望限制用户数)。
方法2:就是你开始说的,检查出该用户的 session 已经存在,就拒绝登录。这样处理的问题是,垃圾 session 会导致用户在一段时间内无法登录。这显然是无法接受的。
方法3:既然你已经能检测出该用户已经登录,当然也就能得到那个 session 对象,你可以让那个 session 失效,然后在新的 session 下继续工作。这样做的问题是,如果原来的 session 并不是僵死的垃圾,那个用户窗口还在继续工作,那么他会觉得“被踢出”了。但这种结果总归是比前一种结果好,而且也能满足“限制用户数”的需求。
方法4:查出已经登录的那个 session 后,通过“URL转发”把客户端引导到那个 session 里面去,而把新的这个 session 作废。这样的效果是,新登录的用户感觉是在原来那个 session 的基础上继续工作。这种处理方法对于“误操作关闭所有窗口后重新登录继续工作”最有效,但如果真的是“两个不同的人从不同的机器上用同一个账号登录”,可能会互相干扰,可以看做是对“限制用户数”需求的另一种处理方式。
你是在 application 里保存了所有登录用户名。如果你把所有登录的 session 都保存在 application 里,不就 OK 了? :)
索性我给你一段源程序吧,里面实现了我前面说的 4 种策略,如果你有耐心读完的话,也许会有些帮助。
这是一个 Filter,它在自己的实例中保存了全部登录成功的 session。由于一个 Web-application 中每个 Filter 只有一个实例,所以这种方法跟你在 application 对象中保存 TreeSet 是一样的。
Java code
import java.io.IOException;
import java.util.Hashtable;
import java.util.Iterator;import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;/**
* GateFilter 用于对 web-application 中的 request 进行过滤检查。
*
* <ol type="1">
* <p>它解决两个方面的问题:</p>
*
* <li>检查当前用户是否已经登录
*
* <p>如果尚未登录,将通过重定向把浏览器引导到指定的登录画面。</p>
*
* <li>当同一个用户帐号重复登录时,按指定的策略进行处理
* <pre>
* default - 缺省情况下,各自使用自己的 session;
* join - 后一个用户加入前面的 session(自己的 session 要废掉);
* replace - 后一个用户正常使用,前一个用户的 session 要被废掉;
* deny - 后一个用户会被拒绝;
* </pre>
* </ol>
*
* <p>需要为 GateFilter 指定 3 个参数:
*
* <pre>
* userSessionKey - 在 session 中保存用户登录对象的 key;
* loginUri - 用于提供登录画面和登录验证操作的 uri;
* sessionPolicy - 当同一个用户帐号重复登录时的处理策略;
* </pre>
*/
public class GateFilter implements Filter {
private Log log = LogFactory.getLog(getClass()); public final static String SESSION_KEY_RECORDED = "GateFilter.recorded"; private String userSessionKey;
private String loginUri;
private int policy;
private Hashtable<String, HttpSession> sessions; public void init(FilterConfig config) throws ServletException {
log.trace("init()"); userSessionKey = config.getInitParameter("userSessionKey");
loginUri = config.getInitParameter("loginUri"); String sessionPolicy = config.getInitParameter("sessionPolicy");
policy = 0;
if ("join".equalsIgnoreCase(sessionPolicy)) {
policy = 1;
} else if ("replace".equalsIgnoreCase(sessionPolicy)) {
policy = 2;
} else if ("deny".equalsIgnoreCase(sessionPolicy)) {
policy = 3;
} if (policy > 0)
sessions = new Hashtable<String, HttpSession>();
} public void destroy() {
} public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
log.trace("doFilter()"); if (!(request instanceof HttpServletRequest)) {
chain.doFilter(request, response);
return;
} HttpServletRequest httpRequest = (HttpServletRequest)request;
HttpServletResponse httpResponse = (HttpServletResponse)response;
String requestURI = httpRequest.getRequestURI();
// 如果是登录请求,就不做额外处理
if (requestURI.endsWith(loginUri)) {
chain.doFilter(request, response);
return;
} // 检查 session 中是否记录了有效的 user 对象
HttpSession session = httpRequest.getSession();
Object user = session.getAttribute(userSessionKey);
if (user == null) {
// 如果尚未登录成功,则重定向到登录画面
String contextPath = httpRequest.getContextPath();
String target = httpResponse.encodeRedirectURL(requestURI);
httpResponse.sendRedirect(contextPath + loginUri + "?target=" + target);
return;
} // 如果已经记录过,就不再重复处理了
Boolean bRecorded = (Boolean)session.getAttribute(SESSION_KEY_RECORDED);
if (bRecorded != null) {
chain.doFilter(request, response);
return;
} // 缺省情况下,重复登录的用户各自使用自己的 session
if (policy == 0) {
chain.doFilter(request, response);
return;
} // 查找已经存在的包含相同用户的 session
HttpSession existSession = null;
Iterator it = sessions.values().iterator();
while (it.hasNext()) {
HttpSession ses = (HttpSession)it.next();
try {
Object existUser = ses.getAttribute(userSessionKey);
if (user.equals(existUser)) {
existSession = ses;
}
} catch (RuntimeException e) {
// 如果发生异常,说明 ses 已经失效
sessions.remove(ses.getId());
}
} // 后登录的用户加入到前面已经登录的 session 中
if (policy == 1 && existSession != null) {
// 同一用户帐号前面已经登录过,废除当前的 session,重用找到的这个
// 通过重定向让浏览器重新请求同一个 URL,并通过 cookie 告知浏览器使用新的 JSESSIONID
session.invalidate();
String url = httpRequest.getRequestURL().toString();
// addCookie() 生成的 header 含有引号,导致 cookie 设置失败
//httpResponse.addCookie(new Cookie("JSESSIONID", existSession.getId() + "; Path=/"));
// 只好使用 setHeader() 来设置 cookie
httpResponse.setHeader("Set-Cookie", "JSESSIONID=" + existSession.getId() + "; Path=/");
httpResponse.sendRedirect(url);
return;
} // 后登录的用户继续使用,把前面的用户踢出
else if (policy == 2 && existSession != null) {
sessions.remove(existSession.getId());
existSession.invalidate();
} // 阻止后登录的用户
else if (policy == 3 && existSession != null) {
session.invalidate();
String contextPath = httpRequest.getContextPath();
httpResponse.sendRedirect(contextPath);
return;
} // 把当前这个 session 记录下来,其它一切照旧
sessions.put(session.getId(), session);
session.setAttribute(SESSION_KEY_RECORDED, true); chain.doFilter(request, response);
return;
}}