【SpringMVC】深入解析基于Spring MVC与AJAX的用户登录全流程——参数校验、Session管理、前后端交互与安全实践
用户登录
需求:用户输入账号和密码,后端进行校验密码是否正确
- 如果不正确,前端进行用户告知
- 如果正确,跳转到首页。首页显示当前登录用户
- 后续再访问首页,可以获取到登录用户信息
1. 准备工作
把前端页面放在项目中
码云地址: JavaEE进阶课程资料包
2. 约定前后端交互接口
2.1 需求分析
对于后端开发人员而言,不涉及前端页面的展示,只需要提供两个功能
登录页面
:通过账号和密码,校验输入的账号密码是否正确,并告知前端首页
:告知前端当前登录用户。如果当前已有用户登录,返回登录的账号,如果没有,返回空
2.2 接口定义
(1) 校验接口
请求路径:/user/login
请求方式:POST
接口描述:校验账号密码是否正确
请求参数 |
响应数据 |
Content-Type: text/html
响应内容:
true //账号密码验证成功
false //账号密码验证失败
(2) 查询登录用户接口
请求路径:/user/getLoginUser
请求方式:GET
接口描述:查询当前登录的用户
请求参数 |
无
响应数据 |
Content-Type: text/html
响应内容:
zhangsan
返回当前登录的用户
3. 服务器代码
(1) 校验接口
参数输入合法性校验 |
但是上面的校验,写法比较麻烦,我们可以使用 Spring 提供的一个工具类 StringUtils
底下的 hasLength()
方法:
比起StringUtils.hasLength()
,也可以使用StringUtils.hasText()
,后者可以只输入判断空格
时也为 false:
密码校验 |
注意:
- 比较字符串是否相等,需要用到
equals()
,并且遵循常量.equals(变量)
的比较规则;
a.equals(b)
会因为 a 的值为 null 而报空指针异常
,但是 b 的值为 null 不会报空指针异常;- 在当前逻辑中,虽然变量的值为 null ,会在第一轮校验中筛除这种情况;
- 但是在以后写代码时,可能会文件写第一轮校验,
常量.equals(变量)
的写法更符合开发规范;
- 因为我们还没有学关于校验的数据库操作,所以这里先指定账号密码:
对于上述方法,这两个 if 建议不要嵌套;
密码验证成功,把用户名存储在Session中 |
对该接口进行测试 |
我们重新运行程序,使用 Postman 进行测试:
(2) 查询登录用户接口
在刚刚的校验接口中,每次成功登录后,都会先把用户名存储在 Session 中;
在这个接口里,我们需要完成查询登录用户
的功能,就需要使用刚刚存储好的 session 拿到用户信息;
对该接口进行测试 |
重新运行程序,使用 Postman 进行测试:
登录成功的情况:
登录失败的情况:需要先重新运行程序
4. 调整前端页面代码
(1) 调整登录页面 login.html
如果使用 vscode 编写代码,不同用于 idea ,前者需要手动保存
;
对于前端而言,当点击登录按钮
时,需要把用户输入的信息传递到后端进行校验
:
对用户输入的信息传递到后端进行校验的功能,使用
ajax()
请求来完成:
校验的过程在后端完成,使用 ajax() 是把前端输入的数据传入后端,让后端进行校验;
- 后端校验成功,则跳转到首页:
index.html
; - 后端校验失败,
则直接弹窗
我们在前端使用 succse
关键字,表示发送 HTTP 请求给后端,后端成功返回响应:
根据后端返回结果,再完成对应要执行的逻辑:
登录成功,跳转到首页:
前后端设置的请求参数的名字要一致:
一定要注意使用的参数的大小写,每个字母都要相同,否则后续前端传参,后端参数无法对应,就无法抓取登录
:
页面跳转
的三种方式:
- window.location.href = “book_list.html”;
- window.location.assign(“book_list.html”);
- window.location.replace(“book_list.html”);
以上写法,通常把“window.”省略,
比如 window.location.href = "book_list.html"
; 写成location.href = "book_list.html"
;
三者区别参考: location.assign()、location.href、location.replace(urI) 的不同
(2) 调整首页代码
首页代码比较简单,只显示当前登录用户即可。
当前登录用户需要从后端获取,并显示到前端
对象的属性定义没有先后顺序:
对比 login.html
和 index.html
:
login.html
通过 AJAX 发送 POST 请求 → 后端校验参数 → 返回布尔值 → 前端根据结果跳转或提示。
index.html
通过 AJAX 发送 GET 请求 → 后端从 Session 读取用户名 → 动态更新页面。
它们调用 ajax() 请求的方式有所不同:
- 前者通过点击事件,调用
login() 方法
,在方法内部会执行ajax()
; - 后者利用前端会从上往下加载代码,页面加载的过程中,执行到 ajax() 就会向后端发送请求;
为了避免下载 jquery 的网址失效,我们对两个页面都修改 src ,使用本地下载的 jquery
5. 运行测试
我们重新启动程序;
在登录前,我们先打开首页 http://127.0.0.1:8080/index.html,会发现没有登录人的信息:
打开登录页面:http://127.0.0.1:8080/login.html
输入正确密码,登录成功,记住:前后端请求参数名字一定要完全相同
:
6. 理解前后端交互流程
你的理解非常正确!下面我会详细解释整个交互流程,并澄清 AJAX 的功能,同时补充一些关键细节帮助你更全面地理解。
以下是登录过程的完整交互步骤:
(1) 用户在前端输入信息并点击登录
- 前端页面:
login.html
中的输入框收集用户名和密码。 - 触发事件:点击按钮调用
login()
函数,通过 AJAX 发送请求。
(2) 前端通过 AJAX 发送请求
-
AJAX 请求:前端将用户名和密码封装成键值对,发送到后端接口
/user/login
。data: { userName: $("#userName").val(), // 键名与后端参数名一致 password: $("#password").val() }
-
请求方式:
POST
方法,适合提交敏感数据(如密码)。
(3) 后端接收请求并校验
-
参数绑定:Spring MVC 根据参数名
userName
和password
自动绑定到方法的参数。public Boolean login(String userName, String password, HttpSession session) { // 校验逻辑 }
-
校验逻辑:
- 检查用户名和密码非空。
- 验证是否为预设值(
kunkun
和ikun2.5
)。 - 若校验通过,将用户名存入 Session。
(4) 后端返回响应
- 返回结果:
true
(成功)或false
(失败)。 - Session 存储:成功时,后端通过
session.setAttribute("userName", userName)
存储登录状态。
(5) 前端处理响应
- success 回调:根据后端返回的布尔值
result
执行不同逻辑:if (result) { // 跳转到首页 location.href = "index.html"; } else { // 提示密码错误 alert("密码错误, 请重新输入"); }
(6) 跳转至首页后获取登录信息
- 首页请求:
index.html
加载时,通过另一个 AJAX 请求/user/getLoginUser
获取登录用户名。$.ajax({ type: "get", url: "/user/getLoginUser", success: function(userName) { $("#loginUser").text(userName); } });
- Session 读取:后端从 Session 中取出用户名并返回,前端更新页面显示。
2. AJAX 的功能澄清
你对 AJAX 的理解基本正确,以下是关键点补充:
(1) 异步通信
- 不刷新页面:AJAX 允许在不重新加载整个页面的情况下,与服务器交换数据并更新部分内容。
- 用户体验:用户停留在当前页面,仅局部内容变化(如错误提示),适合登录这种需要即时反馈的场景。
(2) 请求与响应
- 发送请求:通过
$.ajax()
配置请求方法(POST
/GET
)、URL、数据等。 - 处理响应:
success
回调函数处理成功响应,error
处理失败(如网络错误)。
(3) 数据格式
- 发送数据:可以是键值对(如
userName: "kunkun"
)或 JSON。 - 接收数据:后端返回的
true
/false
会被自动解析为布尔值,也可返回 JSON 对象。
3. 关键细节补充
(1) Session 的跨页面保持
- Cookie 机制:当 Session 创建时,后端会自动生成一个
JSESSIONID
并存入 Cookie,浏览器后续请求会携带此 Cookie。 - 身份识别:后端通过
JSESSIONID
找到对应的 Session,实现跨页面状态保持(如首页显示用户名)。
(2) 安全性注意事项
- 密码传输:当前代码使用明文传输密码,实际项目中应使用 HTTPS 并加密(如哈希处理)。
- Session 有效期:默认 Session 在用户关闭浏览器后失效,可手动设置超时时间。
(3) 后端逻辑优化
- 错误提示细化:当前返回
true/false
只能提示密码错误,实际可返回 JSON 对象包含具体原因(如{code: 400, message: "用户名不存在"}
)。 - 参数校验增强:使用 Spring Validation 进行更专业的参数校验(如长度、格式)。
4. 总结
- 你的理解完全正确:流程是
前端收集数据 → AJAX 发送 → 后端校验 → 返回结果 → 前端响应
。 - AJAX 核心作用:实现异步通信,提升用户体验。
- 扩展建议:学习 RESTful API 设计、HTTPS 安全、前端路由(如跳转逻辑优化)等。
通过这个流程,你可以清晰地看到数据是如何在前端和后端之间流动的,而 AJAX 是实现这一过程的关键桥梁。
7. 完整前后端代码
login.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>登录页面</title>
</head>
<body>
<h1>用户登录</h1>
用户名:<input name="userName" type="text" id="userName"><br>
密码:<input name="password" type="password" id="password"><br>
<input type="button" value="登录" onclick="login()">
<!-- 点击的时候触发 login()-->
<script src="js/jquery-3.7.1.min.js"></script>
<script>
function login() {
// ajax 的参数是一个对象, 使用 { } 包住表示一个对象, 对象的属性是键值对,不同属性用 "," 分割
$.ajax({
type : "post" ,
url : "/user/login",
data : {
userName : $("#userName").val(),
password : $("#password").val()
},
// 返回响应成功, 调用回调函数, 使用 result 接收后端返回参数
success : function (result){
if(result){
// 返回 true , 密码正确 , 两种执行跳转页面逻辑
location.href = "index.html";
// location.assign("index.html");
}else {
// 密码错误
alert("密码错误, 请重新输入");
}
}
});
}
</script>
</body>
</html>
index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>用户登录首页</title>
</head>
<body>
登录人: <span id="loginUser"></span>
<script src="js/jquery-3.7.1.min.js"></script>
<script>
$.ajax({
type : "get" ,
url : "/user/getLoginUser",
success : function (userName) {
$("#loginUser").text(userName);
}
})
</script>
</body>
</html>
UserController
package com.example.springmvc_demo;
import ch.qos.logback.core.util.StringUtil;
import jakarta.servlet.http.HttpSession;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/user")
@RestController
public class UserController {
@RequestMapping("/login")
public Boolean login(String userName , String password , HttpSession session){
// 参数输入合法性校验: 两个 StringUtils.hasText() 返回 ture, 才返回 true
if(!StringUtils.hasText(userName) || !StringUtils.hasText(password)){
return false;
}
// 校验用户名和密码是否正确
// 还未学习数据库相关操作,暂且把账号和密码写死 userName: kunkun password: Ikun2.5
if(!"kunkun".equals(userName) || !"ikun2.5".equals(password)){
return false;
}
// 密码验证成功,把用户名存储在Session中
session.setAttribute("userName", userName);
return true;
}
@RequestMapping("/getLoginUser")
public String getLoginUser(HttpSession session){
//从Session中获取用户登录信息
String userName = (String)session.getAttribute("userName");
//如果用户已经登录,则直接返回用户登录
if(StringUtils.hasLength(userName)){
return userName;
}
return "";
}
}