Java外功实战(4)——SpringBoot登录认证全栈实现:Session、统一结果封装、MD5加密与拦截器

Java外功实战(4)——SpringBoot登录认证全栈实现:Session、统一结果封装、MD5加密与拦截器

本文简介

目的:Spring生态为Java后端开发提供了强大支持,但将分散的技术点整合成完整解决方案往往令人困惑。本文将以登录接口为切入点,系统演示如何将IOC/DI、MyBatis数据持久化、MD5加密、Session/Cookie管理、JWT令牌和拦截器机制融合运用,打造企业级认证方案
技术栈:前端:HTML + CSS + JavaScript + Jquery后端:SpringBoot + Mybatis + JWT

搭建环境:数据库:MySQL8.4.0项目结构:maven前端框架:Jquery后端框架:SpringBootJDK:17编译器:IDEA

目录结构

项目搭建及配置

1.创建SpringBoot3.0.0+项目并添加依赖:Spring Web、MyBatis Framework、MySQL Driver、Lombok
2.初始化数据库:

createdatabase spring_blog_login charset utf8mb4;use spring_blog_login;createtable user_info (id intprimarykeyauto_increment,user_name varchar(128)unique, password varchar(128)notnull,delete_flag intdefault0, create_time datetimedefaultnow(),update_time datetimedefaultnow());insertinto user_info (user_name,password)values('张三','123456'),('李四','123456'),('王五','123456');

3.将application.properties修改为application.yml并添加如下配置:

spring:datasource:url: jdbc:mysql://127.0.0.1:3306/spring_blog_login?characterEncoding=utf8&useSSL=falseusername: root password:123456driver-class-name: com.mysql.cj.jdbc.Driver mybatis:configuration:map-underscore-to-camel-case:true#自动驼峰转换server:port:8080#不显式设置默认为8080

按住Ctrl + F5,如果程序能运行成功则说明搭建及配置都没问题(MySQL服务器必须要处于运行状态)

在这里插入图片描述

1.登录认证全栈实现 ->基础版

1.1 后端实现

1.1.1 架构设计

本次登录功能采用Controller、Service、Mapper三层架构:Controller层依赖于Service层来执行业务逻辑并获取处理结果,而Service层又依赖于Mapper层来进行数据持久化操作

在这里插入图片描述

1.1.2 实体类

实体类用于封装业务数据,需要与数据库表结构一一对应

importlombok.Data;importjava.util.Date;@DatapublicclassUserInfo{privateInteger id;privateString userName;privateString password;privateInteger deleteFlag;privateDate createTime;privateDate updateTime;}

1.1.3 Controller

处理HTTP请求、参数校验、返回响应

importorg.example.springlogin.service.UserService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.util.StringUtils;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;@RestController@RequestMapping("/user")publicclassUserController{privatefinalUserService userService;@AutowiredpublicUserController(UserService userService){this.userService = userService;}@RequestMapping("/login")publicStringlogin(String userName,String password){if(!StringUtils.hasLength(userName)||!StringUtils.hasLength(password)){return"用户或密码为空";}return userService.getUserInfoByUserName(userName,password);}}

1.1.4 Service

业务逻辑处理

importorg.example.springlogin.mapper.UserMapper;importorg.example.springlogin.model.UserInfo;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;@ServicepublicclassUserService{privatefinalUserMapper userMapper;@AutowiredpublicUserService(UserMapper userMapper){this.userMapper = userMapper;}publicStringgetUserInfoByUserName(String userName,String password){UserInfo userInfo = userMapper.getUserInfoByUserName(userName);if(userInfo ==null){return"用户不存在";}if(!password.equals(userInfo.getPassword())){return"密码错误";}return"登录成功";}}

1.1.5 Mapper

数据持久化操作

importorg.apache.ibatis.annotations.Mapper;importorg.apache.ibatis.annotations.Select;importorg.example.springlogin.model.UserInfo;@MapperpublicinterfaceUserMapper{@Select("select * from user_info where user_name = #{userName}")UserInfogetUserInfoByUserName(String userName);}

1.2 前端实现

Gitee:项目前端代码,Gitee上的前端代码是最新提交的,如下效果图仅作参考
效果演示:

4.登录成功

在这里插入图片描述

3.密码错误

在这里插入图片描述

2.用户不存在

在这里插入图片描述

1.用户或密码为空

在这里插入图片描述

2.Cookie/Session

HTTP(超文本传输协议)设计为无状态协议,指服务器默认不保留客户端请求之间的任何状态信息。每个请求独立处理,服务器不会记忆之前的交互内容(如下图)
优点:请求独立性:每次请求被视为新请求,服务器不依赖历史请求数据简单高效:无状态设计降低服务器资源消耗,简化实现逻辑

缺点:身份识别困难:需通过额外机制(如Cookies、Session)跟踪用户状态重复传输数据:每次请求需携带完整信息,可能增加冗余(如认证信息)
cookie:是存储在客户端(浏览器)的小型文本数据,由服务器通过HTTP响应头Set-Cookie发送给客户端,并在后续请求中自动携带
session:是存储在服务器端的用户状态信息,通常通过一个唯一的Session ID标识,该ID可能通过Cookie或URL传递
如上图片引用自我的博客:Java EE(13)——网络原理——应用层HTTP协议,服务器内部实际上专门开辟了一个session空间用于存储用户信息,每当新用户发送第一次请求时服务器会将用户信息存储在session中并生成一个session id通过Set-Cookie方法返回给客户端,即cookie
session结构如下:

修改Controller类代码:

importjakarta.servlet.http.HttpSession;importlombok.extern.slf4j.Slf4j;importorg.example.springlogin.service.UserService;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.util.StringUtils;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.util.HashMap;@RestController@RequestMapping("/user")@Slf4jpublicclassUserController{privatefinalUserService userService;@AutowiredpublicUserController(UserService userService){this.userService = userService;}@RequestMapping("/login")publicStringlogin(String userName,String password,HttpSession session){ log.info("接收到参数,userName:{},password:{}",userName,password);if(!StringUtils.hasLength(userName)||!StringUtils.hasLength(password)){return"用户或密码为空";}String result = userService.getUserInfoByUserName(userName, password);if(result.equals("登录成功")){HashMap<String,String> map =newHashMap<>(); map.put("userName",userName); map.put("password",password);//将map作为用户信息存储到session/会话中 session.setAttribute("cookie", map); log.info("登录成功");}return result;}

修改前端代码:

functionlogin(){ $.ajax({url:'/user/login',type:"post",data:{userName:$('#username').val(),password:$('#password').val(),},success:function(result){alert(result);},})}
Fiddler抓包结果
前端/浏览器按住Ctrl + Shift + i打开控制台点击应用程序/application,打开Cookie

3.统一返回结果封装

统一返回结果封装是后端开发中的重要设计模式,能够保持API响应格式的一致性,便于前端处理

1.创建枚举类:统一管理接口或方法的返回状态码和描述信息,标准化业务逻辑中的成功或失败状态

importlombok.Getter;@GetterpublicenumResultStatus{SUCCESS(200,"成功"),FAIL(-1,"失败"),;privatefinalInteger code;privatefinalString message;ResultStatus(Integer code,String message){this.code = code;this.message = message;}}

2.创建Result< T >类:主要用于规范服务端返回给客户端的响应数据格式。通过固定结构(状态码、错误信息、数据)确保前后端交互的一致性

importlombok.Data;@Data//通过泛型<T>设计,可以灵活封装任意类型的数据对象到data字段publicclassResult<T>{//业务码privateResultStatus code;//错误信息privateString errorMessage;//数据privateT data;publicstatic<T>Result<T>success(T data){Result<T> result =newResult<>(); result.setCode(ResultStatus.SUCCESS); result.setErrorMessage(null); result.setData(data);return result;}publicstatic<T>Result<T>fail(String errorMessage){Result<T> result =newResult<>(); result.setCode(ResultStatus.FAIL); result.setErrorMessage(errorMessage); result.setData(null);return result;}}

3.修改Controller代码

@RestController@RequestMapping("/user")@Slf4jpublicclassUserController{privatefinalUserService userService;@AutowiredpublicUserController(UserService userService){this.userService = userService;}@RequestMapping("/login")publicResult<String>login(String userName,String password,HttpSession session){ log.info("接收到参数,userName:{},password:{}",userName,password);if(!StringUtils.hasLength(userName)||!StringUtils.hasLength(password)){returnResult.fail("用户或密码为空");}String result = userService.getUserInfoByUserName(userName, password);if(!result.equals("登录成功")){returnResult.fail(result);}HashMap<String,String> map =newHashMap<>(); map.put("userName",userName); map.put("password",password);//将map作为用户信息存储到session/会话中 session.setAttribute("cookie", map); log.info("登录成功");returnResult.success(result);}}

4.修改前端代码

functionlogin(){ $.ajax({url:'/user/login',type:"post",data:{userName:$('#username').val(),password:$('#password').val(),},success:function(result){if(result.code ==="SUCCESS"){alert(result.data)}else{alert(result.error)}},})}

4.图形验证码

图形验证码(captcha)是一种区分用户是人类还是自动化程序的技术,主要通过视觉或交互任务实现。其核心意义体现在以下方面:防止自动化攻击:通过复杂图形或扭曲文字,阻止爬虫、暴力破解工具等自动化程序批量注册或登录,降低服务器压力提升安全性:在敏感操作(如支付、修改密码)前增加验证步骤,减少数据泄露或恶意操作风险

Hutool提供了CaptchaUtil类用于快速生成验证码,支持图形验证码和GIF动态验证码。在pom.xml文件中添加图下配置:

<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><!-- 版本号应与springboot版本兼容 --><version>5.8.40</version></dependency>

1.创建CaptchaController类,用于生成验证码并返回给前端

importcn.hutool.captcha.CaptchaUtil;importcn.hutool.captcha.LineCaptcha;importjakarta.servlet.http.HttpServletResponse;importjakarta.servlet.http.HttpSession;importlombok.extern.slf4j.Slf4j;importorg.springframework.web.bind.annotation.RequestMapping;importorg.springframework.web.bind.annotation.RestController;importjava.io.IOException;@RestController@RequestMapping("/captcha")@Slf4jpublicclassCaptchaController{//设置过期时间publicfinalstaticlong delay =60_000L;@RequestMapping("/get")publicvoidgetCaptcha(HttpSession session,HttpServletResponse response){ log.info("getCaptcha");LineCaptcha lineCaptcha =CaptchaUtil.createLineCaptcha(200,100);//设置返回类型 response.setContentType("image/jpeg");//禁止缓存 response.setHeader("Pragma","No-cache");try{//通过响应输出生成的图形验证码 lineCaptcha.write(response.getOutputStream());//保存code session.setAttribute("CAPTCHA_SESSION_CODE", lineCaptcha.getCode());//保存当前时间 session.setAttribute("CAPTCHA_SESSION_DATE",System.currentTimeMillis());//关闭输出流 response.getOutputStream().close();}catch(IOException e){thrownewRuntimeException(e);}}}

2.修改前端代码:最终版

<!DOCTYPEhtml><htmllang="zh-CN"><head><metacharset="UTF-8"><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>微信登录</title><linkrel="stylesheet"href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"><linkrel="stylesheet"href="css/login.css"></head><body><divclass="login-container"><divclass="logo"><iclass="fab fa-weixin"></i></div><h2>微信登录</h2><formid="loginForm"><divclass="input-group"><iclass="fas fa-user"></i><labelfor="username"></label><inputtype="text"id="username"placeholder="请输入用户名"required></div><divclass="input-group"><iclass="fas fa-lock"></i><labelfor="password"></label><inputtype="password"id="password"placeholder="请输入密码"required></div><divclass="input-group"><divclass="captcha-container"><labelfor="inputCaptcha"></label><inputtype="text"id="inputCaptcha"class="captcha-input"placeholder="输入验证码"><imgid="verificationCodeImg"src="/captcha/get"class="captcha-img"title="看不清?换一张"alt="验证码"></div></div><divclass="agreement"><inputtype="checkbox"id="agreeCheck"checked><labelfor="agreeCheck">我已阅读并同意<ahref="#">《服务条款》</a>和<ahref="#">《隐私政策》</a></label></div><buttontype="submit"class="login-btn"onclick="login()">登录</button></form><divclass="footer"><p>版权所有 ©九转苍翎</p></div></div><!-- 引入jQuery依赖 --><scriptsrc="js/jquery.min.js"></script><script>//刷新验证码$("#verificationCodeImg").click(function(){//new Date().getTime()).fadeIn()防止前端缓存$(this).hide().attr('src','/captcha/get?dt='+newDate().getTime()).fadeIn();});//登录functionlogin(){ $.ajax({url:'/user/login',type:"post",data:{userName:$('#username').val(),password:$('#password').val(),captcha:$('#inputCaptcha').val(),},success:function(result){ console.log(result);if(result.code ==="SUCCESS"){alert(result.data)}else{alert(result.error)}},})}</script></body></html>

3.在UserController类新增captcha形参接收来自CaptchaController类的请求,并传递给UserService

在这里插入图片描述
importjakarta.servlet.http.HttpSession;importorg.example.springlogin.controller.CaptchaController;importorg.example.springlogin.mapper.UserMapper;importorg.example.springlogin.model.UserInfo;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.stereotype.Service;@ServicepublicclassUserService{privatefinalUserMapper userMapper;@AutowiredpublicUserService(UserMapper userMapper){this.userMapper = userMapper;}publicStringgetUserInfoByUserName(String userName,String password,String captcha,HttpSession session){UserInfo userInfo = userMapper.getUserInfoByUserName(userName);if(userInfo ==null){return"用户不存在";}if(!password.equals(userInfo.getPassword())){return"密码错误";}long saveTime =(long)session.getAttribute("CAPTCHA_SESSION_DATE");if(System.currentTimeMillis()- saveTime >CaptchaController.delay){return"验证码超时";}if(!captcha.equalsIgnoreCase((String) session.getAttribute("CAPTCHA_SESSION_CODE"))){return"验证码错误";}return"登录成功";}}
实现效果:

5.MD5加密

MD5(Message-Digest Algorithm 5)是一种广泛使用的哈希函数,可将任意长度数据生成固定长度(128位,16字节)的哈希值,通常表示为32位十六进制字符串,常用于校验数据完整性或存储密码。但因其安全性不足,通常结合盐值(Salt)配合使用不可逆性:无法通过哈希值反推原始数据唯一性:理论上不同输入产生相同哈希值的概率极低(哈希碰撞)固定长度:无论输入数据大小,输出均为32位十六进制字符串

1.创建SecurityUtil类用于生成和验证密文

importorg.springframework.util.DigestUtils;importorg.springframework.util.StringUtils;importjava.util.UUID;publicclassSecurityUtil{//加密publicstaticStringencrypt(String inputPassword){//生成随机盐值String salt =UUID.randomUUID().toString().replaceAll("-","");//(密码+盐值)进行加密String finalPassword =DigestUtils.md5DigestAsHex((inputPassword + salt).getBytes());return salt + finalPassword;}//验证publicstaticbooleanverify(String inputPassword,String sqlPassword){if(!StringUtils.hasLength(inputPassword)){returnfalse;}if(sqlPassword ==null|| sqlPassword.length()!=64){returnfalse;}//取出盐值String salt = sqlPassword.substring(0,32);//(输入密码 + 盐值)重新生成 加密密码String finalPassword =DigestUtils.md5DigestAsHex((inputPassword + salt).getBytes());//判断数据库中储存的密码与输入密码是否一致return(salt + finalPassword).equals(sqlPassword);}publicstaticvoidmain(String[] args){System.out.println(SecurityUtil.encrypt("123456"));}}

2.将数据库中的密码替换为加密后的值
3.修改验证密码的逻辑(UserService类)

if(!SecurityUtil.verify(password,userInfo.getPassword())){return"密码错误";}

6.拦截器

Spring拦截器(Interceptor)是一种基于AOP的机制,用于在请求处理的不同阶段插入自定义逻辑。常用于权限校验、日志记录、参数预处理等场景

1.创建拦截器类并实现HandlerInterceptor接口,该接口提供了三种方法:

  • preHandle:在Controller方法执行前调用
  • postHandle:Controller方法执行后、视图渲染前调用
  • afterCompletion:请求完成、视图渲染完毕后调用
importjakarta.servlet.http.HttpServletRequest;importjakarta.servlet.http.HttpServletResponse;importlombok.extern.slf4j.Slf4j;importorg.springframework.stereotype.Component;importorg.springframework.web.servlet.HandlerInterceptor;importorg.springframework.web.servlet.ModelAndView;@Slf4j@ComponentpublicclassInterceptorimplementsHandlerInterceptor{@OverridepublicbooleanpreHandle(HttpServletRequest request,HttpServletResponse response,Object handler){//1.获取tokenString cookie = request.getHeader("cookie");if(cookie ==null){ response.setStatus(401);returnfalse;} log.info("接收到cookie:{}",cookie);//2.校验tokenreturntrue;}@OverridepublicvoidpostHandle(HttpServletRequest request,HttpServletResponse response,Object handler,ModelAndView modelAndView){ log.info("postHandle");}@OverridepublicvoidafterCompletion(HttpServletRequest request,HttpServletResponse response,Object handler,Exception ex){ log.info("afterCompletion");}}

2.注册拦截器

importorg.example.springlogin.intercepter.Interceptor;importorg.springframework.beans.factory.annotation.Autowired;importorg.springframework.context.annotation.Configuration;importorg.springframework.web.servlet.config.annotation.InterceptorRegistry;importorg.springframework.web.servlet.config.annotation.WebMvcConfigurer;importjava.util.Arrays;importjava.util.List;@ConfigurationpublicclassConfigimplementsWebMvcConfigurer{privatefinalInterceptorInterceptor;@AutowiredpublicConfig(Interceptor interceptor){Interceptor= interceptor;}//排除不需要拦截的路径privatestaticfinalList<String> excludes =Arrays.asList("/**/login.html","/user/login","/captcha/get");@OverridepublicvoidaddInterceptors(InterceptorRegistry registry){//注册拦截器 registry.addInterceptor(Interceptor)//拦截所有路径.addPathPatterns("/**").excludePathPatterns(excludes);}}

3.创建home.html文件,并且在登录成功后跳转到该页面(在login.html中添加location.href="/home.html")

<!DOCTYPEhtml><htmllang="en"><head><metacharset="UTF-8"><title>home</title></head><body><h1>Hello World</h1></body></html>
实现效果:

未登录直接访问home.html页面时

在这里插入图片描述

成功登陆时

在这里插入图片描述
在这里插入图片描述

Read more

21K 行代码实现一个生产级 AI Agent 框架 — CountBot 架构设计与技术解析 | 今日正式开源

21K 行代码实现一个生产级 AI Agent 框架 — CountBot 架构设计与技术解析 | 今日正式开源

CountBot 是一个轻量级、可扩展的 AI Agent 框架,专为中文用户和国内大模型优化。今天(2026.2.21)正式开源。 GitHub:https://github.com/countbot-ai/CountBot 桌面版下载:https://github.com/countbot-ai/CountBot/releases(支持 Windows / macOS / Linux) 一、项目背景 做 AI Agent 的框架不少,但真正适合个人用户的不多。大多数框架面向企业场景,代码量动辄几十万行,配置复杂,对中文用户和国产大模型的支持也不够友好。 CountBot 的目标很明确:用最少的代码,做一个功能完整、开箱即用、中文优先的个人 AI Agent。 最终成果:约

By Ne0inhk

OpenClaw 配置本地 Ollama 模型完整指南:零成本打造全离线个人 AI 助理

OpenClaw 配置本地 Ollama 模型完整指南:零成本打造全离线个人 AI 助理(2026 最新版·含 Auth 配置) 大家好,我是你的 AI 技术博主。今天我们来聊一个 2026 年最火的本地 AI 助理项目——OpenClaw。它能帮你清理收件箱、发邮件、管理日历、处理文件、集成 Telegram/WhatsApp,甚至执行复杂任务,而且完全跑在你自己的电脑上。 配合 Ollama 运行本地模型(如 Qwen3、Qwen2.5、GLM-4.7、Llama3.3 等),你就可以实现真正零费用、零网络依赖、全隐私保护的智能体体验。官方从 Ollama 0.17

By Ne0inhk
Claude Code vs Cursor:谁才是 2025 年最强 AI 编程助手?

Claude Code vs Cursor:谁才是 2025 年最强 AI 编程助手?

在“能否独立完成跨文件、跨终端复杂任务”这一核心挑战上,Claude Code 已领先半个身位;而在“速度、IDE 手感与成本”这些每天都要面对的细节里,Cursor 仍手握杀手锏。最强与最佳并非同义,合适的场景才是决策关键。 0 | 为什么写这篇文章? Copilot 带火了补全;Cursor/Windsurf 把 RAG 和小型 Agent 带进 IDE;直到 Claude Code 出现,我第一次看到「AI 自己敲命令、装依赖、跑测试、提交 PR」的完整闭环。它真的是当前最强吗?对比日常开发者最常用的 Cursor,我用了三周、跑了多轮基准,也翻遍公开数据,写下这篇实测报告。 1 | 两位选手概览 Claude

By Ne0inhk
Flutter 三方库 http_core_client 的鸿蒙化适配指南 - 打造极简、健壮的 OpenHarmony 网络请求核心组件

Flutter 三方库 http_core_client 的鸿蒙化适配指南 - 打造极简、健壮的 OpenHarmony 网络请求核心组件

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 http_core_client 的鸿蒙化适配指南 - 打造极简、健壮的 OpenHarmony 网络请求核心组件 在 Flutter 应用开发中,网络请求层(Networking Layer)是应用的生命线。虽然 dio 功能强大,但对于追求轻量化、高性能的鸿蒙应用来说,一个精简且核心功能完备的客户端库往往更具优势。http_core_client 为开发者提供了一套基于 BaseClient 封装的极简模型。在 OpenHarmony(鸿蒙)环境下,如何结合其底层网络栈、处理系统的代理配置以及优化连接复用,是构建高标准鸿蒙应用的必修课。本文将带大家深入探讨其适配要点。 前言 随着鸿蒙系统(HarmonyOS)进入原生应用开发的新阶段,网络栈的稳定性与安全性(如 TLS

By Ne0inhk