Application用于存放应用程序级的共享数据,比如用户访问量统计、防止同一账号同一时间多客户端登录等等。一般而言,我们不建议在application中存放数据,尤其是大数据集合,在访问量比较大的网站有时甚至会产生严重的性能瓶颈。
这里仅就账号锁定和账号独立会话两个操作在application中的应用做一个简单的介绍,不妥之处,望不吝赐教。
账号锁定
同一账号连续N次(可配置)登录(login_count)密码有误,则锁定该账号。账号锁定后,该用户在持续锁定时间(lock_time)范围(比如24小时)内不允许登录。超过持续锁定时间后,再次登录,重新计算登录次数。拿网上银行系统举例(可能不太恰当)。网上银行系统一般都规定在一天内,密码连续三次输入错误,则该账号被锁定。
账号独立会话
类似于QQ的功能,一个账号只能在一个客户端(这里可以指一台电脑或者一个会话,不过原理差不来多少,本示例基于session)进行操作,采取后来居上,前客户端登录直接被踢的方式。可能提炼的不够恰当,暂且就这么叫吧,朋友们有更好的术语不妨留言讨论。当然也可以在登录时,判断该账号是否已登录,如果已经登录则给予提示。方法不尽相同,不过殊途同归。
我打算将这两个功能合在一起,主要是基于都是application级应用的考虑。下面谈谈具体的实现方案。
概要说明
这里需要记录下账号的相关信息,所以需要一个POJO类,需要一个拦截器来验证账号信息。
具体描述
在用户登录时,记录下账号信息如登录名、sessionId、最后登录时间、连续错误登录次数等等。以登录名为key,账号信息为value存放在Map集合中,并将Map置于application。
用户每次登录都记录下登录时间,,登录错误则错误登录次数加一。连续错误登录,次数超过限制,则不允许继续登录。超出锁定时间后,再次登录时,连续错误登录次数清零,从而实现账号锁定的功能。
同一账号只会记录该账号最后一次登录的sessionId。在拦截器中对用户的会话ID进行验证,如果不一致,则为之前登录的客户端,直接将当前会话清除,以实现账号独立会话的功能。
具体示例代码如下:
import java.util.Date;
/**
* 登录信息
* @remark 该信息保存在application中,主要用于登录锁定和同一账户同时只能登录一次
* @author lihua
* @version V1.0
* @createDate 2012-9-28
*/
public final class LoginInApp {
private String sessionId;//保存当前用户最新的sessionid
private Date loginTime;//最后登录时间
private int loginCount;//连续错误登录次数,该次数会在登录超过可连续登录时间间隔后自动回位到1
public LoginInApp() {
}
public LoginInApp(String sessionId, Date loginTime, int loginCount) {
this.sessionId = sessionId;
this.loginTime = loginTime;
this.loginCount = loginCount;
}
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
public Date getLoginTime() {
return loginTime;
}
public void setLoginTime(Date loginTime) {
this.loginTime = loginTime;
}
public int getLoginCount() {
return loginCount;
}
public void setLoginCount(int loginCount) {
this.loginCount = loginCount;
}
}
账号锁定验证代码
/**
* 账号锁定验证
*/
String accountLockState = PropertiesUtil.getString(Constants.ACCOUNT_LOCK_STATE);//锁定状态开关
if(ValidateUtil.matchString(accountLockState, "on")){ //开启锁定
HttpSession session = request.getSession();
Object obj = session.getServletContext().getAttribute(Constants.KEY_SESSION_MESSAGE);
if(obj!=null){ //application 用户缓存不为空
Map<String, LoginInApp> map = (HashMap<String, LoginInApp>)obj;
LoginInApp lip = map.get(userdto.getUserName());
if(lip!=null){ //当前用户的登录记录不为空
int max_login_number = 3;//可连续登录次数
int lock_time = 10;//持续锁定时间
SysParam param = sysParamServiceImpl.getByParamName(Constants.SECURITY_PARAMS[0]);
if(param!=null&&ValidateUtil.validateString(param.getParamValue())){
max_login_number = Integer.valueOf(param.getParamValue());
}
SysParam param1 = sysParamServiceImpl.getByParamName(Constants.SECURITY_PARAMS[1]);
if(param1!=null&&ValidateUtil.validateString(param1.getParamValue())){
lock_time = Integer.valueOf(param1.getParamValue());
}
if(lip.getLoginCount()>=max_login_number){ //超出可连续登录次数
Calendar curCal = Calendar.getInstance();
Calendar lockCal = new GregorianCalendar();
lockCal.setTime(lip.getLoginTime());
lockCal.add(Calendar.MINUTE, lock_time);
if(curCal.before(lockCal)){
addActionError("账号"+userdto.getUserName()+"已被锁定,请在"+lock_time+"分钟后登录!");
return "loginerror";
}else{//过期解锁
lip.setLoginCount(0);
}
}
}
}
}
/**
* 修改应用程序缓存账户信息
* @param user
*/
private void modifyLoginInApp(OnlineUser user){
String accountLockState = PropertiesUtil.getString(Constants.ACCOUNT_LOCK_STATE);//锁定状态开关
if(ValidateUtil.matchString(accountLockState, "on")){ //锁定状态开启
ServletContext app = ServletActionContext.getServletContext(); //上下文
Map<String, LoginInApp> appInfo = (Map<String, LoginInApp>)app.getAttribute(Constants.KEY_SESSION_MESSAGE);
if(ValidateUtil.matchString(user.getErrorMsg(),Constants.LOGIN_ERROR_TIP)){//登录异常
if(appInfo!=null){
LoginInApp lip = appInfo.get(user.getUserName());
if(lip==null){
lip = new LoginInApp(ServletActionContext.getRequest().getSession().getId(),new Date(),1);
}else{
lip.setLoginTime(new Date());
lip.setLoginCount(lip.getLoginCount()+1);
}
appInfo.put(user.getUserName(), lip);
}else{
appInfo = new HashMap<String, LoginInApp>();
LoginInApp lip = new LoginInApp(ServletActionContext.getRequest().getSession().getId(),new Date(),1);
appInfo.put(user.getUserName(), lip);
}
app.setAttribute(Constants.KEY_SESSION_MESSAGE, appInfo);
}
}
}
private void sessionManage(OnlineUser user){
HttpSession session = request.getSession();
ServletContext context = ContextLoader.getCurrentWebApplicationContext().getServletContext();
Object obj = context.getAttribute(Constants.KEY_SESSION_MESSAGE);
Map<String, LoginInApp> map = null;
if(obj!=null){
map = (HashMap<String, LoginInApp>)obj;
}else{
map = new HashMap<String, LoginInApp>();
}
map.put(user.getUserName(),new LoginInApp(session.getId(),new Date(),0));
context.setAttribute(Constants.KEY_SESSION_MESSAGE,map);
}
在拦截器中处理多客户端登录
OnlineUser ouser = (OnlineUser)session.getAttribute(Constants.KEY_SESSION_ONLINE_USER);
//多个客户端登录,先登录者被踢出
ServletContext context = session.getServletContext();
Object obj = context.getAttribute(Constants.KEY_SESSION_MESSAGE);
if(obj!=null&&ouser!=null){
Map<String, LoginInApp> map = (HashMap<String, LoginInApp>)obj;
String oldSessionId = map.get(ouser.getUserName())==null?null:map.get(ouser.getUserName()).getSessionId();
if(ValidateUtil.validateString(oldSessionId)&&!ValidateUtil.matchString(session.getId(), oldSessionId)){
session.removeAttribute(Constants.KEY_SESSION_ONLINE_USER);
response.sendRedirect(request.getContextPath() + "/common/outTime.jsp");
return;
}
}