package com.ecep.contract.config; import java.util.List; import java.util.stream.Collectors; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.config.Customizer; 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.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.http.SessionCreationPolicy; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.User; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.core.userdetails.UsernameNotFoundException; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.web.SecurityFilterChain; import com.ecep.contract.ds.other.service.EmployeeService; import com.ecep.contract.model.Employee; import com.ecep.contract.model.EmployeeRole; import com.ecep.contract.vo.EmployeeVo; /** * Spring Security配置类 * 用于配置安全认证和授权规则 */ @Configuration @EnableWebSecurity @EnableMethodSecurity(prePostEnabled = true) // 开启 @PreAuthorize 等注解 public class SecurityConfig { @Lazy @Autowired private EmployeeService employeeService; /** * 配置HTTP安全策略 * 启用表单登录和HTTP Basic认证 */ @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception { http .authorizeHttpRequests(authorize -> authorize .requestMatchers("/login.html", "/css/**", "/js/**", "/images/**", "/webjars/**", "/login", "/error", "/api/login", "/ws/**") .permitAll() // 允许静态资源、登录页面、错误页面和JSON登录API访问,以及WebSocket连接 .anyRequest().authenticated() // 其他所有请求需要认证 ) .csrf(AbstractHttpConfigurer::disable) // 禁用CSRF保护,适合开发环境 .formLogin(form -> form .loginPage("/login.html") // 直接使用静态登录页面 .loginProcessingUrl("/login") // 登录处理URL .permitAll() // 允许所有人访问登录页面 .defaultSuccessUrl("/", true) // 登录成功后重定向到首页 .failureUrl("/login.html?error=true") // 登录失败后重定向到登录页面并显示错误 .usernameParameter("username") // 用户名参数名 .passwordParameter("password") // 密码参数名 ) .httpBasic(Customizer.withDefaults()) // 启用HTTP Basic认证 .logout(logout -> logout .logoutUrl("/logout") // 注销URL .logoutSuccessUrl("/login?logout=true") // 注销成功后重定向到登录页面 .invalidateHttpSession(true) // 使会话失效 .deleteCookies("JSESSIONID") // 删除会话cookie .permitAll() // 允许所有人访问注销URL ) .sessionManagement(session -> session .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED) // 根据需要创建会话 .maximumSessions(1) // 每个用户最多1个会话 .expiredUrl("/login?expired=true") // 会话过期后重定向到登录页面 ) .authenticationManager(authenticationManager); // 设置认证管理器 return http.build(); } /** * 配置AuthenticationManager * 用于处理认证请求 */ @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { return authenticationConfiguration.getAuthenticationManager(); } /** * 配置密码编码器 * BCryptPasswordEncoder是Spring Security推荐的密码编码器 */ @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } /** * 配置基于EmployeeService的用户认证 * 通过EmployeeService获取员工信息并转换为Spring Security的UserDetails */ @Bean public UserDetailsService userDetailsService() { return username -> { // 使用EmployeeService根据用户名查找员工 EmployeeVo employeeVo = employeeService.findByAccount(username); // 如果找不到员工,抛出UsernameNotFoundException异常 if (employeeVo == null) { throw new UsernameNotFoundException("用户不存在: " + username); } // 获取员工实体 Employee employee = employeeService.getById(employeeVo.getId()); // 检查员工是否活跃 if (!employee.isActive()) { throw new UsernameNotFoundException("用户已禁用: " + username); } // 将员工角色转换为Spring Security的GrantedAuthority List authorities = getAuthoritiesFromRoles( employeeService.getRolesByEmployeeId(employee.getId())); // 创建并返回UserDetails对象 // 注意:根据系统设计,Employee实体中没有密码字段,系统使用IP/MAC绑定认证 // 这里使用密码编码器加密后的固定密码,确保认证流程能够正常工作 return User.builder() .username(employee.getAccount()) .password(passwordEncoder().encode("default123")) // 使用默认密码进行加密 .accountExpired(false) // 账户未过期 .accountLocked(false) // 账户未锁定 .credentialsExpired(false) // 凭证未过期 .disabled(!employee.isActive()) // 根据员工状态设置是否禁用 .authorities(authorities) .build(); }; } /** * 将EmployeeRole列表转换为Spring Security的GrantedAuthority列表 */ private List getAuthoritiesFromRoles(List roles) { if (roles == null || roles.isEmpty()) { return List.of(); } // 为每个角色创建GrantedAuthority // 系统管理员拥有ADMIN角色,其他用户拥有USER角色 return roles.stream() .filter(EmployeeRole::isActive) // 只包含活跃的角色 .map(role -> { if (role.isSystemAdministrator()) { return new SimpleGrantedAuthority("ROLE_ADMIN"); } else { return new SimpleGrantedAuthority("ROLE_USER"); } }) .collect(Collectors.toList()); } // Spring Security会自动配置一个使用我们定义的UserDetailsService的DaoAuthenticationProvider // 移除显式的authenticationProvider Bean定义以避免警告 // 当同时存在AuthenticationProvider和UserDetailsService Bean时,Spring // Security会优先使用AuthenticationProvider // 而忽略直接的UserDetailsService,虽然这不影响功能,但会产生警告 }