feat: 实现基于JSON的登录API和安全认证

refactor: 重构登录逻辑和会话管理

fix: 修复会话ID类型和WebSocket连接问题

build: 更新项目版本号和添加Servlet API依赖

style: 清理无用导入和注释代码
This commit is contained in:
2025-09-08 17:46:48 +08:00
parent 3b90db0450
commit 23e1f98ae5
17 changed files with 477 additions and 223 deletions

View File

@@ -6,12 +6,12 @@
<parent>
<groupId>com.ecep.contract</groupId>
<artifactId>Contract-Manager</artifactId>
<version>0.0.58-SNAPSHOT</version>
<version>0.0.80-SNAPSHOT</version>
</parent>
<groupId>com.ecep.contract</groupId>
<artifactId>server</artifactId>
<version>0.0.58-SNAPSHOT</version>
<version>0.0.80-SNAPSHOT</version>
<properties>
<maven.compiler.source>${java.version}</maven.compiler.source>
@@ -22,7 +22,7 @@
<dependency>
<groupId>com.ecep.contract</groupId>
<artifactId>common</artifactId>
<version>0.0.58-SNAPSHOT</version>
<version>0.0.80-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
@@ -74,6 +74,14 @@
<!-- <artifactId>hibernate-jpamodelgen</artifactId> -->
<!-- <scope>provided</scope> -->
<!-- </dependency> -->
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.mysql</groupId>

View File

@@ -0,0 +1,159 @@
package com.ecep.contract.api.controller;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.data.domain.Sort;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ecep.contract.ds.other.service.EmployeeAuthBindService;
import com.ecep.contract.ds.other.service.EmployeeService;
import com.ecep.contract.model.Employee;
import com.ecep.contract.model.EmployeeAuthBind;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@RestController
@RequestMapping("/api")
public class LoginApiController {
private static final Logger logger = LoggerFactory.getLogger(LoginApiController.class);
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private EmployeeService employeeService;
@Autowired
private EmployeeAuthBindService employeeAuthBindService;
/**
* JSON格式的登录API
* 使用Spring Security进行认证返回JSON格式的响应
*/
@PostMapping("/login")
public Map<String, Object> jsonLogin(@RequestBody LoginRequest loginRequest, HttpServletRequest request) {
// 获取HttpSession
HttpSession session = request.getSession();
// 添加日志记录,确认方法被调用
logger.debug("jsonLogin方法被调用请求类型: {}", loginRequest.getType());
logger.debug("请求用户名: {}", loginRequest.getUsername());
logger.debug("会话ID: {}", session != null ? session.getId() : "null");
Map<String, Object> result = new HashMap<>();
try {
Employee employee = null;
if ("client".equals(loginRequest.getType())) {
// 根据用户名查找Employee对象
employee = employeeService.findByAccount(loginRequest.getUsername());
if (employee == null) {
result.put("success", false);
result.put("error", "用户信息不存在");
return result;
}
List<EmployeeAuthBind> authBinds = employeeAuthBindService.findAllByEmployee(employee, Sort.unsorted());
if (authBinds.isEmpty()) {
result.put("success", false);
result.put("error", "用户未绑定认证信息");
return result;
}
EmployeeAuthBind matched = null;
Map<String, String> signMap = loginRequest.getSign();
if (signMap != null) {
for (EmployeeAuthBind authBind : authBinds) {
String clientIp = signMap.get(authBind.getMac());
if (clientIp != null && authBind.getIp().equals(clientIp)) {
matched = authBind;
break;
}
}
}
if (matched == null) {
result.put("success", false);
result.put("error", "认证信息错误");
return result;
}
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(), "default123");
// 使用AuthenticationManager进行认证
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 设置认证结果到SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
} else {
// 创建认证令牌
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
loginRequest.getUsername(), loginRequest.getPassword());
// 使用AuthenticationManager进行认证
Authentication authentication = authenticationManager.authenticate(authenticationToken);
// 设置认证结果到SecurityContext
SecurityContextHolder.getContext().setAuthentication(authentication);
employee = employeeService.findByAccount(loginRequest.getUsername());
}
if (employee != null) {
// 获取当前登录用户
User currentUser = (User) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
// 返回登录成功的信息
result.put("success", true);
result.put("employeeId", employee.getId());
result.put("sessionId", session.getId());
result.put("username", employee.getAccount());
result.put("roles", currentUser.getAuthorities());
result.put("message", "登录成功");
} else {
// 用户不存在
result.put("success", false);
result.put("error", "用户信息不存在");
}
} catch (AuthenticationException e) {
// 认证失败
result.put("success", false);
result.put("error", "用户名或密码错误");
} catch (Exception e) {
// 其他错误
result.put("success", false);
result.put("error", "登录过程中发生错误: " + e.getMessage());
}
return result;
}
/**
* 登录请求的JSON数据模型
*/
@Data
public static class LoginRequest {
private String type;
private String username;
private String password;
private Map<String, String> sign = new HashMap<>();
}
}

View File

@@ -15,13 +15,15 @@ import org.springframework.web.method.annotation.MethodArgumentTypeMismatchExcep
import org.springframework.web.servlet.NoHandlerFoundException;
import org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
/**
* 全局异常处理器捕获并处理所有Controller层抛出的异常将错误信息以JSON格式返回给前端
*/
// @RestControllerAdvice
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
@@ -36,9 +38,24 @@ public class GlobalExceptionHandler {
result.put("code", 500);
result.put("message", "系统内部错误:" + e.getMessage());
result.put("errorType", e.getClass().getName());
result.put("stackTrace", getStackTraceAsString(e));
result.put("success", false);
return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR);
}
/**
* 将异常堆栈转换为字符串
*/
private String getStackTraceAsString(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
try {
throwable.printStackTrace(pw);
return sw.toString();
} finally {
pw.close();
}
}
/**
* 处理运行时异常
@@ -50,6 +67,7 @@ public class GlobalExceptionHandler {
result.put("code", 500);
result.put("message", "运行时错误:" + e.getMessage());
result.put("errorType", e.getClass().getName());
result.put("stackTrace", getStackTraceAsString(e));
result.put("success", false);
return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR);
}

View File

@@ -51,8 +51,8 @@ public class SecurityConfig {
http
.authorizeHttpRequests(authorize -> authorize
.requestMatchers("/login.html", "/css/**", "/js/**", "/images/**", "/webjars/**", "/login",
"/error")
.permitAll() // 允许静态资源、登录页面错误页面访问
"/error", "/api/login")
.permitAll() // 允许静态资源、登录页面错误页面和JSON登录API访问
.anyRequest().authenticated() // 其他所有请求需要认证
)
.csrf(AbstractHttpConfigurer::disable) // 禁用CSRF保护适合开发环境

View File

@@ -1,6 +1,9 @@
package com.ecep.contract.ds.other.controller;
import java.util.Map;
import java.util.HashMap;
import jakarta.servlet.http.HttpSession;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
@@ -8,6 +11,7 @@ import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.security.core.userdetails.User;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@@ -50,4 +54,35 @@ public class EmployeeController {
employeeService.delete(employee);
}
/**
* 获取当前登录用户的信息
* 包括employeeId和sessionId
*/
@RequestMapping("/currentUser")
public Map<String, Object> getCurrentUser(HttpSession session) {
Map<String, Object> result = new HashMap<>();
try {
// 获取当前登录用户
User currentUser = SecurityUtils.getCurrentUser();
if (currentUser != null) {
// 根据用户名查找Employee对象
Employee employee = employeeService.findByName(currentUser.getUsername());
if (employee != null) {
result.put("employeeId", employee.getId());
result.put("sessionId", session.getId());
result.put("success", true);
return result;
}
}
} catch (Exception e) {
// 处理异常
}
// 如果获取失败,返回错误信息
result.put("success", false);
result.put("error", "无法获取当前用户信息");
return result;
}
}

View File

@@ -4,21 +4,14 @@ import java.util.List;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.stereotype.Repository;
import com.ecep.contract.ds.MyRepository;
import com.ecep.contract.model.Employee;
import com.ecep.contract.model.EmployeeAuthBind;
@Lazy
@Repository
public interface EmployeeAuthBindRepository extends
// JDBC interfaces
CrudRepository<EmployeeAuthBind, Integer>, PagingAndSortingRepository<EmployeeAuthBind, Integer>,
// JPA interfaces
JpaRepository<EmployeeAuthBind, Integer>, JpaSpecificationExecutor<EmployeeAuthBind> {
public interface EmployeeAuthBindRepository extends MyRepository<EmployeeAuthBind, Integer> {
List<EmployeeAuthBind> findAllByEmployee(Employee employee, Sort sort);
}