java单用户登录实现(详细代码)

单用户登录的实现比较简单,只要在session上动动手脚就行了

单用户登录有两种实现方式
1)当前用户登录验证成功时,如果该用户的账号已经登录,则挤掉已登录的账号,由该用户登录
2)当前用户登录验证成功时,如果该用户的账号已经登录,则提示当前用户该账号已登录,并且不允许当前用户登录

环境

系统:win10
IDE:sts4
springboot2.2.2.RELEASE、 jdk8、 maven3.3.9

挤掉已登录用户

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
server.port=8081

spring.mvc.view.prefix=/templates/
spring.mvc.view.suffix=.html

登录页是Bootstrap框架中的

<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
  <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <meta name="description" content="">
    <meta name="author" content="">
    <link rel="icon" href="images/favicon.ico">

    <title>Signin Template for Bootstrap</title>

    <!-- Bootstrap core CSS -->
    <link href="css/bootstrap.min.css" rel="stylesheet">

    <!-- Custom styles for this template -->
    <link href="css/signin.css" rel="stylesheet">
  </head>

  <body class="text-center">
    <form class="form-signin" action="/login" method="post">
      <h1 class="h3 mb-3 font-weight-normal">Please sign in</h1>
      <p style="color:red;"  th:text="${failMsg}" th:if="${not #strings.isEmpty(failMsg)}"></p>
      <label for="inputEmail" class="sr-only">Email address</label>
      <input type="text" id="uname" name="uname" class="form-control" placeholder="Username" required autofocus>
      <label for="inputPassword" class="sr-only">Password</label>
      <input type="password" id="pwd" name="pwd" class="form-control" placeholder="Password" required>
      <div class="checkbox mb-3">
        <label>
          <input type="checkbox" value="remember-me"> Remember me
        </label>
      </div>
      <button class="btn btn-lg btn-primary btn-block" type="submit">Sign in</button>
      <p class="mt-5 mb-3 text-muted">&copy; 2017-2018</p>
    </form>
  </body>
</html>
@Controller
public class DemoOneController {
    
    
	
	@Autowired
	DemoOneServiceImpl demoService;

	@RequestMapping("/index")
	public String index() {
    
    
		return "index";
	}
	
	@RequestMapping("/home")
	public String dashboard() {
    
    
		return "dashboard";
	}
	
	@PostMapping("/login")
	public String login(@RequestParam String uname, @RequestParam String pwd, 
						Map<String, Object>map, HttpSession session) {
    
    
		
		if(demoService.validateLogin(uname, pwd, session)) {
    
    
			//把当前用户放到session中
			session.setAttribute("username_session", "xiao");
			
			return "redirect:home";
		}else {
    
    
			map.put("failMsg", "用户名或密码错误");
			return "index";
		}
		
	}

}

注意在 service 中用了静态的 HashMap,用来存储已登录用户的 session id,同一个的会话的 session id 是一样的,这是本例的重点!!!

@Service
public class DemoOneServiceImpl{
    
    

	public static HashMap<String, String> onlineUsers = new HashMap<>();
	
	public static HashMap<String, String> getOnlineUsers(){
    
    
		return onlineUsers;
	}
	
	public boolean validateLogin(String uname, String pwd, HttpSession session) {
    
    
		
		if(uname.equals("xiao")) {
    
    //本例只验证登录用户,密码不管
			//既然是挤掉已登录用户,那就不管三七二十一先把用户session都清除掉即可
			session.removeAttribute("username_session");
			//把当前用户的 session id 放到一个静态 map,在登录拦截器中需要验证使用
			onlineUsers.put(uname, session.getId());
			
			return true;
		}else {
    
    
			
			return false;
		}
	}
}

每次访问页面都会验证用户登录的有效性,这个拦截器就是用来做这个判断的。
一开始我是把封装对象 service 直接拿过来调用 getOnlineUsers() 获取 session id 的,但是实际运行的时候 demoService 是 null,没办法用。后来就换成了静态方法,通过类名来直接获取。

public class LoginInterceptor implements HandlerInterceptor{
    
    
//	@Autowired
//	DemoOneServiceImpl demoService;
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
    
    
		Object username = request.getSession().getAttribute("username_session");
		if(username != null) {
    
     // 如果有 session
			HashMap<String,String> onlineUsers = DemoOneServiceImpl.getOnlineUsers();
			String curSid = request.getSession().getId();
			String onlineSid = onlineUsers.get(username);
			if(onlineSid != null && !curSid.equals(onlineSid)) {
    
     // 如果不是该用户在本地的session
				request.setAttribute("failMsg", "您的账号已在别处登录,请重新登录或修改密码");
				request.getRequestDispatcher("/index").forward(request, response);
				return false;
			}else {
    
    
				return true;
			}
		}else {
    
    
			//未登录,转发到登录页
			request.setAttribute("failMsg", "未登录或登录过时,请登录");
			request.getRequestDispatcher("/index").forward(request, response);
			return false;
		}
	}

}
@Configuration
public class Myconfig extends WebMvcConfigurationSupport {
    
    

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
    
    
    	// 绑定静态资源
        registry.addResourceHandler("/**").
        		 addResourceLocations("classpath:/static/","classpath:/public/");
        super.addResourceHandlers(registry);
    }

	@Override
	protected void addInterceptors(InterceptorRegistry registry) {
    
    
		// 定义要拦截和放行的请求、资源
		registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/**").
									excludePathPatterns("/login","/","/index","/css/**");
		super.addInterceptors(registry);
    }
	
}

本例重点就是在 LoginInterceptor 和 DemoOneServiceImpl 里面动了 session 的判断逻辑,只要看这两个类就可以了。

接下来是演示环节
1)在 Chrome 浏览器登录 “xiao” 这个账号,成功登录,并在该页面多次刷新,无异常。
2)同时,另外打开火狐浏览器,登录 “xiao” 这个账号,成功登录,并在该页面多次刷新,无异常。
3)回到 Chrome 浏览器,刷新已登录页面时,回到了登录页,并红字给出了提示
在这里插入图片描述
在这里插入图片描述

如果账号已登录,提示不允许登录

主要修改 LoginInterceptor 和 DemoOneServiceImpl 类如下

public class LoginInterceptor implements HandlerInterceptor{
    
    
//	@Autowired
//	DemoOneServiceImpl demoService;
	
	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
    
    
		Object username = request.getSession().getAttribute("username_session");
		if(username != null) {
    
     // 如果有 session
			HashMap<String,String> onlineUsers = DemoOneServiceImpl.getOnlineUsers();
			String curSid = request.getSession().getId();
			String onlineSid = onlineUsers.get(username);
			if(onlineSid != null && !curSid.equals(onlineSid)) {
    
     // 如果不是该用户在本地的session
				request.setAttribute("failMsg", "您的账号已在别处登录,请重新登录或修改密码");
				request.getRequestDispatcher("/index").forward(request, response);
				return false;
			}else {
    
    
				//这种情况session还没过期,账号又还是活跃状态,此时应该更新本账号的登录时间
				long curTime = new Date().getTime();
				onlineUsers.put(username + "_loginTime", "" + curTime/1000);
				return true;
			}
		}else {
    
    
			//未登录,转发到登录页
			request.setAttribute("failMsg", "未登录或登录过时,请登录");
			request.getRequestDispatcher("/index").forward(request, response);
			return false;
		}
	}

}

为了更友好的错误提示,这个 service 类中 validateLogin() 的返回值类型被我改了,同时,controller 当然也要稍作调整

@Service
public class DemoOneServiceImpl{
    
    

	public static HashMap<String, String> onlineUsers = new HashMap<>();
	
	public static HashMap<String, String> getOnlineUsers(){
    
    
		return onlineUsers;
	}
	
	public int validateLogin(String uname, String pwd, HttpSession session) {
    
    
		
		if(uname.equals("xiao")) {
    
    //本例只验证登录用户,密码不管
			Date date = new Date();
			//如果该账号已经登录,且session id不同,并且上个用户的session没有超时,则说明是有用户在别处已经登录了
			if(onlineUsers.get(uname) != null && !session.getId().equals(onlineUsers.get(uname))) {
    
    
				Long onlineUser_loginTime = new Long(onlineUsers.get(uname + "_loginTime"));
				System.out.println("onlineUser_loginTime==" + onlineUser_loginTime);
				long interval = date.getTime()/1000 - onlineUser_loginTime;
				System.out.println("interval==" + interval);
				if(interval <= 60) {
    
    
					// 该用户已经登录且session还在有效期内
					return 2;
				}else {
    
    
					// 账号虽然在别处已经登录,但是 他的session已经到期了,当前用户就可以登录该账号
					onlineUsers.put(uname, session.getId());
					onlineUsers.put(uname + "_loginTime", "" + date.getTime()/1000);
					return 1;
				}
			}else {
    
    
				//把当前用户的 session id 放到一个静态 map,在登录拦截器中需要验证使用
				onlineUsers.put(uname, session.getId());
				onlineUsers.put(uname + "_loginTime", "" + date.getTime()/1000);
				return 1;
			}
			
		}else {
    
    
			//账号密码验证失败
			return 0;
		}
	}
}

这里只贴了改动的地方

扫描二维码关注公众号,回复: 13298506 查看本文章
	@PostMapping("/login")
	public String login(@RequestParam String uname, @RequestParam String pwd, 
						Map<String, Object>map, HttpSession session) {
    
    
		int ansKey = demoService.validateLogin(uname, pwd, session);
		if(1 == ansKey) {
    
    
			//把当前用户放到session中
			session.setAttribute("username_session", "xiao");
			
			return "redirect:home";
		}else if(2 == ansKey) {
    
    
			map.put("failMsg", "该账号已登录,请不要重复登录");
			return "index";
		}else {
    
    
			map.put("failMsg", "用户名或密码错误");
			return "index";
		}
		
	}

为了测试方便,我把session的过期时间设置为了60秒

# 设置session有效时间为1分钟
server.servlet.session.timeout=60

现在就可以启动项目测试了
1)在 Chrome 浏览器登录 “xiao” 这个账号,成功登录,并在该页面多次刷新,无异常。
2)同时,60秒内,另外打开火狐浏览器,登录 “xiao” 这个账号,登录失败,提示“该账号已登录,请不要重复登录”,查看控制台输出
3)等 Chrome 浏览器的 session 过期,在火狐浏览器登录 “xiao” 这个账号,登录成功,查看控制台输出
在这里插入图片描述

在这里插入图片描述在这里插入图片描述等个60多秒,session过期,在火狐浏览器就可以正常登录了,而此时回到 chrome 浏览器刷新后会提示 “未登录或登录过时,请登录
在这里插入图片描述
在这里插入图片描述另一种情况就是当前账号一直处于活跃状态时,在本例中即60秒内至少刷新一次,测试另一个用户能不能登录该账号,我测试了是没问题的,无法登录,会提示 “该账号已登录,请不要重复登录

有兴趣的朋友可以多试试其他情况,看看有没有异常情况,后面慢慢完善,这里我也不一 一贴图了,以上应该能满足基本需求了。

猜你喜欢

转载自blog.csdn.net/Alias_fa/article/details/103935420