JEE保证登陆唯一

来源:互联网 发布:东莞知路电子有限公司 编辑:程序博客网 时间:2024/05/22 14:21

在做上个课程项目的时候就遇到过这个问题,当时的实现方式 是通过全局的监听来实现的,通过session 来标记一个用户的登陆状态,只有当当前用户注销时 用户用同样的账号登陆才会成功,这个做法存在多个问题,其中主要问题是判断用户登出,如果用户点击注销键,这个没问题,但是如果用户异常登出呢?   当然也不是没有办法,首先 我们可以设立监听事件  unload   这样在网页关闭的时候会触发这个时间,  但是这个事件会被多个动作触发,部分动作并不表示退出,比如说网页刷新等,另外直接关闭浏览器主窗体也不会触发标签页的unload事件,     当然还有方法补救,  我们通过session的 生命周期来,  如果用户在session 生命周期内没有与服务器发生交互 我们就认为用户已经离开,这个也是一个较为稳妥的方法,这个方法存在一个问题  如果用户异常登出,  那么用户需要等待当前session 会话结束才能再次登陆,  这个也是我上个项目没有实现这个功能的主要理由


现在换了一种决策,我认为登陆是可以互顶的,后登陆的会顶掉先登录的人,这样的逻辑思维也比较正常,如果一个人多处登陆,那么他使用最后登陆的设备与服务器交互的可能性也最大,如果存在异常一个人账号被多个人知晓,他也能通过互顶账号来实现保护自己的账号安全,检查互顶频率 也可以实现账号冻结等功能,


扯完背景了 扯点实在的

这里采用的方式是struts 的拦截器   我通过静态的map 存储userid 到 sessionid 的映射    然后通过检查映射来完成引导


这里复用了我一个测试的项目 代码逻辑就不用管了  只看实现就成


首先定义通用方法   使用单例 管理map 对象 同时 考虑多线程访问

package com.yueguang.util;import java.util.HashMap;import java.util.concurrent.Semaphore;public class AccountUtil {private static AccountUtil accountUtil= new AccountUtil();private HashMap<String, String> accountToSession;Semaphore semaphore;private AccountUtil() {   accountToSession = new HashMap<String, String>();   semaphore = new Semaphore(1);}public static AccountUtil getAccountUtil() {return accountUtil;}public synchronized void registerSessionid(String account, String sessionid) {try {semaphore.acquire();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}accountToSession.put(account, sessionid);System.out.println(account+" " + sessionid);semaphore.release();}public synchronized boolean checkSessionid(String account, String sessionid) {try {semaphore.acquire();} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}String old_sessionid = accountToSession.get(account);semaphore.release();        System.out.println("1111   "   + old_sessionid);        System.out.println(account + " "+ sessionid);if (old_sessionid != null && old_sessionid.equals(sessionid)) {return true;} else {return false;}}}

其次定义拦截器   拦截请求 交给验证  如果验证失败 引导到重新登录的页面

package com.yueguang.Interceptor;import java.util.Map;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpSession;import org.apache.struts2.ServletActionContext;import org.apache.struts2.interceptor.ServletRequestAware;import org.apache.struts2.interceptor.SessionAware;import com.opensymphony.xwork2.ActionInvocation;import com.opensymphony.xwork2.interceptor.AbstractInterceptor;import com.yueguang.util.AccountUtil;public class LoginInterceptor extends AbstractInterceptor {Map<String, Object> session;HttpServletRequest request;@Overridepublic String intercept(ActionInvocation actionInvocation) throws Exception {// TODO Auto-generated method stubAccountUtil accountUtil = AccountUtil.getAccountUtil();session = actionInvocation.getInvocationContext().getSession();if (session == null) {return "reLogin";}String userid = (String) session.get("userid");if (userid == null) {return "reLogin";}request = ServletActionContext.getRequest();String sessionid = (String) request.getSession().getId();if (accountUtil.checkSessionid(userid, sessionid)) {return actionInvocation.invoke();} else {return "reLogin";}}}

然后在登陆成功的时候  想session 中注入用户名    并且向map中 注入键值对


// 实现登陆功能的通用模块private String loginUtil(Class c) {Object someone = baseDao.load(c, username);Method m;try {m = c.getMethod("getPassword", null);if (someone != null&& ((String) m.invoke(someone)).equals(password)) {request.setAttribute(c.getName().substring(c.getName().lastIndexOf(".") + 1),someone);<span style="color:#ff0000;">AccountUtil accountUtil = AccountUtil.getAccountUtil();accountUtil.registerSessionid(username, request.getSession().getId());    session.put("userid", username);</span>return SUCCESS;}} catch (NoSuchMethodException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (SecurityException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalAccessException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IllegalArgumentException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (InvocationTargetException e) {// TODO Auto-generated catch blocke.printStackTrace();}this.addActionError("用户  " + username + "用户类型 "+ c.getName().substring(c.getName().lastIndexOf(".") + 1)+ " 登陆失败     ");return "fail";}

配置struts 拦截器

<package name="checkLogin" extends="struts-default">     <interceptors>     <interceptor name="login" class="com.yueguang.Interceptor.LoginInterceptor"/>      <interceptor-stack name="login_default">        <interceptor-ref name="login"/>        <interceptor-ref name="defaultStack"/>      </interceptor-stack> </interceptors>  <default-interceptor-ref name="login_default"/>   </package>

让需要改拦截器的模块扩展 这个包

<package name="test" namespace="/test" extends="<span style="color:#ff0000;">checkLogin</span>">      <global-results>        <result name="reLogin">/Login.jsp</result>     </global-results>     <action name="haha" class="testAction"  method="test">      <result name="success">/Error.jsp</result>        <result name="fail" type="chain">           <param name="actionName">kaka</param>           <param name="namespace">/test</param>      </result>     </action>     <action name="kaka" class="testAction"  method="testkaka">      <result name="success">/Error.jsp</result>        <result name="fail">/Login.jsp</result>     </action>   </package> 


这个方法能实现单人登陆的效果,但是存在个问题是  转发的请求 也会被拦截器 检测  这个会导致效率低下  我正在查不拦截 dispatcher 的方法


0 0