一、登录校验的核心意义与整体流程
1.1 核心意义
登录校验是Web应用的基础安全防护手段,核心作用是确认用户身份合法性,防止未登录用户访问系统敏感资源(如个人中心、后台管理页面、接口等),同时区分不同用户的操作权限,保障系统数据安全和用户信息隐私。
简单来说,登录校验就是“守门人”——只有通过身份验证(输入正确的账号密码等凭证)的用户,才能进入系统的“核心区域”,未登录用户会被拦截并引导至登录页面。
1.2 登录校验整体流程(
无论使用哪种会话技术和拦截技术,登录校验的核心流程一致,分为3步:
-
用户登录:用户提交账号、密码等凭证,服务器校验凭证合法性(如查询数据库匹配账号密码)。
-
身份标识存储:校验通过后,服务器生成唯一的用户身份标识(如SessionID、Token),并通过会话技术将标识传递给客户端,由客户端保存。
-
后续请求校验:用户后续访问敏感资源时,客户端携带身份标识发送请求,服务器通过拦截技术拦截请求,校验标识的合法性(如判断标识是否有效、是否对应合法用户),校验通过则放行,失败则引导至登录页面。
其中,会话技术负责“身份标识的存储与传递”,拦截技术负责“拦截请求并校验身份标识”,二者协同完成完整的登录校验。
二、核心技术一:会话技术(身份标识的存储与传递)
会话(Session)本质是“客户端与服务器之间的一次交互周期”,会话技术的核心是解决“HTTP协议无状态”的问题——HTTP协议本身不记录用户的访问状态,每次请求都是独立的,服务器无法识别“当前请求来自哪个用户”,而会话技术通过存储用户身份标识,让服务器记住用户状态。
常用的会话技术有3种:Cookie、Session、Token,其中Cookie和Session是传统会话方式,Token是当前前后端分离项目的主流方式。
2.1 Cookie(客户端存储技术)
2.1.1 核心定义
Cookie是服务器发送给客户端的小型文本文件,由客户端(浏览器)保存,每次客户端向服务器发送请求时,会自动携带对应的Cookie,服务器通过Cookie中的信息识别用户身份。
Cookie的核心特点:存储在客户端(浏览器),容量有限(通常4KB),可设置过期时间,支持跨请求携带。
2.1.2 登录校验中的使用流程
-
用户登录:提交账号密码,服务器校验通过后,生成唯一的用户标识(如userId)。
-
服务器创建Cookie:将用户标识(如userId=123)存入Cookie,设置过期时间、访问路径等属性,发送给客户端。
-
客户端保存Cookie:浏览器接收Cookie后,按规则保存(内存中或本地文件)。
-
后续请求:客户端访问敏感资源时,自动携带该Cookie,服务器读取Cookie中的userId,校验用户合法性。
2.1.3 实操代码(Java Web,Servlet方式)
// 1. 登录接口:校验通过后,创建Cookie并发送给客户端
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 接收账号密码(简化,实际需做参数校验)
String username = req.getParameter("username");
String password = req.getParameter("password");
// 模拟校验:假设账号密码正确(实际需查询数据库)
if ("admin".equals(username) && "123456".equals(password)) {
// 生成用户标识(如用户ID)
Integer userId = 1;
// 创建Cookie,存储用户标识
Cookie cookie = new Cookie("userId", userId.toString());
// 设置Cookie属性
cookie.setMaxAge(3600); // 过期时间:1小时(单位:秒),0表示立即删除,-1表示关闭浏览器失效
cookie.setPath("/"); // 访问路径:根路径,所有请求都能携带该Cookie
cookie.setHttpOnly(true); // 禁止前端JS操作Cookie,防止XSS攻击(重要安全配置)
// 发送Cookie到客户端
resp.addCookie(cookie);
// 响应登录成功
resp.getWriter().write("登录成功");
} else {
resp.getWriter().write("账号密码错误");
}
}
}
2.1.4 优缺点
-
优点:实现简单,无需客户端额外操作,自动携带Cookie;服务器无需存储过多用户信息,减轻服务器压力。
-
缺点:存储容量有限;存在安全风险(可被篡改、窃取,需配合HttpOnly、HTTPS);跨域请求时Cookie携带受限(需配置跨域允许Cookie)。
2.2 Session(服务器端存储技术)
2.2.1 核心定义
Session是服务器端为每个登录用户创建的会话对象,用于存储用户的身份信息、会话状态等数据,每个Session对应一个唯一的SessionID(由服务器生成),SessionID通过Cookie传递给客户端,客户端后续请求携带SessionID,服务器通过SessionID找到对应的Session对象,识别用户身份。
Session的核心特点:存储在服务器端,容量无限制(取决于服务器内存),安全性高,默认随浏览器关闭而失效(可手动设置过期时间)。
关键关联:Session依赖Cookie——SessionID必须通过Cookie传递给客户端,若客户端禁用Cookie,Session无法使用(可通过URL重写规避,但不推荐)。
2.2.2 登录校验中的使用流程
-
用户登录:提交账号密码,服务器校验通过后,创建Session对象,存入用户信息(如userId、username)。
-
服务器生成SessionID:将SessionID存入Cookie,发送给客户端(默认Cookie名称为JSESSIONID)。
-
客户端保存Cookie:浏览器保存携带SessionID的Cookie。
-
后续请求:客户端携带Cookie(SessionID)请求敏感资源,服务器通过SessionID找到对应的Session对象,若Session存在且有效,则放行;否则引导登录。
2.2.3 实操代码(Java Web,Servlet方式)
// 1. 登录接口:校验通过后,创建Session并存储用户信息
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String username = req.getParameter("username");
String password = req.getParameter("password");
// 模拟校验通过
if ("admin".equals(username) && "123456".equals(password)) {
// 获取Session(若不存在则自动创建)
HttpSession session = req.getSession();
// 存储用户信息到Session
session.setAttribute("userId", 1);
session.setAttribute("username", "admin");
// 设置Session过期时间:1小时(单位:秒),默认30分钟
session.setMaxInactiveInterval(3600);
resp.getWriter().write("登录成功");
} else {
resp.getWriter().write("账号密码错误");
}
}
}
// 2. 测试接口:需要登录才能访问
@WebServlet("/user/info")
public class UserInfoServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 获取Session(false:若不存在则返回null,不创建新Session)
HttpSession session = req.getSession(false);
if (session == null || session.getAttribute("userId") == null) {
// 未登录,引导至登录页面
resp.sendRedirect("/login.html");
return;
}
// 已登录,返回用户信息
String username = (String) session.getAttribute("username");
resp.getWriter().write("当前登录用户:" + username);
}
}
2.2.4 优缺点
-
优点:安全性高(用户信息存储在服务器端,不易被篡改);存储容量大,可存储复杂用户信息;操作简单,Java Web原生支持。
-
缺点:服务器压力大(每个用户对应一个Session,占用服务器内存);不支持分布式部署(Session存储在单个服务器,多服务器集群时无法共享);依赖Cookie,客户端禁用Cookie则失效。
2.3 Token(无状态会话技术,主流)
2.3.1 核心定义
Token(令牌)是服务器校验用户身份通过后,生成的一串加密字符串,包含用户身份信息(如userId)、过期时间等数据,服务器不存储Token,仅负责生成和校验Token,客户端(浏览器、APP)存储Token(如localStorage、sessionStorage、Cookie),后续请求携带Token,服务器通过解密Token校验用户合法性。
Token的核心特点:无状态(服务器不存储Token,减轻服务器压力);支持跨域、分布式部署;不依赖Cookie,可适配APP、小程序等非浏览器场景;可自定义加密规则,安全性高。
常用Token类型:JWT(JSON Web Token),是目前最主流的Token格式,结构清晰、易于解析,广泛应用于前后端分离项目。
2.3.2 JWT Token结构(必记)
JWT Token由3部分组成,用“.”分隔,整体格式:Header.Payload.Signature
-
Header(头部):指定加密算法(如HS256)和Token类型(JWT),经过Base64编码(可解码,不加密)。
-
Payload(载荷):存储核心数据(如userId、username、过期时间),经过Base64编码(可解码,不加密,禁止存储敏感信息如密码)。
-
Signature(签名):用Header指定的加密算法,结合服务器秘钥(secret),对Header和Payload的编码结果进行加密,用于校验Token是否被篡改(核心安全保障)。
2.3.3 登录校验中的使用流程(JWT为例)
-
用户登录:提交账号密码,服务器校验通过后,生成JWT Token(包含userId、过期时间等)。
-
服务器返回Token:将Token通过响应体(如JSON)返回给客户端,不依赖Cookie。
-
客户端存储Token:客户端将Token存入localStorage(持久化)或sessionStorage(会话级)。
-
后续请求:客户端每次请求敏感资源时,通过请求头(如Authorization: Bearer Token)携带Token。
-
服务器校验Token:拦截请求,提取Token,用秘钥解密校验(判断Token是否过期、是否被篡改),校验通过则放行,失败则返回未登录提示。
2.3.4 实操代码(Spring Boot + JWT)
第一步:导入JWT依赖(Maven)
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
第二步:编写JWT工具类(生成Token、校验Token)
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
public class JwtUtils {
// 服务器秘钥(自定义,必须保密,用于签名和验签)
private static final String SECRET = "abc1234567890xyz";
// Token过期时间:1小时(单位:毫秒)
private static final long EXPIRATION = 3600000;
// 生成JWT Token
public static String generateToken(Integer userId) {
Date now = new Date();
Date expirationDate = new Date(now.getTime() + EXPIRATION);
return Jwts.builder()
.setSubject(userId.toString()) // 存储用户ID(Payload)
.setIssuedAt(now) // 生成时间
.setExpiration(expirationDate) // 过期时间
.signWith(SignatureAlgorithm.HS256, SECRET) // 加密算法+秘钥
.compact();
}
// 校验Token,并获取Token中的用户ID
public static Integer verifyToken(String token) {
try {
// 解析Token,获取Payload
Claims claims = Jwts.parser()
.setSigningKey(SECRET) // 用秘钥验签
.parseClaimsJws(token)
.getBody();
// 提取用户ID并返回
return Integer.parseInt(claims.getSubject());
} catch (Exception e) {
// Token无效(过期、篡改、格式错误)
return null;
}
}
}
第三步:登录接口(生成Token)
@RestController
public class LoginController {
// 模拟登录校验(实际需查询数据库)
@PostMapping("/login")
public Result login(@RequestBody LoginDTO loginDTO) {
String username = loginDTO.getUsername();
String password = loginDTO.getPassword();
if ("admin".equals(username) && "123456".equals(password)) {
// 校验通过,生成Token
String token = JwtUtils.generateToken(1); // 假设用户ID为1
return Result.success("登录成功", token);
}
return Result.error("账号密码错误");
}
}
2.3.5 优缺点
-
优点:无状态,减轻服务器压力;支持分布式部署、跨域;不依赖Cookie,适配多端(浏览器、APP、小程序);安全性高(签名机制防止篡改)。
-
缺点:Token一旦生成,无法主动撤回(除非修改秘钥或等待过期);Payload部分可解码,禁止存储敏感信息;需要手动实现Token的生成、校验逻辑。
2.4 三种会话技术对比(实战选型重点)
| 技术类型 | 存储位置 | 安全性 | 分布式支持 | 适配场景 |
|---|---|---|---|---|
| Cookie | 客户端(浏览器) | 较低(可篡改、窃取) | 差(跨服务器Cookie不共享) | 简单Web项目、存储少量非敏感信息 |
| Session | 服务器端 | 较高(用户信息不暴露) | 差(需额外配置Session共享) | 传统Java Web项目、非分布式部署 |
| Token(JWT) | 客户端(任意存储) | 高(签名校验,防篡改) | 好(无状态,支持集群) | 前后端分离项目、分布式项目、多端适配(APP/小程序) |
三、核心技术二:拦截技术(请求拦截与身份校验)
拦截技术的核心作用是“拦截客户端发送的请求”,在请求到达目标接口/资源前,校验用户的身份标识(Cookie、SessionID、Token),实现“统一登录校验”——无需在每个敏感接口中重复编写校验逻辑,提升代码复用性和维护性。
Java Web中常用的拦截技术有2种:Filter(过滤器)和Interceptor(拦截器),二者功能类似,但底层实现、适用场景有差异,实际开发中可单独使用,也可结合使用。
3.1 Filter(过滤器,Java Web原生)
3.1.1 核心定义
Filter是Java Web原生的拦截组件,属于Servlet规范的一部分,运行在请求进入Servlet之前,可拦截所有请求(包括静态资源、接口请求),核心功能是对请求进行预处理(如登录校验、编码设置、跨域配置)。
Filter的核心特点:基于函数回调实现,拦截范围广(所有请求),优先级高于Interceptor,可修改请求和响应对象。
3.1.2 登录校验中的使用(Session为例)
// 登录校验过滤器
@WebFilter(urlPatterns = "/user/*") // 拦截/user/开头的所有请求(敏感资源)
public class LoginFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
// 1. 转换请求/响应对象(ServletRequest -> HttpServletRequest)
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
// 2. 获取Session,校验登录状态
HttpSession session = req.getSession(false);
if (session == null || session.getAttribute("userId") == null) {
// 未登录,响应提示或引导至登录页面
resp.setContentType("text/html;charset=utf-8");
resp.getWriter().write("请先登录!");
return; // 拦截请求,不继续向下执行
}
// 3. 已登录,放行请求(让请求到达目标接口/资源)
filterChain.doFilter(req, resp);
}
}
3.1.3 关键说明
-
urlPatterns:指定拦截的请求路径(如/user/*拦截所有/user/开头的请求,/*拦截所有请求)。
-
FilterChain.doFilter():放行请求,若不调用该方法,请求会被拦截,无法到达目标资源。
-
放行白名单:实际开发中,需对登录接口(/login)、注册接口、静态资源(如/login.html)设置白名单,避免拦截。
3.2 Interceptor(拦截器,Spring框架提供)
3.2.1 核心定义
Interceptor是Spring框架提供的拦截组件,运行在Spring MVC的DispatcherServlet之后、目标Controller之前,仅拦截Controller接口请求(不拦截静态资源),核心功能是对接口请求进行预处理和后处理(如登录校验、日志记录)。
Interceptor的核心特点:基于AOP思想实现,与Spring框架深度整合,可注入Spring容器中的Bean(如Service、Mapper),仅拦截接口请求,灵活性高。
3.2.2 登录校验中的使用(JWT Token为例)
第一步:编写拦截器类
// 登录校验拦截器
@Component
public class LoginInterceptor implements HandlerInterceptor {
// 预处理方法:请求到达Controller之前执行(核心校验逻辑)
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 1. 提取请求头中的Token
String token = request.getHeader("Authorization");
// 处理Token格式(若前端传递格式为Bearer Token,需截取)
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
}
// 2. 校验Token
Integer userId = JwtUtils.verifyToken(token);
if (userId == null) {
// 未登录,返回JSON提示(前后端分离场景)
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(JSON.toJSONString(Result.error("请先登录")));
return false; // 拦截请求,不进入Controller
}
// 3. 已登录,放行请求(可将用户ID存入请求域,供Controller使用)
request.setAttribute("userId", userId);
return true;
}
}
第二步:配置拦截器(Spring Boot)
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginInterceptor loginInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 注册拦截器
registry.addInterceptor(loginInterceptor)
.addPathPatterns("/api/**") // 拦截/api/开头的所有接口(敏感接口)
.excludePathPatterns("/api/login", "/api/register"); // 白名单(不拦截)
}
}
3.2.3 关键说明
-
preHandle():预处理方法,返回true则放行,返回false则拦截,是登录校验的核心方法。
-
addPathPatterns():指定拦截的接口路径,支持通配符(如/**拦截所有接口)。
-
excludePathPatterns():指定不拦截的白名单(如登录、注册接口),避免拦截必要请求。
-
依赖注入:Interceptor可注入Spring容器中的Bean(如JwtUtils、UserService),方便扩展校验逻辑(如查询用户是否有效)。
3.3 Filter与Interceptor对比(选型重点)
| 对比维度 | Filter(过滤器) | Interceptor(拦截器) |
|---|---|---|
| 底层实现 | Servlet规范,基于函数回调 | Spring AOP,基于反射 |
| 拦截范围 | 所有请求(静态资源、接口) | 仅拦截Controller接口请求 |
| 依赖框架 | 无,Java Web原生支持 | 依赖Spring框架 |
| 依赖注入 | 不支持注入Spring Bean | 支持注入Spring Bean,灵活性高 |
| 执行时机 | 请求进入Servlet之前 | DispatcherServlet之后,Controller之前 |
| 适用场景 | 编码设置、跨域配置、拦截静态资源 | 接口登录校验、日志记录、权限控制 |
四、登录校验实战注意事项(避坑重点)
-
身份标识的安全防护:
-
Cookie需设置HttpOnly=true,禁止前端JS操作,防止XSS攻击;设置Secure=true,仅在HTTPS协议下传输,防止Cookie被窃取。
-
Token需使用复杂秘钥,避免秘钥泄露;Payload部分禁止存储敏感信息(如密码、手机号),仅存储userId等非敏感标识。
-
SessionID、Token需设置合理的过期时间,避免长期有效导致安全风险(如1-2小时,可根据业务调整)。
-
-
白名单配置:必须对登录接口、注册接口、静态资源(登录页面、CSS、JS)设置白名单,否则会出现“无法访问登录页面”的死循环。
-
分布式部署适配:
-
使用Session时,需配置Session共享(如Redis存储Session),避免多服务器集群时Session无法共享导致登录失效。
-
使用Token时,无需额外配置,天然支持分布式(服务器仅需统一秘钥即可校验Token)。
-
-
Token的撤回问题:Token一旦生成无法主动撤回,可通过“Token黑名单”(如Redis存储失效Token)解决——用户退出登录时,将Token存入黑名单,校验时先判断Token是否在黑名单中。
-
请求头携带Token规范:前后端分离场景中,建议使用Authorization请求头,格式为“Bearer Token”,便于统一拦截和解析。
-
异常处理:登录校验失败时(Token过期、无效,Session失效),需返回清晰的提示信息(如“请先登录”“登录已过期,请重新登录”),并引导用户重新登录。
五、总结
登录校验的核心是“身份标识的生成-存储-校验”,其中会话技术负责“生成和存储身份标识”,拦截技术负责“拦截请求并校验身份标识”,二者协同完成安全防护。
选型建议:传统Java Web项目可使用Session+Filter/Interceptor;前后端分离、分布式项目优先使用Token(JWT)+Interceptor,兼顾灵活性和安全性;简单项目可使用Cookie,但需做好安全配置。
实际开发中,登录校验不仅要实现“是否登录”的基础校验,还需结合权限控制(如不同角色访问不同资源),同时做好安全防护,避免出现XSS、CSRF、Token泄露等安全问题,保障系统和用户数据安全。