登录认证-上篇:基于 Session 的传统身份验证
基于 Session 的登录机制的主要作用:
1. 核心作用:维持登录状态 (Maintaining Login State)
这是最基本的作用。一旦用户登录成功,服务器就在 Session 中做一个标记(session.setAttribute("user", ...))。浏览器持有的 JSESSIONID Cookie 就变成了这个标记的“钥匙”。只
要钥匙有效(Session 未过期),服务器就认为用户是登录状态,无需再次输入用户名密码。
2. 服务器端数据存储 (Server-Side Storage)
Session 是服务器内存(或数据库/Redis)中的一块存储空间。你可以把与当前用户会话相关的任何数据存进去,而不仅仅是用户ID。
在代码中:
存了验证码 (session.setAttribute("code", code))
存了用户信息 (session.setAttribute("user", userDTO))
其他常见用途:
购物车:用户将商品加入购物车,在付款前这些信息可以暂存在 Session 中。
多步表单数据:比如一个需要分三步填写的注册表单,每一步的数据都可以先存到 Session,最后一步再统一提交到数据库。
用户偏好设置:如语言、主题等,在本次会话中生效。
3. 安全性 (Security)
敏感信息不暴露给客户端:用户的身份信息(如ID、角色、权限)只保存在服务器的 Session 中,客户端只有一个无意义的 JSESSIONID。这比把用户信息直接放在客户端的 Cookie 里要安全得多。
访问控制: LoginInterceptor 完美体现了这一点。它可以拦截所有请求,检查 Session 中是否存在用户信息,从而实现统一的权限校验。没有合法 Session 的请求一律拒绝(返回401或跳转到登录页)。
控制会话生命周期:服务器可以主动设置 Session 的过期时间(如30分钟无操作则自动过期),强制用户重新登录,这在安全性上很重要。

具体代码如下:
UserController
package com.hmdp.controller;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.UserInfo;
import com.hmdp.service.IUserInfoService;
import com.hmdp.service.IUserService;
import com.hmdp.utils.UserHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpSession;
/**
* <p>
* 前端控制器
* </p>
*
* @author ztn
* @since 2025-9-10
*/
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@Resource
private IUserService userService;
@Resource
private IUserInfoService userInfoService;
/**
* 发送手机验证码
*/
@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {
// TODO 发送短信验证码并保存验证码
return userService.sendCode(phone,session);
}
/**
* 登录功能
* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码
*/
@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){
// TODO 实现登录功能
return userService.login(loginForm,session);
}
/**
* 登出功能
* @return 无
*/
@PostMapping("/logout")
public Result logout(){
// TODO 实现登出功能
return Result.fail("功能未完成");
}
@GetMapping("/me")
public Result me(){
// TODO 获取当前登录的用户并返回
UserDTO user = UserHolder.getUser();
return Result.ok(user);
}
@GetMapping("/info/{id}")
public Result info(@PathVariable("id") Long userId){
// 查询详情
UserInfo info = userInfoService.getById(userId);
if (info == null) {
// 没有详情,应该是第一次查看详情
return Result.ok();
}
info.setCreateTime(null);
info.setUpdateTime(null);
// 返回
return Result.ok(info);
}
}
UserServiceImpl
package com.hmdp.service.impl;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.dto.LoginFormDTO;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.User;
import com.hmdp.mapper.UserMapper;
import com.hmdp.service.IUserService;
import com.hmdp.utils.RegexUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
import java.util.Random;
import static com.hmdp.utils.SystemConstants.USER_NICK_NAME_PREFIX;
/**
* <p>
* 服务实现类
* </p>
*
* @author ztn
* @since 2025-9-10
*/
@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Override
public Result sendCode(String phone, HttpSession session) {
//1.校验手机号,isPhoneInvalid是无效
if(RegexUtils.isPhoneInvalid(phone)){
//2.如果不符合,返回错误信息
return Result.fail("手机号格式错误!");
}
//3.符合,生成验证码
String code = RandomUtil.randomNumbers(6);
//4.保存验证码到session
session.setAttribute("code",code);
//5.发送验证码
log.debug("发送短信验证码成功,验证码:{}",code);
//返回ok
return Result.ok();
}
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {
String phone = loginForm.getPhone();
//1.校验手机号,isPhoneInvalid是无效
if(RegexUtils.isPhoneInvalid(phone)){
//2.如果不符合,返回错误信息
return Result.fail("手机号格式错误!");
}
//2.校验验证码
Object cachecode = session.getAttribute("code");
String code = loginForm.getCode();
if(cachecode == null && !cachecode.toString().equals(code)){
//3.不一致,报错
return Result.fail("验证码错误");
}
//4.一致,根据手机号查询用户select * from tb_user where phone = ?
User user = query().eq("phone", phone).one();
//5.判断用户是否存在
if(user == null){
//6.不存在,创建用户并保存
user = createUserWithphone(phone);
}
//7.保存用户信息到session中
session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));
return Result.ok();
}
private User createUserWithphone(String phone) {
//1.创建用户
User user = new User();
user.setPhone(phone);
user.setNickName(USER_NICK_NAME_PREFIX+RandomUtil.randomString(10));
//2.保存用户
save(user);
return user;
}
}
LoginInterceptor
package com.hmdp.utils;
import com.hmdp.dto.UserDTO;
import org.springframework.lang.Nullable;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//1.获取session
HttpSession session = request.getSession();
//2.获取session中的用户
Object user = session.getAttribute("user");
//3.判断用户是否存在
if (user == null) {
//4.不存在,拦截
response.setStatus(401);
return false;
}
//5.存在,保存用户信息到ThreadLocal
UserHolder.saveUser((UserDTO) user);
//6.放行
return true;
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
//移除用户
UserHolder.removeUser();
}
}
MvcConfig
package com.hmdp.config;
import com.hmdp.utils.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.excludePathPatterns(//放行
"/user/code",
"/user/login",
"/blog/hot",
"/shop/**",
"/upload/**",
"/shop-type/**",
"/voucher/**"
);
}
}
注:本篇文章未实现调用第三方接口实现短信登录,并只作为笔记。
浙公网安备 33010602011771号