feat: 重构员工控制器并优化JSON序列化配置
refactor(EmployeeController): 重命名EmployyeController为EmployeeController并优化代码结构 feat(EmployeeController): 添加@JsonIgnoreProperties注解解决循环引用问题 feat(JacksonConfig): 新增Jackson配置类处理Hibernate代理和循环引用 fix(EmployeeService): 修复缓存注解格式问题 feat(Employee): 添加@JsonIgnoreProperties注解忽略可能导致循环引用的字段 feat(EmployeeRole): 添加@JsonIgnore注解忽略关联字段 fix(application.properties): 调整Redis缓存配置和错误处理设置 refactor(IndexController): 移除错误处理方法 feat(GlobalExceptionHandler): 新增全局异常处理类 refactor(SecurityConfig): 优化安全配置并启用方法级安全注解 refactor(AbstractCtx): 优化日期时间处理方法 build: 更新项目版本至0.0.53-SNAPSHOT
This commit is contained in:
@@ -0,0 +1,160 @@
|
||||
package com.ecep.contract.config;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.converter.HttpMessageNotReadableException;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.HttpRequestMethodNotSupportedException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.MissingServletRequestParameterException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
|
||||
import org.springframework.web.servlet.NoHandlerFoundException;
|
||||
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 全局异常处理器,捕获并处理所有Controller层抛出的异常,将错误信息以JSON格式返回给前端
|
||||
*/
|
||||
// @RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
|
||||
|
||||
/**
|
||||
* 处理所有未捕获的异常
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<Map<String, Object>> handleException(Exception e) {
|
||||
logger.error("系统异常", e);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 500);
|
||||
result.put("message", "系统内部错误:" + e.getMessage());
|
||||
result.put("errorType", e.getClass().getName());
|
||||
result.put("success", false);
|
||||
return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理运行时异常
|
||||
*/
|
||||
@ExceptionHandler(RuntimeException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleRuntimeException(RuntimeException e) {
|
||||
logger.error("运行时异常", e);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 500);
|
||||
result.put("message", "运行时错误:" + e.getMessage());
|
||||
result.put("errorType", e.getClass().getName());
|
||||
result.put("success", false);
|
||||
return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理请求方法不支持异常
|
||||
*/
|
||||
@ExceptionHandler(HttpRequestMethodNotSupportedException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleHttpRequestMethodNotSupportedException(
|
||||
HttpRequestMethodNotSupportedException e) {
|
||||
logger.warn("请求方法不支持", e);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 405);
|
||||
result.put("message", "请求方法不支持:" + e.getMessage());
|
||||
result.put("errorType", "MethodNotAllowed");
|
||||
result.put("success", false);
|
||||
return new ResponseEntity<>(result, HttpStatus.METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理资源未找到异常
|
||||
*/
|
||||
@ExceptionHandler(NoHandlerFoundException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleNoHandlerFoundException(NoHandlerFoundException e) {
|
||||
logger.warn("资源未找到", e);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 404);
|
||||
result.put("message", "请求的资源不存在:" + e.getRequestURL());
|
||||
result.put("errorType", "ResourceNotFound");
|
||||
result.put("success", false);
|
||||
return new ResponseEntity<>(result, HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理参数绑定异常
|
||||
*/
|
||||
@ExceptionHandler(BindException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleBindException(BindException e) {
|
||||
logger.warn("参数绑定异常", e);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 400);
|
||||
result.put("message", "参数绑定错误:" + e.getAllErrors().get(0).getDefaultMessage());
|
||||
result.put("errorType", "ParameterBindingError");
|
||||
result.put("success", false);
|
||||
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理方法参数验证异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleMethodArgumentNotValidException(
|
||||
MethodArgumentNotValidException e) {
|
||||
logger.warn("方法参数验证异常", e);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 400);
|
||||
result.put("message", "参数验证错误:" + e.getBindingResult().getAllErrors().get(0).getDefaultMessage());
|
||||
result.put("errorType", "ParameterValidationError");
|
||||
result.put("success", false);
|
||||
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理缺少请求参数异常
|
||||
*/
|
||||
@ExceptionHandler(MissingServletRequestParameterException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleMissingServletRequestParameterException(
|
||||
MissingServletRequestParameterException e) {
|
||||
logger.warn("缺少请求参数异常", e);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 400);
|
||||
result.put("message", "缺少必要参数:" + e.getParameterName());
|
||||
result.put("errorType", "MissingParameter");
|
||||
result.put("success", false);
|
||||
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理方法参数类型不匹配异常
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentTypeMismatchException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleMethodArgumentTypeMismatchException(
|
||||
MethodArgumentTypeMismatchException e) {
|
||||
logger.warn("方法参数类型不匹配异常", e);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 400);
|
||||
result.put("message", "参数类型错误:参数 '" + e.getName() + "' 应类型为 "
|
||||
+ (e.getRequiredType() != null ? e.getRequiredType().getSimpleName() : "未知类型"));
|
||||
result.put("errorType", "ParameterTypeError");
|
||||
result.put("success", false);
|
||||
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理HTTP消息不可读异常(如JSON解析错误)
|
||||
*/
|
||||
@ExceptionHandler(HttpMessageNotReadableException.class)
|
||||
public ResponseEntity<Map<String, Object>> handleHttpMessageNotReadableException(
|
||||
HttpMessageNotReadableException e) {
|
||||
logger.warn("HTTP消息不可读异常", e);
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("code", 400);
|
||||
result.put("message", "请求体格式错误:" + e.getMessage());
|
||||
result.put("errorType", "MessageFormatError");
|
||||
result.put("success", false);
|
||||
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
}
|
||||
239
server/src/main/java/com/ecep/contract/config/JacksonConfig.java
Normal file
239
server/src/main/java/com/ecep/contract/config/JacksonConfig.java
Normal file
@@ -0,0 +1,239 @@
|
||||
package com.ecep.contract.config;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.hibernate.Hibernate;
|
||||
import org.hibernate.collection.spi.PersistentCollection;
|
||||
import org.hibernate.proxy.HibernateProxy;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
|
||||
|
||||
import com.ecep.contract.model.IdentityEntity;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.BeanDescription;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializationConfig;
|
||||
import com.fasterxml.jackson.databind.SerializationFeature;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.ser.BeanSerializerModifier;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||
import com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;
|
||||
|
||||
/**
|
||||
* Jackson配置类,用于配置JSON序列化相关的设置,解决循环引用问题
|
||||
*/
|
||||
@Configuration
|
||||
public class JacksonConfig {
|
||||
|
||||
@Bean
|
||||
public ObjectMapper objectMapper(Jackson2ObjectMapperBuilder builder) {
|
||||
ObjectMapper objectMapper = builder.build();
|
||||
|
||||
// 关闭日期时间格式化输出为时间戳,而是输出为ISO格式的字符串
|
||||
objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
|
||||
|
||||
// 配置处理循环引用的策略,使用INDENT_OUTPUT格式化输出
|
||||
objectMapper.enable(SerializationFeature.INDENT_OUTPUT);
|
||||
// 处理循环引用,启用引用处理功能
|
||||
objectMapper.enable(SerializationFeature.WRITE_ENUMS_USING_TO_STRING);
|
||||
|
||||
// 配置Java 8时间模块的序列化格式
|
||||
JavaTimeModule javaTimeModule = new JavaTimeModule();
|
||||
javaTimeModule.addSerializer(LocalDate.class, new LocalDateSerializer(DateTimeFormatter.ISO_LOCAL_DATE));
|
||||
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
javaTimeModule.addSerializer(LocalTime.class, new LocalTimeSerializer(DateTimeFormatter.ofPattern("HH:mm:ss")));
|
||||
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DateTimeFormatter.ISO_LOCAL_DATE));
|
||||
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(
|
||||
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
|
||||
javaTimeModule.addDeserializer(LocalTime.class, new LocalTimeDeserializer(DateTimeFormatter.ISO_LOCAL_TIME));
|
||||
objectMapper.registerModule(javaTimeModule);
|
||||
|
||||
// 添加自定义模块,用于处理JPA/Hibernate代理对象
|
||||
SimpleModule proxyModule = new SimpleModule("HibernateProxyModule");
|
||||
|
||||
// 添加代理对象序列化器,只输出ID字段
|
||||
proxyModule.addSerializer(HibernateProxy.class, new HibernateProxySerializer());
|
||||
// 使用BeanSerializerModifier来处理IdentityEntity类型,避免递归调用
|
||||
proxyModule.setSerializerModifier(new BeanSerializerModifier() {
|
||||
@Override
|
||||
public JsonSerializer<?> modifySerializer(SerializationConfig config,
|
||||
BeanDescription beanDesc,
|
||||
JsonSerializer<?> serializer) {
|
||||
// 只对IdentityEntity类型进行修改
|
||||
if (IdentityEntity.class.isAssignableFrom(beanDesc.getBeanClass()) &&
|
||||
!HibernateProxy.class.isAssignableFrom(beanDesc.getBeanClass())) {
|
||||
return new IdentityEntitySerializer(serializer);
|
||||
}
|
||||
return serializer;
|
||||
}
|
||||
});
|
||||
|
||||
objectMapper.registerModule(proxyModule);
|
||||
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
* 专门用于处理Hibernate代理对象的序列化器
|
||||
*/
|
||||
private static class HibernateProxySerializer extends StdSerializer<HibernateProxy> {
|
||||
|
||||
protected HibernateProxySerializer() {
|
||||
super(HibernateProxy.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(HibernateProxy value, JsonGenerator gen, SerializerProvider serializers)
|
||||
throws IOException {
|
||||
// 尝试初始化代理对象,如果未初始化则只输出ID
|
||||
try {
|
||||
Object unwrapped = value.getHibernateLazyInitializer().getImplementation();
|
||||
// 检查是否为IdentityEntity实现类
|
||||
if (unwrapped instanceof IdentityEntity) {
|
||||
gen.writeStartObject();
|
||||
gen.writeNumberField("id", ((IdentityEntity) unwrapped).getId());
|
||||
gen.writeEndObject();
|
||||
} else {
|
||||
// 如果不是IdentityEntity,使用默认序列化
|
||||
serializers.defaultSerializeValue(unwrapped, gen);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// 如果初始化失败,只输出ID
|
||||
if (value instanceof IdentityEntity) {
|
||||
gen.writeStartObject();
|
||||
gen.writeNumberField("id", ((IdentityEntity) value).getId());
|
||||
gen.writeEndObject();
|
||||
} else {
|
||||
// 如果不是IdentityEntity,输出对象的基本信息
|
||||
gen.writeStartObject();
|
||||
gen.writeStringField("class", value.getClass().getName());
|
||||
gen.writeEndObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于处理集合类型的序列化器,避免懒加载集合在会话关闭时无法序列化的问题
|
||||
*/
|
||||
private static class CollectionSerializer extends StdSerializer<Collection<?>> {
|
||||
|
||||
protected CollectionSerializer() {
|
||||
super((Class<Collection<?>>) (Class<?>) Collection.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Collection<?> value, JsonGenerator gen, SerializerProvider serializers)
|
||||
throws IOException {
|
||||
// 检查集合是否为Hibernate的持久化集合
|
||||
if (value instanceof PersistentCollection) {
|
||||
PersistentCollection persistentCollection = (PersistentCollection) value;
|
||||
// 如果集合未初始化,返回空集合
|
||||
if (!persistentCollection.wasInitialized()) {
|
||||
gen.writeStartArray();
|
||||
gen.writeEndArray();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 如果已初始化,使用默认序列化
|
||||
serializers.defaultSerializeValue(value, gen);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于处理IdentityEntity类型的序列化器包装器
|
||||
*/
|
||||
private static class IdentityEntitySerializer extends StdSerializer<Object> {
|
||||
private final JsonSerializer<?> delegate;
|
||||
|
||||
protected IdentityEntitySerializer(JsonSerializer<?> delegate) {
|
||||
super(Object.class);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers)
|
||||
throws IOException {
|
||||
// 对于IdentityEntity对象,如果未初始化,则只输出ID
|
||||
if (value instanceof IdentityEntity && !Hibernate.isInitialized(value)) {
|
||||
gen.writeStartObject();
|
||||
gen.writeNumberField("id", ((IdentityEntity) value).getId());
|
||||
gen.writeStringField("_proxy_", value.getClass().getName());
|
||||
gen.writeEndObject();
|
||||
} else {
|
||||
// 已初始化的实体,使用原始的序列化器进行序列化,避免递归调用
|
||||
((JsonSerializer<Object>) delegate).serialize(value, gen, serializers);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用于处理对象引用的序列化器,通过检测和管理对象引用避免循环引用问题
|
||||
*/
|
||||
private static class IdentityReferenceSerializer extends StdSerializer<Object> {
|
||||
private final JsonSerializer<?> delegate;
|
||||
private final ThreadLocal<java.util.Set<Object>> visitedObjects = ThreadLocal
|
||||
.withInitial(java.util.HashSet::new);
|
||||
|
||||
protected IdentityReferenceSerializer(JsonSerializer<?> delegate) {
|
||||
super(Object.class);
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
|
||||
// 处理null值
|
||||
if (value == null) {
|
||||
serializers.defaultSerializeNull(gen);
|
||||
return;
|
||||
}
|
||||
|
||||
java.util.Set<Object> visited = visitedObjects.get();
|
||||
try {
|
||||
// 检查对象是否已经被访问过
|
||||
if (visited.contains(value)) {
|
||||
// 如果对象实现了IdentityEntity,只输出ID
|
||||
if (value instanceof IdentityEntity) {
|
||||
gen.writeStartObject();
|
||||
gen.writeNumberField("id", ((IdentityEntity) value).getId());
|
||||
gen.writeStringField("_ref_", "" + ((IdentityEntity) value).getId());
|
||||
gen.writeEndObject();
|
||||
} else {
|
||||
// 对于非IdentityEntity对象,输出对象的toString或hashCode
|
||||
gen.writeString(value.toString());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 标记对象为已访问
|
||||
visited.add(value);
|
||||
|
||||
// 使用委托序列化器进行正常序列化
|
||||
((JsonSerializer<Object>) delegate).serialize(value, gen, serializers);
|
||||
} finally {
|
||||
// 清理访问记录
|
||||
visited.remove(value);
|
||||
if (visited.isEmpty()) {
|
||||
visitedObjects.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,9 @@ 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.authentication.dao.DaoAuthenticationProvider;
|
||||
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;
|
||||
@@ -22,7 +23,6 @@ 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 org.springframework.security.config.Customizer;
|
||||
|
||||
import com.ecep.contract.ds.other.service.EmployeeService;
|
||||
import com.ecep.contract.model.Employee;
|
||||
@@ -34,8 +34,9 @@ import com.ecep.contract.model.EmployeeRole;
|
||||
*/
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
@EnableMethodSecurity(prePostEnabled = true) // 开启 @PreAuthorize 等注解
|
||||
public class SecurityConfig {
|
||||
|
||||
|
||||
@Lazy
|
||||
@Autowired
|
||||
private EmployeeService employeeService;
|
||||
@@ -45,49 +46,53 @@ public class SecurityConfig {
|
||||
* 启用表单登录和HTTP Basic认证
|
||||
*/
|
||||
@Bean
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager) throws Exception {
|
||||
public SecurityFilterChain securityFilterChain(HttpSecurity http, AuthenticationManager authenticationManager)
|
||||
throws Exception {
|
||||
http
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/login.html", "/css/**", "/js/**", "/images/**", "/webjars/**", "/login", "/error").permitAll() // 允许静态资源、登录页面和错误页面访问
|
||||
.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); // 设置认证管理器
|
||||
|
||||
.authorizeHttpRequests(authorize -> authorize
|
||||
.requestMatchers("/login.html", "/css/**", "/js/**", "/images/**", "/webjars/**", "/login",
|
||||
"/error")
|
||||
.permitAll() // 允许静态资源、登录页面和错误页面访问
|
||||
.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 {
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration)
|
||||
throws Exception {
|
||||
return authenticationConfiguration.getAuthenticationManager();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 配置密码编码器
|
||||
* BCryptPasswordEncoder是Spring Security推荐的密码编码器
|
||||
@@ -96,7 +101,7 @@ public class SecurityConfig {
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 配置基于EmployeeService的用户认证
|
||||
* 通过EmployeeService获取员工信息并转换为Spring Security的UserDetails
|
||||
@@ -106,31 +111,32 @@ public class SecurityConfig {
|
||||
return username -> {
|
||||
// 使用EmployeeService根据用户名查找员工
|
||||
Employee employee = employeeService.findByAccount(username);
|
||||
|
||||
|
||||
// 如果找不到员工,抛出UsernameNotFoundException异常
|
||||
if (employee == null) {
|
||||
throw new UsernameNotFoundException("用户不存在: " + username);
|
||||
}
|
||||
|
||||
|
||||
// 检查员工是否活跃
|
||||
if (!employee.isActive()) {
|
||||
throw new UsernameNotFoundException("用户已禁用: " + username);
|
||||
}
|
||||
|
||||
|
||||
// 将员工角色转换为Spring Security的GrantedAuthority
|
||||
List<GrantedAuthority> authorities = getAuthoritiesFromRoles(employee.getRoles());
|
||||
|
||||
List<GrantedAuthority> authorities = getAuthoritiesFromRoles(
|
||||
employeeService.getRolesByEmployeeId(employee.getId()));
|
||||
|
||||
// 创建并返回UserDetails对象
|
||||
// 注意:根据系统设计,Employee实体中没有密码字段,系统使用IP/MAC绑定认证
|
||||
// 这里使用密码编码器加密后的固定密码,确保认证流程能够正常工作
|
||||
return User.builder()
|
||||
.username(employee.getName())
|
||||
.password(passwordEncoder().encode("default123")) // 使用默认密码进行加密
|
||||
.authorities(authorities)
|
||||
.build();
|
||||
.username(employee.getName())
|
||||
.password(passwordEncoder().encode("default123")) // 使用默认密码进行加密
|
||||
.authorities(authorities)
|
||||
.build();
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 将EmployeeRole列表转换为Spring Security的GrantedAuthority列表
|
||||
*/
|
||||
@@ -138,23 +144,24 @@ public class SecurityConfig {
|
||||
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());
|
||||
.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
|
||||
// 当同时存在AuthenticationProvider和UserDetailsService Bean时,Spring
|
||||
// Security会优先使用AuthenticationProvider
|
||||
// 而忽略直接的UserDetailsService,虽然这不影响功能,但会产生警告
|
||||
}
|
||||
Reference in New Issue
Block a user