实现单点登录,同一个账号只能有一个在线,再次登录时将已经处于登录状态的所有此账号踢下线(Springboot或Springboot+shiro)
前言:在项目中,我们需要让每个账户只能有一个在线,如果一个账号在一个系统中登录多次并且这些同时 对系统操作是非常不安全的,因此需要做出限制,在登陆前可以先判断当前账户在系统中是否处于已登录状态,如果有登录的可以直接将其他的当前同一账户挤下线,在之前我首先想到的是直接用HttpSession获取到session在其setAttribute方法中传入当前账户信息进行判断当前账户是否过期或者是否处于已登录状态,后来测试发现这个session只能在同一个浏览器可以使用getAttribute方法拿到值,而在不同浏览器是拿不到值,返回null,故而无法判断,因此下面方法使用的是通过设置全局变量map<账号,Session>来存储信息,这样就可以跨浏览器取值进行判断了,之前觉得为啥要传Session进去,直接传字符串不也一样?后来需要实现当前用户在规定时间没任何操作使其下线,而Session可以通过设置过期时间正好实现此需求,时间一到session中存的信息会被销毁,并且Session可以用setAttribute传入用户信息,我们进行登录前的判断时直接获取map中的session,然后获取出session中的用户信息进行判断,完美解决这里的需求,如果项目使用shiro同样可以使用,亲测可用。
正文:
一、创建实体类,在实体类中添加getter,setter方法,记得加注解@Component,否则使用@Autowired注解注入时会报错
1 import org.springframework.stereotype.Repository;
2 import javax.servlet.http.HttpSession;
3 import java.util.HashMap;
4 import java.util.Map;
5
6 @Component
7 public class ManageSession {
8 Map<String, HttpSession> manageSession=new HashMap<>();
9
10 //setter
11 public Map<String, HttpSession> getManageSession() {
12 return manageSession;
13 }
14 //getter
15 public void setManageSession(Map<String, HttpSession> manageSession) {
16 this.manageSession = manageSession;
17 }
18 }
二、在启动类中添加全局session变量
1 @SpringBootApplication
2 public class BtkbringomgApplication {
4 public static void main(String[] args) {
5 SpringApplication.run(BtkbringomgApplication.class, args);
6 }
7 /* 全局session变量 */
8 public static ManageSession manageSession;
9 }
三、在Controller中注入
1 @Autowired
2 private ManageSession manageSession;
四、在Controller中的登录方法下添加,为了简便下面的登录方法返回值设置为void,读者可根据自己的需求进行修改返回自己想要的数据
@RequestMapping("/dologin")
public void login(@RequestBody User user,HttpServletRequest request){
HttpSession session = request.getSession();//获取session
session.setMaxInactiveInterval(60*30*2);//1h //session过期时间 单位:s 也可以不设置或传入参数0或负数表示永久有效,这里设置是为了判断在这么多时间未对系统进行任何操作让它下线
session.setAttribute(user.username,user.username);//session想传入的东西,随便传,这里传入了登录账号,达到上面时间自动销毁
/**
*每个账号只能在线一个
*
*当前登录后其他的踢掉
**/
HttpSession httpSession = manageSession.getManageSession().get(user.getUsername());
//System.out.println("session:"+httpSession);
//if (httpSession!=null && !httpSession.isNew()){
if (httpSession!=null){
//第一种方法:session销毁
httpSession.invalidate();
// 第二种方法:清除session---账户已经登录时另一个此账号直接登不进去,不建议使用
//Enumeration<String> enumeration = request.getSession().getAttributeNames();
//while (enumeration.hasMoreElements()) {
//String key = enumeration.nextElement().toString();
//request.getSession().removeAttribute(key);
//}
}
//写入session信息
manageSession.getManageSession().put(user.getUsername(),session);
}
五、有时候登录时session已经销毁导致我们销毁时出现异常信息:IllegalStateException:Session already invalidated,如果使用shiro进行登录认证时认证失败会走异常,会造成即使认证成功也会进异常而导致认证失败的假象,所以如果使用shiro登录的话我们使用try/catch将其包裹对此异常进行放行。
........
catch (Exception e){
e.printStackTrace();
String messagename = e.getMessage();// 扒出异常内容
if(messagename.contains("Session already invalidated")){
//session已经销毁,放行
//返回认证成功的信息--这里的和步骤六result为我自定义的结果集Result result = new Result();来的--可直接删掉按照自己需求返回自己想要的
result.setSuccess(true);
result.setMessage("认证成功");
//写入session信息
manageSession.getManageSession().put(user.getUsername(),session);
}else{ e.printStackTrace(); /*认证失败*/ result.setSuccess(false); result.setMessage("认证失败!"); }
六、有时我们有这样一个需求:在登陆前判断当前账户是否处于已经登录状态并给出提示,上面代码中session.setMaxInactiveInterval(60*30*2);可以设置过期时间,时间一到销毁session中存储的值
@Autowired
private ManageSession manageSession;
@RequestMapping("/beforelogin")
public Result beforelogin(@RequestBody User user) {
Result result = new Result();
//判断是否有人正在使用此帐号(此帐号是否处于已登录状态)--获取map中的session
HttpSession httpSession = manageSession.getManageSession().get(user.getUsername());
try {
//扒出session中的用户信息
if(httpSession.getAttribute(user.username)!=null){
//当前session有值,说明1.此帐号处于已登录状态有人正在使用,2.session还在有效期未被销毁
//System.out.println(httpSession.getAttribute(user.username));
//此账号有人正在使用-----返回提示信息
result.setSuccess(false);
result.setMessage("此帐号有人正在使用,是否继续登录将其挤掉?");
}else {
result.setSuccess(true);
result.setMessage("此账号空闲,可直接登录使用!");
}
return result;
} catch (Exception e) {
//掉进异常,说明当前账户要么空闲,要么超时过期,直接登录就好
//首次登录或账号处于空闲状态(未登录)报空指针NullPointerException
//报异常getAttribute: Session already invalidated说明此帐号之前的登录已经过期,现在可直接登录
result.setSuccess(true);
result.setMessage("此账号空闲,可直接登录!");
System.out.println("此账号空闲");
return result;
}
}