登录认证-上篇:基于 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分钟无操作则自动过期),强制用户重新登录,这在安全性上很重要。

image

具体代码如下:

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/**"
                );
    }


}


注:本篇文章未实现调用第三方接口实现短信登录,并只作为笔记。

posted @ 2025-09-10 17:58  雨花阁  阅读(32)  评论(0)    收藏  举报