公司动态
SpringBoot实战:从零构建一个带Session验证的登录系统(附完整前后端源码)
1. 项目概述与核心目标这个实战项目将带你从零构建一个基于SpringBoot的登录系统重点在于理解Session验证机制在前后端分离模式下的应用。想象一下你正在开发一个需要用户认证的小型应用比如个人博客后台而登录功能是所有交互的起点。通过这个麻雀虽小五脏俱全的项目你将掌握前后端数据交互的基本流程Session在用户身份验证中的核心作用如何安全地存储和验证用户凭证完整的项目结构设计与代码组织我会使用最简洁的技术栈SpringBoot 原生HTML/JQuery来降低学习门槛但实现的是企业级登录验证的核心逻辑。所有代码都经过实际验证你可以在文末获取完整源码。2. 环境准备与项目创建2.1 开发工具要求确保你的开发环境包含JDK 1.8或更高版本IntelliJ IDEA推荐或EclipseMaven 3.6现代浏览器Chrome/Firefox2.2 创建SpringBoot项目通过Spring Initializr快速初始化项目访问 start.spring.io选择Project: MavenLanguage: JavaSpring Boot: 2.7.x添加依赖Spring WebThymeleaf可选用于模板渲染或者直接用IDEA的Spring Initializr向导创建。初始化后的pom.xml应该包含dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-test/artifactId scopetest/scope /dependency /dependencies2.3 项目结构规划创建以下目录结构部分目录SpringBoot会自动生成src/ ├── main/ │ ├── java/ │ │ └── com/example/logindemo/ │ │ ├── controller/ # 控制器 │ │ ├── config/ # 配置类 │ │ └── LoginDemoApplication.java │ └── resources/ │ ├── static/ # 静态资源 │ │ ├── css/ │ │ ├── js/ │ │ └── images/ │ ├── templates/ # 模板文件 │ └── application.properties3. 前端页面开发3.1 登录页面(login.html)在resources/static下创建登录页面!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 title用户登录/title script srchttps://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js/script style .login-form { width: 300px; margin: 100px auto; padding: 20px; border: 1px solid #ddd; border-radius: 5px; } .form-group { margin-bottom: 15px; } .form-group label { display: block; margin-bottom: 5px; } .form-group input { width: 100%; padding: 8px; box-sizing: border-box; } .submit-btn { width: 100%; padding: 10px; background: #1890ff; color: white; border: none; border-radius: 4px; cursor: pointer; } /style /head body div classlogin-form h2用户登录/h2 div classform-group label用户名/label input typetext iduserName placeholder请输入用户名 /div div classform-group label密码/label input typepassword idpassword placeholder请输入密码 /div button classsubmit-btn onclicklogin()登录/button /div script function login() { const username $(#userName).val(); const password $(#password).val(); if(!username || !password) { alert(用户名和密码不能为空); return; } $.ajax({ url: /user/login, type: POST, contentType: application/json, data: JSON.stringify({ userName: username, password: password }), success: function(result) { if(result.success) { window.location.href /index.html; } else { alert(登录失败: result.message); } }, error: function(xhr) { alert(请求异常: xhr.statusText); } }); } /script /body /html3.2 主页(index.html)创建简单的主页用于展示登录状态!DOCTYPE html html langzh-CN head meta charsetUTF-8 title系统主页/title script srchttps://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.min.js/script /head body h1欢迎访问系统/h1 div 当前登录用户: span idloginUser/span button onclicklogout() stylemargin-left: 20px;退出登录/button /div script // 页面加载时获取用户信息 $(document).ready(function() { $.get(/user/getUserInfo, function(username) { if(username) { $(#loginUser).text(username); } else { alert(未登录或会话已过期); window.location.href /login.html; } }); }); function logout() { $.post(/user/logout, function() { window.location.href /login.html; }); } /script /body /html4. 后端核心实现4.1 用户控制器(UserController)创建处理登录逻辑的核心控制器package com.example.logindemo.controller; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; RestController RequestMapping(/user) public class UserController { // 模拟用户数据实际项目应从数据库获取 private static final String VALID_USERNAME admin; private static final String VALID_PASSWORD 123456; PostMapping(/login) public Result login(RequestBody LoginRequest request, HttpServletRequest httpRequest) { // 参数校验 if (!StringUtils.hasText(request.getUserName()) || !StringUtils.hasText(request.getPassword())) { return Result.fail(用户名和密码不能为空); } // 验证凭证实际项目应使用加密验证 if (VALID_USERNAME.equals(request.getUserName()) VALID_PASSWORD.equals(request.getPassword())) { // 创建会话 HttpSession session httpRequest.getSession(); session.setAttribute(username, request.getUserName()); session.setMaxInactiveInterval(30 * 60); // 30分钟过期 return Result.success(登录成功); } return Result.fail(用户名或密码错误); } GetMapping(/getUserInfo) public String getUserInfo(HttpSession session) { // 从会话中获取用户名 String username (String) session.getAttribute(username); if (username null) { throw new RuntimeException(未登录或会话已过期); } return username; } PostMapping(/logout) public Result logout(HttpSession session) { // 使会话失效 session.invalidate(); return Result.success(退出成功); } // 内部类登录请求体 public static class LoginRequest { private String userName; private String password; // getters and setters } // 简单结果封装 public static class Result { private boolean success; private String message; // 静态工厂方法 public static Result success(String message) { return new Result(true, message); } public static Result fail(String message) { return new Result(false, message); } // constructor and getters } }4.2 Session配置优化在application.properties中添加Session配置# Session配置 server.servlet.session.timeout1800 # 30分钟(单位秒) server.servlet.session.cookie.nameLOGIN_SESSION server.servlet.session.cookie.http-onlytrue # 防止XSS server.servlet.session.cookie.securefalse # 开发环境可关闭生产环境应开启对于更复杂的场景可以创建WebConfig配置类Configuration public class WebConfig implements WebMvcConfigurer { Bean public ServletWebServerFactory servletContainer() { TomcatServletWebServerFactory factory new TomcatServletWebServerFactory(); factory.addErrorPages(new ErrorPage(HttpStatus.UNAUTHORIZED, /error/401)); return factory; } }5. 安全增强与最佳实践5.1 密码加密存储实际项目中绝对不要明文存储密码使用BCrypt加密添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-security/artifactId /dependency创建密码工具类import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; public class PasswordUtil { private static final BCryptPasswordEncoder encoder new BCryptPasswordEncoder(); public static String encode(String rawPassword) { return encoder.encode(rawPassword); } public static boolean matches(String rawPassword, String encodedPassword) { return encoder.matches(rawPassword, encodedPassword); } }5.2 防御CSRF攻击虽然我们的简单示例没有使用CSRF保护但在生产环境中应该启用Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and() .sessionManagement() .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED); } }5.3 登录失败处理增强登录接口的安全性PostMapping(/login) public Result login(RequestBody LoginRequest request, HttpServletRequest httpRequest) { // 添加登录失败次数限制 HttpSession session httpRequest.getSession(); Integer attemptCount (Integer) session.getAttribute(loginAttempts); if (attemptCount null) { attemptCount 0; } if (attemptCount 3) { return Result.fail(尝试次数过多请稍后再试); } // ...验证逻辑... if (!VALID_USERNAME.equals(request.getUserName()) || !VALID_PASSWORD.equals(request.getPassword())) { session.setAttribute(loginAttempts, attemptCount); return Result.fail(用户名或密码错误); } // 登录成功重置计数器 session.removeAttribute(loginAttempts); // ...其他逻辑... }6. 项目测试与调试6.1 测试登录流程启动应用运行LoginDemoApplication访问http://localhost:8080/login.html测试用例正确凭证admin/123456错误凭证观察提示信息直接访问index.html应重定向到登录页6.2 使用Postman测试API创建以下请求集合登录请求Method: POSTURL:http://localhost:8080/user/loginBody (raw/JSON):{ userName: admin, password: 123456 }检查响应中的Set-Cookie头获取用户信息Method: GETURL:http://localhost:8080/user/getUserInfo添加Cookie: LOGIN_SESSION你的sessionId6.3 常见问题排查Session不持久检查浏览器是否禁用Cookie跨域问题确保前端和后端同源或配置CORS静态资源404确认文件放在resources/static目录响应时间过长检查Session存储配置默认使用内存7. 项目扩展方向这个基础版本可以进一步扩展数据库集成使用Spring Data JPA连接MySQL创建User实体和Repository记住我功能使用持久化Cookie实现长期登录结合Token机制验证码支持集成Google Kaptcha或使用Hutool工具生成简单验证码OAuth2.0集成支持GitHub/微信等第三方登录使用Spring Security OAuth前端框架整合替换原生HTML为Vue/React使用Axios替代JQuery Ajax完整项目源码已托管在GitHub包含所有配置和扩展示例。通过这个项目你不仅学会了Session验证的实现更重要的是理解了Web认证的核心流程。在实际开发中可以根据需求组合不同的安全机制构建更健壮的认证系统。