当前位置: 首页 > news >正文

springboot-security安全插件使用故障解析

springboot-security安全插件使用故障解析!最近开发网站,使用了这个安全插件,但是我发现,用户虽然可以正常登录,但是无论如何也无法设置HttpSession对象绑定loginUser;后来偶然间想到了一件事,会不会是,登录时,插件本身默认调用了

public class CustomUserDetailsService implements UserDetailsService;

因为UserDetailsService的职责就是负责用户登录验证的。我果断打了一个断点,启动debug模式,顺利捕捉到了断点路径。

这个就解决了之前困惑的问题,为什么我辛苦写的登录方法,还有配置的业务逻辑方法(追加登录日志,修改用户登录信息等)丝毫不起作用呢。这个问题原因根源在此啊。


给大家看我自己写的登录方法。

/*** 处理用户登录信息-email* @param username 用户名(手机号或邮箱)* @param password 密码* @param ipAddress 登录IP地址* @param userAgent 用户代理信息* @return* @throws BusinessException*/
@Override
@Transactional
public User login(String username, String password, String ipAddress, String userAgent) throws BusinessException {return handleLoginSuccess(username, ipAddress, userAgent);}它位于我自定义的一个AuthService业务接口实现类里面。

package com.example.feng.service.impl;import com.example.feng.dto.UserRegistrationDTO;
import com.example.feng.entity.LoginLog;
import com.example.feng.entity.User;
import com.example.feng.exception.BusinessException;
import com.example.feng.service.AuthService;
import com.example.feng.service.LoginLogService;
import com.example.feng.service.UserService;
import com.example.feng.service.VerificationCodeService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;import java.time.LocalDateTime;/*** 认证服务实现类* 实现用户注册、登录功能,集成角色管理和登录日志记录*/
@Service
@Transactional
@Slf4j
public class AuthServiceImpl implements AuthService {@Resourceprivate UserService userService;@Resourceprivate LoginLogService loginLogService;@Resourceprivate PasswordEncoder passwordEncoder;@Resourceprivate VerificationCodeService verificationCodeService;/*** 最大允许连续登录失败次数*/private static final int MAX_FAILED_ATTEMPTS = 5;/*** 账号锁定时长(分钟)*/private static final int LOCK_MINUTES = 60;@Overridepublic User registerByEmail(UserRegistrationDTO dto) throws BusinessException {// 1. 验证数据validateRegistrationData(dto, "email");// 2. 验证验证码verificationCodeService.verifyCode(dto.getEmail(), dto.getCode(), "email");// 3. 检查邮箱是否已注册if (userService.existsByEmail(dto.getEmail())) {throw new BusinessException("该邮箱已注册");}// 4. 创建用户User user = createUser(dto);user.setEmail(dto.getEmail());// 5. 保存用户User savedUser = userService.addOneUser(user);log.info("用户邮箱注册成功 - 用户ID: {}, 邮箱: {}", savedUser.getId(), savedUser.getEmail());return savedUser;}/*** 处理用户登录信息-email* @param username 用户名(手机号或邮箱)* @param password 密码* @param ipAddress 登录IP地址* @param userAgent 用户代理信息* @return* @throws BusinessException*/@Override@Transactionalpublic User login(String username, String password, String ipAddress, String userAgent) throws BusinessException {return handleLoginSuccess(username, ipAddress, userAgent);}@Overridepublic void verifyCode(String target, String code, String type) throws BusinessException {if (!verificationCodeService.verifyCode(target, code, type)) {throw new BusinessException("验证码不正确或已过期");}}/*** 创建用户对象*/private User createUser(UserRegistrationDTO dto) {User user = new User();user.setPassword(dto.getPassword());user.setRole("STUDENT"); // 默认角色为学生//INSTITUTIONuser.setStatus(1); // 默认为正常状态user.setLoginCount(0);user.setFailLoginCount(0);return user;}/*** 验证注册数据*/private void validateRegistrationData(UserRegistrationDTO dto, String type) throws BusinessException {System.out.println(dto.getPassword()+":"+dto.getConfirmPassword());// 验证密码一致性
//        if (!dto.getPassword().equals(dto.getConfirmPassword())) {
//            throw new BusinessException("两次输入的密码不一致");
//        }if ("email".equals(type) && !isValidEmail(dto.getEmail())) {throw new BusinessException("请输入正确的邮箱地址");}}/*** 手机号格式验证*/private boolean isValidPhone(String phone) {return StringUtils.hasText(phone) && phone.matches("^1[3-9]\\d{9}$");}/*** 邮箱格式验证*/private boolean isValidEmail(String email) {return StringUtils.hasText(email) && email.matches("^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$");}/*** 检查用户状态*/private void checkUserStatus(User user) throws BusinessException {if (user.getStatus() == 0) {throw new BusinessException("账号尚未激活,请先激活");}if (user.getStatus() == 3) {throw new BusinessException("账号已注销");}if (user.isLocked()) {throw new BusinessException("账号已被锁定," + user.getLockReason() + ",请" + LOCK_MINUTES + "分钟后再试");}}/*** 处理登录成功*/private User handleLoginSuccess(String username, String ipAddress, String userAgent) {User  user = userService.findUserByUsername(username);try{// 1. 更新用户信息//根据用户名(手机号或邮箱)查找用户user.setLastLoginTime(LocalDateTime.now());user.setLastLoginIp(ipAddress);user.setLastLoginDevice(extractDeviceType(userAgent));user.setLoginCount(user.getLoginCount() + 1);user.resetFailLoginCount(); // 重置失败次数System.out.println("here is 处理登录成功操作方法内部。准备更新用户信息。");User updatedUser = userService.update(user);// 2. 记录成功登录日志recordSuccessLoginLog(updatedUser, ipAddress, userAgent);}catch (Exception e){e.printStackTrace();}return user;}/*** 处理登录失败*/private void handleLoginFailure(User user, String ipAddress, String userAgent, String reason) throws BusinessException {// 1. 增加失败次数user.incrementFailLoginCount();// 2. 检查是否需要锁定账号if (user.getFailLoginCount() >= MAX_FAILED_ATTEMPTS) {user.setStatus(2); // 锁定状态user.setLockReason("连续" + MAX_FAILED_ATTEMPTS + "次登录失败");user.setLockTime(LocalDateTime.now().plusMinutes(LOCK_MINUTES));reason = "连续" + MAX_FAILED_ATTEMPTS + "次登录失败,账号已锁定" + LOCK_MINUTES + "分钟";}userService.update(user);// 3. 记录失败登录日志-记录成功登录日志recordFailLoginLog(user.getLoginAccount(), ipAddress, userAgent, reason);log.warn("用户登录失败 - 用户ID: {}, 账号: {}, 原因: {}", user.getId(), user.getLoginAccount(), reason);}/*** 记录成功登录日志*/private void recordSuccessLoginLog(User user, String ipAddress, String userAgent) {try{LoginLog loginLog = new LoginLog();loginLog.setUserId(user.getId());loginLog.setUserName(user.getUsername());loginLog.setLoginIp(ipAddress);loginLog.setLoginTime(LocalDateTime.now());loginLog.setStatus(1);loginLog.setUserAgent(userAgent);loginLog.setDeviceType(extractDeviceType(userAgent));loginLog.setLocation(resolveLocation(ipAddress)); // 实际项目中可集成IP解析服务System.out.println("这里是记录登录成功日志的方法内部");log.warn("用户登录成功了 - 用户: {}", user.getUsername());loginLogService.addOneLoginlog(loginLog);}catch (Exception e){e.printStackTrace();}}/*** 记录失败登录日志*/private void recordFailLoginLog(String account, String ipAddress, String userAgent, String reason) {try{LoginLog loginLog = new LoginLog();loginLog.setUserName(account);loginLog.setLoginIp(ipAddress);loginLog.setLoginTime(LocalDateTime.now());loginLog.setStatus(0);// 0=失败(必须显式设置,避免默认值错误)loginLog.setFailReason(reason);loginLog.setUserAgent(userAgent);loginLog.setDeviceType(extractDeviceType(userAgent));loginLog.setLocation(resolveLocation(ipAddress));System.out.println("这里是记录登录失败日志的方法内部");log.warn("用户登录失败 - 用户: {}", account);loginLogService.addOneLoginlog(loginLog);}catch (Exception e){e.printStackTrace();}}/*** 提取设备类型*/private String extractDeviceType(String userAgent) {if (userAgent == null) {return "Unknown";}if (userAgent.contains("Android")) {return "Android";} else if (userAgent.contains("iPhone") || userAgent.contains("iOS")) {return "iOS";} else if (userAgent.contains("Windows") || userAgent.contains("Macintosh")) {return "PC";} else {return "Other";}}/*** 解析IP地址对应的地理位置* 实际项目中可集成IP解析服务*/private String resolveLocation(String ipAddress) {// 示例实现,实际项目中应调用IP解析APIif (ipAddress.startsWith("192.168.") || ipAddress.startsWith("127.0.")) {return "本地网络";}return "未知地区";}
}

虽然,我也做了配置路径,设置好了处理登录请求的接口地址。但是,系统依然没有去寻址这里。

package com.example.feng.config;import com.example.feng.security.JwtAuthenticationFilter;
import com.example.feng.security.JwtTokenProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;@Configuration
@EnableWebSecurity
@EnableMethodSecurity(prePostEnabled = true) // 启用方法级权限注解
public class SecurityConfig {private final JwtTokenProvider jwtTokenProvider;private final UserDetailsService userDetailsService; // 注入用户详情服务(表单登录必需)// 构造函数注入依赖public SecurityConfig(JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) {this.jwtTokenProvider = jwtTokenProvider;this.userDetailsService = userDetailsService;}@Beanpublic SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {http// 1. 修复Session配置冲突:表单登录需保留Session,删除STATELESS配置.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED))// 2. 表单登录配置(核心:确保登录接口可被匿名访问).formLogin(form -> form.loginPage("/auth/login") // 登录页面路径.loginProcessingUrl("/auth/login-by-email") // 登录请求处理接口.defaultSuccessUrl("/web/index", true) // 登录成功强制跳转.failureUrl("/auth/login?error=true") // 登录失败跳转(带错误参数).permitAll() // 允许匿名访问登录相关接口)// 3. CSRF配置:排除登录接口,避免表单提交CSRF验证失败.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) // 前端可获取CSRF令牌.ignoringRequestMatchers("/auth/login-by-email", // 登录接口排除CSRF"/liuyan","/liuyan/response","/about/**","/auth/send-email-code"))// 4. 授权规则配置(修复/liuyan权限,明确公共接口).authorizeHttpRequests(auth -> auth// 公共接口:允许匿名访问.requestMatchers("/auth/login","/auth/login-by-email", // 关键:登录接口允许匿名调用"/auth/register","/auth/send-email-code","/auth/register-by-email","/swagger-ui/**","/v3/api-docs/**","/webjars/**","/favicon.ico","/web/**","/static/**","/about/**").permitAll()// 管理员接口:需ADMIN角色.requestMatchers("/admin/**").hasRole("ADMIN")// 其他所有请求:需登录认证.anyRequest().authenticated())// 5. 添加JWT过滤器:在用户名密码过滤器之前执行,且已在过滤器内部排除登录接口.addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, userDetailsService),UsernamePasswordAuthenticationFilter.class)// 6. 注销配置(可选,优化用户体验).logout(logout -> logout.logoutUrl("/auth/logout").logoutSuccessUrl("/auth/login?logout=true") // 注销成功跳转登录页.permitAll());return http.build();}// 密码加密器(表单登录密码验证必需)@Beanpublic PasswordEncoder passwordEncoder() {return new BCryptPasswordEncoder();}// 认证管理器(表单登录认证必需)@Beanpublic AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {return authenticationConfiguration.getAuthenticationManager();}
//    @Bean
//    public AuthenticationSuccessHandler authenticationSuccessHandler() {
//        return (request, response, authentication) -> {
//            // 设置安全上下文
//            SecurityContextHolder.getContext().setAuthentication(authentication);
//
//            // 设置Session
//            request.getSession().setAttribute("loginUser", authentication.getPrincipal());
//
//            response.sendRedirect("/web/index");
//        };
//    }
}

如图。目前就是这样。

也就是说,你可以自定义一个实现类,但是必须实现官方的那个接口。

UserDetailsService;

它里面有一个方法叫

loadUserByUsername;

你必须重写这个方法。才能实现登录效果。

http://www.dtcms.com/a/389539.html

相关文章:

  • 企业移动化管理(EMM)实战:如何一站式解决设备、应用与安全管控难题?
  • 高频面试题——深入掌握栈和队列的数据结构技巧
  • 【C++ qml】qml页面加载配置文件信息的两种方式
  • 运维笔记:神卓 N600 解决企业远程访问 NAS 的 3 个核心痛点
  • GitHub 热榜项目 - 日榜(2025-09-18)
  • 使用开源免费的组件构建一套分布式微服务技术选型推荐
  • 需求质量检测Prompt之是否涉及异常场景
  • QT按钮和容器
  • Kafka4.0 可观测性最佳实践
  • 深入解析 Spring AI 系列:解析函数调用
  • ​​[硬件电路-245]:电气制图软件有哪些
  • 不会索赔500万的苹果,翻车如期到来,不过已没啥影响了
  • 第十一章:AI进阶之--模块的概念与使用(一)
  • 【IoTDB】01 - IoTDB的基本使用
  • 【C++】模版语法基础:认识模版(初识篇)
  • 继承测试用例回归策略
  • 卡普空《怪物猎人》系列策略转变:PC平台成重要增长点
  • UML 顺序图 | 概念 / 组成 / 作用 / 绘制
  • 安装SSL证书后如何测试和验证其是否正确配置?
  • A股大盘数据-20250918分析
  • 容器环境变量管理在云服务器多环境部署中的配置方法
  • 算法练习-排序-选择排序
  • 岭回归(Ridge Regression)在机器学习中的应用
  • python高级编程面试题
  • 模拟ic工程师如何提升自己?
  • springboot flowable 工作流入门与实战
  • 飞算Java的在线考试系统的设计与实现——学生开发者的课程实践记录
  • Vue3 基础语法详解:从入门到实践
  • 大白话聊明白:同步刷盘、异步刷盘以及RocketMQ和RabbitMQ的刷盘策略
  • I0流学习