百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术资源 > 正文

Java Web轻松学63 - 使用session进行会话跟踪

lipiwang 2024-11-03 15:51 10 浏览 0 评论

本系列文章旨在记录和总结自己在Java Web开发之路上的知识点、经验、问题和思考,希望能帮助更多(Java)码农和想成为(Java)码农的人。

目录

  1. 介绍
  2. 需求
  3. 思路
  4. 登录Handler
  5. 设置session超时时间
  6. 获取房源列表的Handler
  7. 其他Handler
  8. JSP页面修改
  9. 总结

介绍

上篇文章我们实现了简单的用户登录功能,不过我们依然是使用URL重写(可以参考这篇文章)的技术来实现用户登录后的会话跟踪,这种技术比较低级,且不安全。

所以,需要使用Servlet容器为我们提供的另外一个强大的技术 - Session技术,来进行会话的跟踪。事实上,Session技术的底层也是基于URL重写技术和Cookie技术来实现的。

需求

  • 用户登录之后的每次访问需要验证用户是否已经登录;
  • 去除URL重写技术中在URL末尾添加的用户名参数;
  • 用户登录之后,如果后续的操作之间超过一段时间,则需要重新登录。

思路

  1. 在Spring MVC基于注解的控制器HouseRenterController中,为各个Handler方法注入HttpSession类型的参数;
  2. 在处理登录请求的Handler中,为session添加属性(并设置session超时时间,此处可选,默认是30分钟);
  3. 在其他Handler中,利用session属性校验是否已经登录,如果session失效(Servlet容器根据超时时间为你判断了),则跳转到登录页面;
  4. 在各个Handler中,去除URL中末尾添加的用户名参数;
  5. 在JSP页面中,去除URL中末尾添加的用户名参数。

服务层和DAO层都不需要做任何改动。

登录Handler

先来看看处理用户登录请求的Handler:

	@PostMapping("/login.action")
	public ModelAndView postLogin(HttpSession session, String userName, String password) {
		System.out.println("userName: " + userName + ", password: " + password);
		ModelAndView mv = new ModelAndView();
		try {
			userService.login(userName, password);
			session.setAttribute("userName", userName);
			session.setAttribute("loggedIn", Boolean.TRUE);
			session.setMaxInactiveInterval(30);
			//重定向到查找感兴趣房源列表的动作
			mv.setViewName("redirect:houses.action");
		} catch (Exception e) {
			mv.addObject("errorMessage", e.getMessage());
			mv.setViewName("login-failure.jsp");
		}
		return mv;
	}

需要关注:

  • 方法的参数中添加了 HttpSession session
  • 在调用完UserService组件的login()方法后,为session添加了两个属性,一个是用户名,一个是登录标识;
  • 然后为session设置超时时间(以秒为单位),事实上,此步可以省略,因为Servlet容器为所有session设置了默认的超时时间(30分钟),此处设置为30秒只是为了方便测试;
  • 最后,重定向的URL中不再需要userName参数了。


设置session超时时间

有三种方法可以设置session的超时时间:

  • 像上面的代码中调用session.setMaxInactiveInterval()方法,单位是秒,只对该session有效,优先级最高;
  • 在应用中的部署描述符web.xml中,添加以下配置:
	<session-config>
		<session-timeout>10</session-timeout>
	</session-config>

单位是分钟,对该应用的所有session有效,优先级次之;

  • 在Tomcat安装路径(比如,我的路径是 E:\csdn\apache-tomcat-9.0.16\conf )下的全局部署描述符web.xml中,添加类似上面的配置。不过,开发环境通常将Tomcat集成到Eclipse中(参考这篇文章),此时可以修改工作空间中的这个路径(比如,E:\csdn\eclipse-workspace\.metadata\.plugins\org.eclipse.wst.server.core\tmp0\conf)的部署描述符web.xml。在这个部署描述符中,也可以看到session超时时间的默认配置是30分钟。这种方法的配置单位也是分钟,但是对Servlet容器中的所有session有效,优先级是最低的。

获取房源列表的Handler

用户登录成功后,将自动重定向到自己感兴趣的房源列表页面,此页面的生成需要先执行获取房源列表的Handler:

	@GetMapping("/houses.action")
	public ModelAndView getHouses(HttpSession session, @SessionAttribute String userName) {
		ModelAndView mv = new ModelAndView();
		if (session.getAttribute("loggedIn") == null) {
			mv.setViewName("login.html");
			return mv;
		}
		//查找感兴趣房源并绑定到相应JSP页面,然后将请求转发到该页面
		mv.addObject("mockHouses", houseService.findHousesInterested(userName));
		mv.setViewName("houses.jsp");
		return mv;
	}

需要关注:

  • 方法的参数中也添加了 HttpSession session
  • 方法的 userName 参数使用了 @SessionAttribute 注解,这样我们就能够访问上一步中挂载在session中的数据了,匹配的规则是按照参数名和属性名是否一致。事实上,原来的userName参数也是有默认的注解修饰的,它就是Spring MVC中的 @RequestParam
  • 然后,我们就可以使用挂载在session中的登录标识来校验session是否已经失效(由Servlet容器为我们进行超时判断了),如果用户长时间未操作导致该session失效的话,Servlet容器将会生成新的session,这个新的session当然也就不会挂载登录标识 loggedIn 这个属性了,所以可以直接跳转到登录页面,让用户重新登录。所以可以这么说,HTTP中的session本质上是时间间隔不超过某个限制的连续的一组操作
  • 最后,同样可以去掉URL中的userName参数了。

其他Handler

剩下的获取房源详情、获取房源编辑表单、提交房源编辑结果的Handler都是类似的修改。

最后给出 HouseRenterController 的完整代码:

package houserenter.controller;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.SessionAttribute;
import org.springframework.web.servlet.ModelAndView;
import houserenter.entity.House;
import houserenter.service.HouseService;
import houserenter.service.UserService;
@Controller
public class HouseRenterController {
	
	@Autowired
	private HouseService houseService;
	
	@Autowired
	private UserService userService;
	
	@GetMapping("/test.action")
	@ResponseBody
	public String test() {
		return "hello";
	}
	
	@PostMapping("/register.action")
	public ModelAndView postRegister(String userName, String password, String passwordConfirmed) {
		System.out.println("userName: " + userName + ", password: " + password + ", passwordConfirmed: " + passwordConfirmed);
		ModelAndView mv = new ModelAndView();
		try {
			userService.register(userName, password, passwordConfirmed);
			mv.setViewName("register-success.html");
		} catch (Exception e) {
			mv.addObject("errorMessage", e.getMessage());
			mv.setViewName("register-failure.jsp");
		}
		return mv;
	}
	
	@PostMapping("/login.action")
	public ModelAndView postLogin(HttpSession session, String userName, String password) {
		System.out.println("userName: " + userName + ", password: " + password);
		ModelAndView mv = new ModelAndView();
		try {
			userService.login(userName, password);
			session.setAttribute("userName", userName);
			session.setAttribute("loggedIn", Boolean.TRUE);
			session.setMaxInactiveInterval(30);
			//重定向到查找感兴趣房源列表的动作
			mv.setViewName("redirect:houses.action");
		} catch (Exception e) {
			mv.addObject("errorMessage", e.getMessage());
			mv.setViewName("login-failure.jsp");
		}
		return mv;
	}
	
	@GetMapping("/houses.action")
	public ModelAndView getHouses(HttpSession session, @SessionAttribute String userName) {
		ModelAndView mv = new ModelAndView();
		if (session.getAttribute("loggedIn") == null) {
			mv.setViewName("login.html");
			return mv;
		}
		//查找感兴趣房源并绑定到相应JSP页面,然后将请求转发到该页面
		mv.addObject("mockHouses", houseService.findHousesInterested(userName));
		mv.setViewName("houses.jsp");
		return mv;
	}
	
	@GetMapping("/house-details.action")
	public ModelAndView getHouseDetails(HttpSession session, String houseId) {
		ModelAndView mv = new ModelAndView();
		if (session.getAttribute("loggedIn") == null) {
			mv.setViewName("login.html");
			return mv;
		}
		//查找房源详情并绑定到相应JSP页面,然后将请求转发到该页面
		mv.addObject("target", houseService.findHouseById(houseId));
		mv.setViewName("house-details.jsp");
		return mv;
	}
	
	@GetMapping("/house-form.action")
	public ModelAndView getHouseForm(HttpSession session, String houseId) {
		ModelAndView mv = new ModelAndView();
		if (session.getAttribute("loggedIn") == null) {
			mv.setViewName("login.html");
			return mv;
		}
		//查找房源详情并绑定到相应JSP页面,然后将请求转发到该页面
		mv.addObject("target", houseService.findHouseById(houseId));
		mv.setViewName("house-form.jsp");
		return mv;
	}
	
	@PostMapping("/house-form.action")
	public ModelAndView postHouseForm(HttpSession session, House house) {
		ModelAndView mv = new ModelAndView();
		if (session.getAttribute("loggedIn") == null) {
			mv.setViewName("login.html");
			return mv;
		}
		//更新指定房源的详情
		houseService.updateHouseById(house);
		//将请求转发到查找房源详情的动作
		mv.setViewName("redirect:house-details.action?houseId=" + house.getId());
		return mv;
	}
}

JSP页面修改

最后,我们JSP页面中凡是末尾有userName参数的URL都可以将该参数去掉了,在此不再赘述,某些JSP页面的源码可以参考这篇文章

不过,需要提到的一点是,在JSP页面中使用EL表达式访问session的属性需要用到 sessionScope 这个隐式对象。比如,include.jsp 页面的内容如下:

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ page import="java.util.List" %>
<%@ page import="houserenter.entity.House" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>租房网</title>
</head>
<body>
<h1>你好,${sessionScope.userName}!欢迎来到租房网! <a href="login.html">退出</a></h1>
<br><br>


总结

这样,我们的用户登录功能才算比较完整一些了,拥有了登录的验证、超时,还把URL中的用户名参数

给去除了。大家可以重新发布应用,启动Tomcat自行验证一下。

  • HTTP中的session本质上是时间间隔不超过某个限制的连续的一组操作
  • session可以使用Servlet中的HttpServletRequest.getSession()方法得到;
  • session也可以使用Spring MVC的Handler方法注入HttpSession类型的参数得到;
  • session超时时间有三种配置方式:代码调用session.setMaxInactiveInterval()、应用的部署描述符、Tomcat的部署描述符;
  • 访问session的参数可以使用session.getAttribute()方法;
  • 访问session的参数也可以直接使用Spring MVC的 @SessionAttribute 注解;
  • JSP页面中访问session的参数使用EL表达式的隐式对象 sessionScope;

不过,也还有一些问题没有考虑:

  • session是非线程安全的(事实上,一般从 HttpServletRequest 或 HttpServletResponse 中获取的对象都是非线程安全的)。
  • session是Servlet容器实例化并放在内存中的,如何持久化它呢?
  • 如果一个应用部署在多个Servlet容器中的分布式集群环境中,如何让session在各个分布式节点中共享呢?
  • 登录验证的代码似乎又重复了多次,是否可以类似之前使用Servlet技术的Filter来拦截并统一处理呢?
  • 用户退出如何处理session呢?
  • 等等。

相关推荐

前端入门——css 网格轨道详细介绍

上篇前端入门——cssGrid网格基础知识整体大概介绍了cssgrid的基本概念及使用方法,本文将介绍创建网格容器时会发生什么?以及在网格容器上使用行、列属性如何定位元素。在本文中,将介绍:...

Islands Architecture(孤岛架构)在携程新版首页的实践

一、项目背景2022,携程PC版首页终于迎来了首次改版,完成了用户体验与技术栈的全面升级。作为与用户连接的重要入口,旧版PC首页已经陪伴携程走过了22年,承担着重要使命的同时,也遇到了很多问题:维护/...

HTML中script标签中的那些属性

HTML中的<script>标签详解在HTML中,<script>标签用于包含或引用JavaScript代码,是前端开发中不可或缺的一部分。通过合理使用<scrip...

CSS 中各种居中你真的玩明白了么

页面布局中最常见的需求就是元素或者文字居中了,但是根据场景的不同,居中也有简单到复杂各种不同的实现方式,本篇就带大家一起了解下,各种场景下,该如何使用CSS实现居中前言页面布局中最常见的需求就是元...

CSS样式更改——列表、表格和轮廓

上篇文章主要介绍了CSS样式更改篇中的字体设置Font&边框Border设置,这篇文章分享列表、表格和轮廓,一起来看看吧。1.列表List1).列表的类型<ulstyle='list-...

一文吃透 CSS Flex 布局

原文链接:一文吃透CSSFlex布局教学游戏这里有两个小游戏,可用来练习flex布局。塔防游戏送小青蛙回家Flexbox概述Flexbox布局也叫Flex布局,弹性盒子布局。它决定了...

css实现多行文本的展开收起

背景在我们写需求时可能会遇到类似于这样的多行文本展开与收起的场景:那么,如何通过纯css实现这样的效果呢?实现的难点(1)位于多行文本右下角的展开收起按钮。(2)展开和收起两种状态的切换。(3)文本...

css 垂直居中的几种实现方式

前言设计是带有主观色彩的,同样网页设计中的css一样让人摸不头脑。网上列举的实现方式一大把,或许在这里你都看到过,但既然来到这里我希望这篇能让你看有所收获,毕竟这也是前端面试的基础。实现方式备注:...

WordPress固定链接设置

WordPress设置里的最后一项就是固定链接设置,固定链接设置是决定WordPress文章及静态页面URL的重要步骤,从站点的SEO角度来讲也是。固定链接设置决定网站URL,当页面数少的时候,可以一...

面试发愁!吃透 20 道 CSS 核心题,大厂 Offer 轻松拿

前端小伙伴们,是不是一想到面试里的CSS布局题就发愁?写代码时布局总是对不齐,面试官追问兼容性就卡壳,想跳槽却总被“多列等高”“响应式布局”这些问题难住——别担心!从今天起,咱们每天拆解一...

3种CSS清除浮动的方法

今天这篇文章给大家介绍3种CSS清除浮动的方法。有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助。首先,这里就不讲为什么我们要清楚浮动,反正不清除浮动事多多。下面我就讲3种常用清除浮动的...

2025 年 CSS 终于要支持强大的自定义函数了?

大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!1.什么是CSS自定义属性CSS自...

css3属性(transform)的一个css3动画小应用

闲言碎语不多讲,咱们说说css3的transform属性:先上效果:效果说明:当鼠标移到a标签的时候,从右上角滑出二维码。实现方法:HTML代码如下:需要说明的一点是,a链接的跳转需要用javasc...

CSS基础知识(七)CSS背景

一、CSS背景属性1.背景颜色(background-color)属性值:transparent(透明的)或color(颜色)2.背景图片(background-image)属性值:none(没有)...

CSS 水平居中方式二

<divid="parent"><!--定义子级元素--><divid="child">居中布局</div>...

取消回复欢迎 发表评论: