Spring Boot登录注册功能实现与安全实践 📅 发布时间:2026/9/10 22:41:21 👁 浏览次数: 1. 为什么需要自己实现登录注册功能在开始动手之前我们先要搞清楚一个基础问题为什么在Spring Boot项目中需要自己实现登录注册功能现在不是有很多现成的解决方案吗比如Spring Security、OAuth2、第三方登录等。这个问题困扰了我很久直到在实际项目中踩过几次坑才真正明白。首先现成的安全框架确实强大但它们的复杂度往往超出了简单项目的需求。我曾经在一个小型内部系统中直接上Spring Security结果花了三天时间才搞清楚如何自定义登录页面。其次自己实现可以让你完全掌控用户数据的处理流程。有次我们使用第三方登录结果对方API突然变更导致系统瘫痪这个教训让我记忆犹新。最关键的还是学习价值。登录注册看似简单实则包含了密码加密、会话管理、表单验证、异常处理等多个核心知识点。我建议每个Java开发者都应该至少完整实现一次这个流程这对理解Web开发本质非常有帮助。2. 项目基础环境搭建2.1 初始化Spring Boot项目我习惯使用Spring Initializrstart.spring.io来初始化项目以下是本次演示选择的依赖Spring Web用于构建Web应用Thymeleaf模板引擎比JSP更现代MyBatis持久层框架MySQL Driver数据库连接Lombok减少样板代码注意如果你使用Spring Boot 3.x确保Java版本≥17。我在第一次尝试时用了Java 8结果各种不兼容错误。创建好项目后先调整application.properties为application.yml个人觉得更清晰并配置基础数据库连接spring: datasource: url: jdbc:mysql://localhost:3306/user_auth?useSSLfalse username: root password: yourpassword driver-class-name: com.mysql.cj.jdbc.Driver thymeleaf: cache: false # 开发时关闭缓存2.2 数据库设计用户表设计看似简单但有几个关键点需要注意。这是我优化过多次的版本CREATE TABLE sys_user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL COMMENT 登录账号, password varchar(100) NOT NULL COMMENT 加密后的密码, salt varchar(20) NOT NULL COMMENT 加密盐值, email varchar(100) DEFAULT NULL COMMENT 邮箱, phone varchar(20) DEFAULT NULL COMMENT 手机号, avatar varchar(255) DEFAULT NULL COMMENT 头像URL, status tinyint(1) DEFAULT 1 COMMENT 状态0-禁用 1-正常, last_login_time datetime DEFAULT NULL COMMENT 最后登录时间, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY idx_username (username), KEY idx_email (email), KEY idx_phone (phone) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表;关键设计考虑密码字段要足够长建议100字符因为加密后的字符串可能很长必须包含salt字段用于增强密码安全性建立合适的索引提高查询效率记录最后登录时间用于后续分析3. 核心功能实现3.1 密码加密存储绝对不能明文存储密码我在早期项目中犯过这个错误后来被安全审计时差点造成严重事故。现在推荐使用BCryptPasswordEncoder它是Spring Security默认的密码编码器。首先在配置类中创建BeanConfiguration public class SecurityConfig { Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } }然后在用户注册时加密密码Service RequiredArgsConstructor public class UserServiceImpl implements UserService { private final PasswordEncoder passwordEncoder; Override public void register(UserRegisterDTO dto) { User user new User(); user.setUsername(dto.getUsername()); // 加密密码 user.setPassword(passwordEncoder.encode(dto.getPassword())); // 其他字段设置... userMapper.insert(user); } }BCrypt的优点在于自动加盐不需要我们自己管理每次加密结果都不同但能正确验证内置自适应成本因子可以随硬件性能提升而增强安全性3.2 注册功能实现注册流程看似简单但有很多细节需要注意。完整的注册流程应该包括表单验证前端后端双重验证用户名唯一性检查密码复杂度校验数据持久化注册成功后的处理如发送欢迎邮件Controller层实现Controller RequiredArgsConstructor public class AuthController { private final UserService userService; GetMapping(/register) public String registerPage() { return auth/register; } PostMapping(/register) public String register(Valid UserRegisterDTO dto, BindingResult result) { // 验证表单数据 if (result.hasErrors()) { return auth/register; } // 检查用户名是否已存在 if (userService.existsByUsername(dto.getUsername())) { result.rejectValue(username, error.username, 该用户名已被注册); return auth/register; } try { userService.register(dto); return redirect:/login?registersuccess; } catch (Exception e) { result.reject(register.failed, 注册失败请稍后重试); return auth/register; } } }DTO类示例使用Jakarta ValidationData public class UserRegisterDTO { NotBlank(message 用户名不能为空) Size(min 4, max 20, message 用户名长度4-20个字符) private String username; NotBlank(message 密码不能为空) Pattern(regexp ^(?.*[a-z])(?.*[A-Z])(?.*\\d)[a-zA-Z\\d]{8,}$, message 密码至少8位包含大小写字母和数字) private String password; NotBlank(message 确认密码不能为空) private String confirmPassword; Email(message 邮箱格式不正确) private String email; // 自定义验证方法 public boolean isPasswordMatch() { return password ! null password.equals(confirmPassword); } }3.3 登录功能实现登录功能的核心是会话管理。在Spring Boot中我们可以使用HttpSessionPostMapping(/login) public String login(Valid UserLoginDTO dto, BindingResult result, HttpSession session, RedirectAttributes attributes) { if (result.hasErrors()) { return auth/login; } User user userService.findByUsername(dto.getUsername()); if (user null || !passwordEncoder.matches(dto.getPassword(), user.getPassword())) { result.reject(login.failed, 用户名或密码错误); return auth/login; } if (user.getStatus() 0) { result.reject(account.disabled, 账号已被禁用); return auth/login; } // 登录成功设置session session.setAttribute(currentUser, user); userService.updateLoginTime(user.getId()); return redirect:/dashboard; }登录成功后我们可以通过拦截器来验证登录状态Component public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session request.getSession(); if (session.getAttribute(currentUser) null) { response.sendRedirect(/login); return false; } return true; } }然后在配置类中注册拦截器Configuration RequiredArgsConstructor public class WebConfig implements WebMvcConfigurer { private final AuthInterceptor authInterceptor; Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns(/**) .excludePathPatterns(/, /login, /register, /css/**, /js/**, /error); } }4. 安全增强措施4.1 防止暴力破解简单的登录功能很容易被暴力破解攻击。我曾在生产环境遇到过每分钟数千次的登录尝试。解决方案添加验证码限制登录尝试次数记录登录日志验证码实现示例使用Google的KaptchaConfiguration public class KaptchaConfig { Bean public Producer kaptchaProducer() { Properties properties new Properties(); properties.setProperty(kaptcha.image.width, 150); properties.setProperty(kaptcha.image.height, 50); properties.setProperty(kaptcha.textproducer.char.string, ABCDEFGHJKLMNPQRSTUVWXYZ23456789); properties.setProperty(kaptcha.textproducer.char.length, 4); Config config new Config(properties); DefaultKaptcha kaptcha new DefaultKaptcha(); kaptcha.setConfig(config); return kaptcha; } }然后在登录页面显示验证码img th:src{/captcha} onclickthis.src/captcha?dnew Date().getTime()/ input typetext namecaptcha required placeholder请输入验证码/Controller中添加验证码生成接口GetMapping(/captcha) public void getCaptcha(HttpServletResponse response, HttpSession session) throws IOException { String text kaptchaProducer.createText(); session.setAttribute(captcha, text); BufferedImage image kaptchaProducer.createImage(text); response.setContentType(image/jpeg); try (OutputStream out response.getOutputStream()) { ImageIO.write(image, jpg, out); } }4.2 密码重置功能密码重置是账户安全的重要环节常见实现方式有通过邮箱发送重置链接通过手机短信验证安全问题验证这里展示邮箱重置的实现Service RequiredArgsConstructor public class PasswordService { private final UserMapper userMapper; private final JavaMailSender mailSender; private final PasswordEncoder passwordEncoder; public void sendResetEmail(String email) { User user userMapper.findByEmail(email); if (user null) { throw new BusinessException(该邮箱未注册); } String token UUID.randomUUID().toString(); user.setResetToken(token); user.setTokenExpiry(LocalDateTime.now().plusHours(2)); userMapper.update(user); SimpleMailMessage message new SimpleMailMessage(); message.setTo(email); message.setSubject(密码重置请求); message.setText(请点击以下链接重置密码\n http://yourdomain.com/reset-password?token token); mailSender.send(message); } public void resetPassword(String token, String newPassword) { User user userMapper.findByResetToken(token); if (user null || user.getTokenExpiry().isBefore(LocalDateTime.now())) { throw new BusinessException(链接无效或已过期); } user.setPassword(passwordEncoder.encode(newPassword)); user.setResetToken(null); user.setTokenExpiry(null); userMapper.update(user); } }5. 前端页面实现5.1 Thymeleaf模板集成使用Thymeleaf可以方便地集成后端数据到HTML中。登录页面示例!DOCTYPE html html xmlns:thhttp://www.thymeleaf.org head meta charsetUTF-8 title用户登录/title link relstylesheet th:href{/css/auth.css} /head body div classauth-container h2用户登录/h2 form th:action{/login} methodpost div th:if${param.error} classalert alert-error 用户名或密码错误 /div div th:if${param.logout} classalert alert-success 您已成功登出 /div div classform-group label forusername用户名/label input typetext idusername nameusername required th:class${#fields.hasErrors(username)} ? error-field span th:if${#fields.hasErrors(username)} th:errors*{username} classerror-message/span /div div classform-group label forpassword密码/label input typepassword idpassword namepassword required /div div classform-group label forcaptcha验证码/label div classcaptcha-container input typetext idcaptcha namecaptcha required img th:src{/captcha} onclickthis.src/captcha?dnew Date().getTime()/ /div /div button typesubmit classbtn-login登录/button /form div classauth-footer 还没有账号a th:href{/register}立即注册/a /div /div /body /html5.2 表单验证增强前端验证可以减轻服务器压力提升用户体验。使用HTML5原生验证结合JavaScriptdocument.querySelector(form).addEventListener(submit, function(e) { const password document.getElementById(password).value; const confirm document.getElementById(confirmPassword).value; if (password ! confirm) { e.preventDefault(); alert(两次输入的密码不一致); return false; } // 其他自定义验证... });对于更复杂的验证推荐使用jQuery Validation等库$(#registerForm).validate({ rules: { username: { required: true, minlength: 4, remote: /check-username }, password: { required: true, minlength: 8, strongPassword: true }, email: { required: true, email: true, remote: /check-email } }, messages: { username: { remote: 该用户名已被注册 } } }); // 自定义密码强度验证 $.validator.addMethod(strongPassword, function(value) { return /[A-Z]/.test(value) /[a-z]/.test(value) /[0-9]/.test(value); }, 密码必须包含大小写字母和数字);6. 测试与部署6.1 单元测试编写全面的单元测试确保功能正确性SpringBootTest class UserServiceTest { Autowired private UserService userService; Test void testRegister() { UserRegisterDTO dto new UserRegisterDTO(); dto.setUsername(testuser); dto.setPassword(Test1234); dto.setConfirmPassword(Test1234); dto.setEmail(testexample.com); userService.register(dto); User user userService.findByUsername(testuser); assertNotNull(user); assertEquals(testexample.com, user.getEmail()); } Test void testLogin() { UserLoginDTO dto new UserLoginDTO(); dto.setUsername(testuser); dto.setPassword(Test1234); User user userService.login(dto); assertNotNull(user); } }6.2 集成测试使用TestRestTemplate进行端到端测试SpringBootTest(webEnvironment WebEnvironment.RANDOM_PORT) class AuthIntegrationTest { LocalServerPort private int port; Autowired private TestRestTemplate restTemplate; Test void testRegisterAndLogin() { // 注册 MultiValueMapString, String form new LinkedMultiValueMap(); form.add(username, integrationuser); form.add(password, IntTest123); form.add(confirmPassword, IntTest123); form.add(email, integrationtest.com); ResponseEntityString response restTemplate.postForEntity( http://localhost: port /register, form, String.class); assertEquals(302, response.getStatusCodeValue()); // 登录 form.clear(); form.add(username, integrationuser); form.add(password, IntTest123); ResponseEntityString loginResponse restTemplate.postForEntity( http://localhost: port /login, form, String.class); assertEquals(302, loginResponse.getStatusCodeValue()); assertTrue(loginResponse.getHeaders().getLocation().getPath().endsWith(/dashboard)); } }6.3 生产环境部署建议在实际部署时有几个关键点需要注意HTTPS配置登录页面必须使用HTTPSserver: ssl: enabled: true key-store: classpath:keystore.p12 key-store-password: yourpassword key-store-type: PKCS12 port: 443会话持久化默认的HttpSession在服务器重启后会丢失可以使用Redis存储spring: session: store-type: redis redis: host: localhost port: 6379密码加密强度根据服务器性能调整BCrypt强度Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(12); // 默认是10 }防止CSRF攻击Spring Security默认启用如果自己实现可以添加CSRF tokeninput typehidden th:name${_csrf.parameterName} th:value${_csrf.token}/7. 常见问题与解决方案7.1 登录后跳转问题常见问题登录成功后没有跳转到原始请求页面。解决方案是在登录前记录原始URLGetMapping(/login) public String loginPage(RequestParam(required false) String redirect, HttpSession session) { if (redirect ! null) { session.setAttribute(redirectUrl, redirect); } return auth/login; } PostMapping(/login) public String login(..., HttpSession session) { // ...登录逻辑 String redirectUrl (String) session.getAttribute(redirectUrl); if (redirectUrl ! null !redirectUrl.isEmpty()) { session.removeAttribute(redirectUrl); return redirect: redirectUrl; } return redirect:/dashboard; }7.2 记住我功能实现记住我功能需要持久化登录状态创建记住我token表CREATE TABLE persistent_logins ( series varchar(64) PRIMARY KEY, username varchar(64) NOT NULL, token varchar(64) NOT NULL, last_used timestamp NOT NULL );配置记住我服务Bean public PersistentTokenRepository persistentTokenRepository(DataSource dataSource) { JdbcTokenRepositoryImpl repo new JdbcTokenRepositoryImpl(); repo.setDataSource(dataSource); return repo; }在登录表单中添加复选框input typecheckbox nameremember-me/ 记住我7.3 多端登录限制有时需要限制同一账号在多设备同时登录Service public class LoginLimitService { private final ConcurrentHashMapString, String onlineUsers new ConcurrentHashMap(); public void onLogin(String username, String sessionId) { String existingSession onlineUsers.putIfAbsent(username, sessionId); if (existingSession ! null) { // 踢出前一个会话 sessionRegistry.getSessionInformation(existingSession).expireNow(); onlineUsers.put(username, sessionId); } } public void onLogout(String username) { onlineUsers.remove(username); } }然后在登录成功和注销时调用相应方法。8. 性能优化建议8.1 缓存用户数据频繁查询数据库会影响性能可以引入缓存Service CacheConfig(cacheNames users) public class UserServiceImpl implements UserService { Override Cacheable(key #username) public User findByUsername(String username) { return userMapper.findByUsername(username); } Override CacheEvict(key #user.username) public void update(User user) { userMapper.update(user); } }8.2 异步日志记录登录日志等非核心功能可以异步处理Async public void recordLoginLog(Long userId, String ip) { LoginLog log new LoginLog(); log.setUserId(userId); log.setIp(ip); log.setLoginTime(LocalDateTime.now()); loginLogMapper.insert(log); }确保在配置类上添加EnableAsync注解。8.3 数据库查询优化用户表查询优化建议为常用查询字段建立索引避免SELECT *只查询需要的字段对大表考虑分库分表Select(SELECT id, username, email, avatar FROM sys_user WHERE username #{username}) User findByUsername(String username);9. 扩展功能思路9.1 第三方登录集成集成微信、GitHub等第三方登录可以提升用户体验添加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-oauth2-client/artifactId /dependency配置OAuth2客户端spring: security: oauth2: client: registration: github: client-id: your-client-id client-secret: your-client-secret scope: user:email创建登录链接a href/oauth2/authorization/github使用GitHub登录/a9.2 多因素认证(MFA)提升安全性可以增加短信/邮箱验证码验证public void sendVerificationCode(String phone) { String code generateRandomCode(); cacheService.set(mfa: phone, code, 5, TimeUnit.MINUTES); smsService.send(phone, 您的验证码是 code); } public boolean verifyCode(String phone, String code) { String cachedCode cacheService.get(mfa: phone); return code ! null code.equals(cachedCode); }9.3 JWT替代Session对于分布式系统可以考虑使用JWTpublic String generateToken(User user) { return Jwts.builder() .setSubject(user.getUsername()) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 3600000)) .signWith(SignatureAlgorithm.HS512, your-secret-key) .compact(); } public boolean validateToken(String token) { try { Jwts.parser().setSigningKey(your-secret-key).parseClaimsJws(token); return true; } catch (Exception e) { return false; } }10. 项目结构建议经过多次迭代我发现这样的项目结构比较合理src/main/java └── com └── example └── demo ├── config # 配置类 ├── controller # 控制器 │ └── auth # 认证相关控制器 ├── dto # 数据传输对象 │ └── auth # 认证相关DTO ├── entity # 实体类 ├── exception # 自定义异常 ├── interceptor # 拦截器 ├── mapper # MyBatis映射接口 ├── service # 服务层 │ └── impl # 服务实现 ├── util # 工具类 └── DemoApplication.java src/main/resources ├── static │ ├── css # 样式文件 │ └── js # JavaScript文件 └── templates └── auth # 认证相关模板这种结构的好处是按功能模块组织而非按技术层次相关功能集中存放便于维护适合中小型项目不会过度设计11. 实际开发中的经验教训在多个项目中实现登录注册功能后我总结了以下经验密码加密时机不要在Controller或DTO层加密密码这会导致密码在日志中泄露。应该在Service层处理。错误信息提示不要透露太多信息给客户端。比如用户名不存在和密码错误应该返回相同的提示防止恶意用户枚举有效账号。会话超时设置生产环境应该设置合理的会话超时时间server: servlet: session: timeout: 30m # 30分钟并发控制在高并发场景下注册和登录接口需要考虑并发问题比如使用分布式锁防止重复注册。日志记录关键操作如登录失败、密码修改等需要记录详细日志但要注意不要记录敏感信息。国际化支持如果面向多语言用户提前设计好i18n方案GetMapping(/login) public String loginPage(Model model, Locale locale) { model.addAttribute(title, messageSource.getMessage(login.title, null, locale)); return auth/login; }前端验证不可靠永远不要依赖前端验证作为唯一的安全措施后端必须进行完整的验证。定期更换加密算法虽然BCrypt目前安全但要保持关注准备迁移方案。12. 监控与报警完善的监控可以及时发现安全问题登录失败监控短时间内多次失败可能是暴力破解Aspect Component public class LoginMonitorAspect { private final MapString, Integer failCountMap new ConcurrentHashMap(); AfterThrowing(pointcut execution(* com.example.demo.service.AuthService.login(..)), throwing ex) public void afterLoginFailed(JoinPoint jp, Exception ex) { String username (String) jp.getArgs()[0]; failCountMap.merge(username, 1, Integer::sum); if (failCountMap.get(username) 5) { alertService.sendAlert(多次登录失败: username); // 可以临时锁定账号 } } }异常登录检测异地登录、非常用设备等public void checkLoginLocation(User user, String ip) { String lastLocation locationService.getLocation(user.getLastLoginIp()); String currentLocation locationService.getLocation(ip); if (!currentLocation.equals(lastLocation)) { securityService.sendWarningEmail(user.getEmail(), 异常登录检测, 您的账号在 currentLocation 登录); } }账号活动监控长期未登录的账号可以自动禁用Scheduled(cron 0 0 1 * * ?) // 每天凌晨1点执行 public void checkInactiveAccounts() { LocalDate threshold LocalDate.now().minusMonths(6); ListUser inactiveUsers userMapper.findInactiveUsers(threshold); inactiveUsers.forEach(user - { user.setStatus(0); // 禁用 userMapper.update(user); mailService.send(user.getEmail(), 账号停用通知, 您的账号因长期未使用已被停用); }); }13. 升级与迁移策略随着业务发展可能需要对认证系统进行升级密码加密算法升级当需要更换更强的算法时public void upgradePasswordEncoding() { ListUser users userMapper.findAll(); for (User user : users) { if (user.getPassword().startsWith({bcrypt})) { continue; // 已经是新算法 } String newPassword newPasswordEncoder.encode(user.getPassword()); user.setPassword(newPassword); userMapper.update(user); } }用户表结构调整添加新字段时考虑兼容性Transactional public void addNewFieldWithDefault() { // 1. 修改表结构 jdbcTemplate.execute(ALTER TABLE sys_user ADD COLUMN new_field VARCHAR(50) DEFAULT default_value); // 2. 更新现有数据 jdbcTemplate.update(UPDATE sys_user SET new_field ? WHERE new_field IS NULL, default_value); }多系统统一认证当需要与其他系统共享认证时可以考虑实现SSOGetMapping(/sso/login) public String ssoLogin(RequestParam String service, HttpSession session) { if (session.getAttribute(currentUser) ! null) { String token generateSSOToken(); return redirect: service ?token token; } return redirect:/login?service service; }14. 安全审计要点定期进行安全审计非常重要主要检查密码策略是否强制复杂密码是否有密码过期策略是否防止密码重复使用会话管理会话ID是否足够随机是否实现会话固定保护注销后会话是否真正失效敏感数据保护密码是否加密存储日志中是否可能记录敏感信息错误信息是否泄露系统细节其他漏洞是否存在SQL注入风险是否对上传文件进行安全检查是否实现CSRF保护可以使用OWASP ZAP等工具进行自动化扫描结合人工代码审查。15. 性能测试建议在正式上线前应该进行压力测试重点关注登录接口性能模拟1000用户并发登录测试不同密码加密强度的性能影响监控数据库连接池使用情况会话存储性能测试本地会话与Redis会话的差异模拟大量活跃会话时的内存使用测试会话失效对性能的影响缓存效果验证测试启用缓存前后的性能对比模拟缓存失效时的降级能力验证缓存一致性可以使用JMeter或Gatling等工具进行测试重点关注TPS和响应时间。16. 容器化部署考虑如果使用Docker部署需要注意会话持久化必须使用外部存储如Redis不能依赖容器内存FROM openjdk:17-jdk COPY target/demo.jar app.jar ENTRYPOINT [java,-jar,/app.jar]健康检查添加健康检查端点GetMapping(/health) public ResponseEntityString health() { return databaseHealthCheck() ? ResponseEntity.ok(OK) : ResponseEntity.status(503).body(Service Unavailable); }配置管理敏感信息如数据库密码应使用环境变量或Secretspring: datasource: url: ${DB_URL} username: ${DB_USER} password: ${DB_PASSWORD}资源限制合理设置内存限制防止OOM# docker-compose.yml services: app: mem_limit: 512m environment: - JAVA_OPTS-Xmx256m -Xms128m17. 微服务架构下的调整在微服务架构中认证系统通常作为独立服务认证服务设计RestController RequestMapping(/auth) public class AuthController { PostMapping(/login) public ResponseEntityAuthResponse login(RequestBody LoginRequest request) { // 验证逻辑 String token tokenProvider.generateToken(user); return ResponseEntity.ok(new AuthResponse(token)); } GetMapping(/validate) public ResponseEntityUserInfo validateToken(RequestHeader(Authorization) String token) { if (tokenProvider.validateToken(token)) { return ResponseEntity.ok(tokenProvider.getUserInfo(token)); } return ResponseEntity.status(401).build(); } }网关统一验证Component public class AuthFilter implements GlobalFilter { Override public MonoVoid filter(ServerWebExchange exchange, GatewayFilterChain chain) { String token exchange.getRequest().getHeaders().getFirst(Authorization); if (!authService.validateToken(token)) { exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED); return exchange.getResponse().setComplete(); } return chain.filter(exchange); } }服务间认证使用JWT或OAuth2 Client Credentialsspring: security: oauth2: client: registration: internal-service: client-id: internal-client client-secret: secret authorization-grant-type: client_credentials18. 前端分离架构实现对于前后端分离项目调整认证流程跨域支持Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://frontend-domain.com) .allowedMethods(*) .allowedHeaders(*) .allowCredentials(true); } }JWT认证流程// 前端登录逻辑 async function login(username, password) { const response await fetch(/auth/login, { method: POST, headers: {Content-Type: application/json}, body: JSON.stringify({username, password}) }); if (response.ok) { const {token} await response.json(); localStorage.setItem(jwt, token); // 后续请求携带token } } // 请求拦截器 axios.interceptors.request.use(config { const token localStorage.getItem(jwt); if (token) { config.headers.Authorization Bearer ${token}; } return config; });Token刷新机制PostMapping(/refresh) public ResponseEntityAuthResponse