diff --git a/pom.xml b/pom.xml
index df9d2ea..70f1d0c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -78,22 +78,4 @@
2.15.3
-
-
-
-
- org.springframework.boot
- spring-boot-maven-plugin
-
-
-
- org.projectlombok
- lombok
-
-
-
-
-
-
-
diff --git a/src/main/java/com/supervision/ai/service/hub/config/SecurityConfig.java b/src/main/java/com/supervision/ai/service/hub/config/SecurityConfig.java
new file mode 100644
index 0000000..7266267
--- /dev/null
+++ b/src/main/java/com/supervision/ai/service/hub/config/SecurityConfig.java
@@ -0,0 +1,73 @@
+package com.supervision.ai.service.hub.config;
+
+import com.supervision.ai.service.hub.filter.JwtAuthenticationFilter;
+import com.supervision.ai.service.hub.service.impl.SysUserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.AuthenticationProvider;
+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.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.crypto.bcrypt.BCryptPasswordEncoder;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+
+
+@Configuration
+@EnableWebSecurity
+public class SecurityConfig {
+
+ @Autowired
+ private JwtAuthenticationFilter jwtAuthenticationFilter;
+ @Autowired
+ private SysUserService sysUserService;
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .csrf(AbstractHttpConfigurer::disable) // 禁用CSRF
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/auth/**").permitAll()
+ .anyRequest().authenticated()
+ )
+ .sessionManagement(session -> session
+ .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
+ )
+ .authenticationProvider(authenticationProvider())
+ .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
+ // 👇 正确禁用方式(推荐)
+ .formLogin(Customizer.withDefaults()) // 启用默认配置
+ .httpBasic(Customizer.withDefaults()); // 启用默认配置
+ return http.build();
+ }
+
+
+ @Bean
+ public AuthenticationProvider authenticationProvider() {
+ // 使用DaoAuthenticationProvider,并注入自定义的UserDetailsService和PasswordEncoder
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+ authProvider.setUserDetailsService(sysUserService); // 从数据库读取用户进行认证
+ authProvider.setPasswordEncoder(passwordEncoder()); // 使用BCrypt密码器验证密码
+ return authProvider;
+ }
+
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
+ // 从AuthenticationConfiguration中获取AuthenticationManager实例
+ return config.getAuthenticationManager();
+ }
+
+ @Bean
+ public PasswordEncoder passwordEncoder() {
+ // 使用BCryptPasswordEncoder作为密码加密器
+ return new BCryptPasswordEncoder();
+ }
+
+}
diff --git a/src/main/java/com/supervision/ai/service/hub/constant/UserConstant.java b/src/main/java/com/supervision/ai/service/hub/constant/UserConstant.java
new file mode 100644
index 0000000..cbe5431
--- /dev/null
+++ b/src/main/java/com/supervision/ai/service/hub/constant/UserConstant.java
@@ -0,0 +1,6 @@
+package com.supervision.ai.service.hub.constant;
+
+public class UserConstant {
+ public static final String USER_STATUS_ENABLED = "1"; // 用户状态启用
+ public static final String USER_STATUS_DISABLED = "0"; // 用户状态禁用
+}
diff --git a/src/main/java/com/supervision/ai/service/hub/controller/AuthController.java b/src/main/java/com/supervision/ai/service/hub/controller/AuthController.java
new file mode 100644
index 0000000..0765983
--- /dev/null
+++ b/src/main/java/com/supervision/ai/service/hub/controller/AuthController.java
@@ -0,0 +1,37 @@
+package com.supervision.ai.service.hub.controller;
+
+import com.supervision.ai.service.hub.service.impl.AuthService;
+import lombok.Data;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/auth")
+@RequiredArgsConstructor
+public class AuthController {
+
+ private final AuthService authService;
+
+ @PostMapping("/login")
+ public ResponseEntity> login(@RequestBody LoginRequest request) {
+ try {
+ String token = authService.login(request.getUsername(), request.getPassword());
+ Map response = new HashMap<>();
+ response.put("token", token);
+ return ResponseEntity.ok(response);
+ } catch (RuntimeException e) {
+ return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(e.getMessage());
+ }
+ }
+
+ @Data
+ public static class LoginRequest {
+ private String username;
+ private String password;
+ }
+}
diff --git a/src/main/java/com/supervision/ai/service/hub/controller/SysUserController.java b/src/main/java/com/supervision/ai/service/hub/controller/SysUserController.java
new file mode 100644
index 0000000..b034138
--- /dev/null
+++ b/src/main/java/com/supervision/ai/service/hub/controller/SysUserController.java
@@ -0,0 +1,39 @@
+package com.supervision.ai.service.hub.controller;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.supervision.ai.service.hub.domain.SysUser;
+import com.supervision.ai.service.hub.service.impl.SysUserService;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * 用户控制器,用于处理用户相关接口
+ */
+@RestController
+@RequestMapping("/user")
+@RequiredArgsConstructor
+public class SysUserController {
+
+ private final SysUserService sysUserService;
+
+ /**
+ * 查看当前用户详情接口
+ *
+ * @return 当前登录用户的详细信息,如果用户不存在,则返回 404
+ */
+ @GetMapping("/me")
+ public ResponseEntity> getCurrentUserDetails() {
+ Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+ String username = authentication.getName();
+ SysUser user = sysUserService.getOne(new LambdaQueryWrapper().eq(SysUser::getUsername, username));
+ if (user == null) {
+ return ResponseEntity.status(404).body("用户不存在");
+ }
+ return ResponseEntity.ok(user);
+ }
+}
diff --git a/src/main/java/com/supervision/ai/service/hub/domain/SysUser.java b/src/main/java/com/supervision/ai/service/hub/domain/SysUser.java
new file mode 100644
index 0000000..aff8073
--- /dev/null
+++ b/src/main/java/com/supervision/ai/service/hub/domain/SysUser.java
@@ -0,0 +1,16 @@
+package com.supervision.ai.service.hub.domain;
+
+import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.IdType;
+import lombok.Data;
+
+@Data
+@TableName("sys_user")
+public class SysUser {
+ @TableId(type = IdType.AUTO)
+ private Long id; // 主键ID
+ private String username; // 用户名
+ private String password; // 密码 (加密存储)
+ private String status; // 状态 (1表示正常,0表示禁用)
+}
diff --git a/src/main/java/com/supervision/ai/service/hub/filter/JwtAuthenticationFilter.java b/src/main/java/com/supervision/ai/service/hub/filter/JwtAuthenticationFilter.java
new file mode 100644
index 0000000..e2c6fa9
--- /dev/null
+++ b/src/main/java/com/supervision/ai/service/hub/filter/JwtAuthenticationFilter.java
@@ -0,0 +1,62 @@
+package com.supervision.ai.service.hub.filter;
+
+import com.supervision.ai.service.hub.service.impl.SysUserService;
+import com.supervision.ai.service.hub.util.JwtUtils;
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
+import org.springframework.stereotype.Component;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+import java.io.IOException;
+
+
+@Component
+@RequiredArgsConstructor
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+
+ private final JwtUtils jwtUtils;
+ private final SysUserService sysUserService;
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request, @NonNull HttpServletResponse response,
+ @NonNull FilterChain filterChain) throws ServletException, IOException {
+ String authHeader = request.getHeader("Authorization");
+ if (authHeader != null && authHeader.startsWith("Bearer ")) {
+ // 提取JWT Token(去掉前缀"Bearer ")
+ String token = authHeader.substring(7);
+ String username;
+ try {
+ // 从JWT中解析用户名
+ username = jwtUtils.getUsernameFromToken(token);
+ } catch (Exception e) {
+ // 如果JWT格式不正确或过期,直接放行(后续的过滤器会处理认证失败)
+ filterChain.doFilter(request, response);
+ return;
+ }
+ // 如果成功提取到用户名,并且当前没有已认证的用户
+ if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
+ // 根据用户名从数据库加载用户信息
+ UserDetails userDetails = sysUserService.loadUserByUsername(username);
+ // 验证Token的有效性(是否未过期,用户名是否匹配)
+ if (jwtUtils.validateToken(token, userDetails)) {
+ // 将用户信息封装到Authentication对象中,标记为已认证
+ UsernamePasswordAuthenticationToken authToken =
+ new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
+ authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
+ // 将Authentication对象放入SecurityContext,表示当前请求已通过认证
+ SecurityContextHolder.getContext().setAuthentication(authToken);
+ }
+ }
+ }
+ filterChain.doFilter(request, response);
+ }
+
+}
diff --git a/src/main/java/com/supervision/ai/service/hub/mapper/SysUserMapper.java b/src/main/java/com/supervision/ai/service/hub/mapper/SysUserMapper.java
new file mode 100644
index 0000000..f3e1a7f
--- /dev/null
+++ b/src/main/java/com/supervision/ai/service/hub/mapper/SysUserMapper.java
@@ -0,0 +1,10 @@
+package com.supervision.ai.service.hub.mapper;
+
+import com.supervision.ai.service.hub.domain.SysUser;
+import org.apache.ibatis.annotations.Mapper;
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+
+@Mapper
+public interface SysUserMapper extends BaseMapper {
+ // 继承BaseMapper后,CRUD方法可直接使用,无需额外定义
+}
\ No newline at end of file
diff --git a/src/main/java/com/supervision/ai/service/hub/service/impl/AuthService.java b/src/main/java/com/supervision/ai/service/hub/service/impl/AuthService.java
new file mode 100644
index 0000000..4ce4f1f
--- /dev/null
+++ b/src/main/java/com/supervision/ai/service/hub/service/impl/AuthService.java
@@ -0,0 +1,36 @@
+package com.supervision.ai.service.hub.service.impl;
+
+import com.supervision.ai.service.hub.util.JwtUtils;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.BadCredentialsException;
+import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Service;
+
+@Service
+@RequiredArgsConstructor
+public class AuthService {
+
+ private final AuthenticationManager authenticationManager;
+ private final JwtUtils jwtUtils;
+
+ /**
+ * 登录认证,返回JWT Token
+ */
+ public String login(String username, String password) {
+ try {
+ Authentication authentication = authenticationManager.authenticate(
+ new UsernamePasswordAuthenticationToken(username, password)
+ );
+ UserDetails userDetails = (UserDetails) authentication.getPrincipal();
+ return jwtUtils.generateToken(userDetails);
+ } catch (BadCredentialsException e) {
+ throw new RuntimeException("用户名或密码错误");
+ } catch (DisabledException e) {
+ throw new RuntimeException("用户已被禁用");
+ }
+ }
+}
diff --git a/src/main/java/com/supervision/ai/service/hub/service/impl/SysUserService.java b/src/main/java/com/supervision/ai/service/hub/service/impl/SysUserService.java
new file mode 100644
index 0000000..d0993de
--- /dev/null
+++ b/src/main/java/com/supervision/ai/service/hub/service/impl/SysUserService.java
@@ -0,0 +1,48 @@
+package com.supervision.ai.service.hub.service.impl;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.supervision.ai.service.hub.domain.SysUser;
+import com.supervision.ai.service.hub.mapper.SysUserMapper;
+import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.core.GrantedAuthority;
+import org.springframework.security.core.userdetails.User;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UserDetailsService;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
+import org.springframework.stereotype.Service;
+
+import java.util.Collections;
+import java.util.List;
+
+import static com.supervision.ai.service.hub.constant.UserConstant.USER_STATUS_DISABLED;
+import static com.supervision.ai.service.hub.constant.UserConstant.USER_STATUS_ENABLED;
+
+@Service
+public class SysUserService extends ServiceImpl implements UserDetailsService {
+
+ /**
+ * 根据用户名加载用户信息
+ * Spring Security 会调用该方法来获取用户信息
+ *
+ * @param username 用户名
+ * @return UserDetails
+ * @throws UsernameNotFoundException 用户名未找到异常
+ */
+ @Override
+ public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+ SysUser user = this.getOne(new LambdaQueryWrapper().eq(SysUser::getUsername, username));
+ if (user == null) {
+ throw new UsernameNotFoundException("用户不存在: " + username);
+ }
+ if (USER_STATUS_DISABLED.equals(user.getStatus())) {
+ throw new DisabledException("用户已被禁用");
+ }
+ // 将查询到的用户信息组装成UserDetails对象
+ // **扩展点**:如需加载用户角色权限,可在此处查询 sys_user_role 表关联的角色,并将角色加入 authorities 列表
+ List authorities = Collections.emptyList();
+ // 使用Spring Security提供的User对象作为UserDetails返回
+ return new User(user.getUsername(), user.getPassword(), authorities);
+ }
+
+}
diff --git a/src/main/java/com/supervision/ai/service/hub/util/JwtUtils.java b/src/main/java/com/supervision/ai/service/hub/util/JwtUtils.java
new file mode 100644
index 0000000..d0814af
--- /dev/null
+++ b/src/main/java/com/supervision/ai/service/hub/util/JwtUtils.java
@@ -0,0 +1,71 @@
+package com.supervision.ai.service.hub.util;
+
+
+import io.jsonwebtoken.Claims;
+import io.jsonwebtoken.Jws;
+import io.jsonwebtoken.Jwts;
+import io.jsonwebtoken.security.Keys;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.stereotype.Component;
+
+import javax.crypto.SecretKey;
+import java.util.Base64;
+import java.util.Date;
+
+@Component
+public class JwtUtils {
+
+ @Value("${jwt.secret}")
+ private String Secret;
+
+ @Value("${jwt.expiration}")
+ private long Expiration; // 1天
+
+ private SecretKey secretKey;
+
+ @PostConstruct
+ public void initKey() {
+ this.secretKey = Keys.hmacShaKeyFor(Base64.getDecoder().decode(Secret));
+ }
+
+ // 生成Token
+ public String generateToken(UserDetails userDetails) {
+ return Jwts.builder()
+ .subject(userDetails.getUsername())
+ .issuedAt(new Date())
+ .expiration(new Date(System.currentTimeMillis() + Expiration))
+ .signWith(secretKey, Jwts.SIG.HS256)
+ .compact();
+ }
+
+ // 从 Token 中解析用户名
+ public String getUsernameFromToken(String token) {
+ Jws jws = Jwts.parser()
+ .verifyWith(secretKey)
+ .build()
+ .parseSignedClaims(token);
+ return jws.getPayload().getSubject();
+ }
+
+
+ // 校验Token是否有效
+ public boolean validateToken(String token, UserDetails userDetails) {
+ try {
+ String username = getUsernameFromToken(token);
+ return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
+ } catch (Exception e) {
+ return false;
+ }
+ }
+
+ // 判断是否过期
+ private boolean isTokenExpired(String token) {
+ Jws jws = Jwts.parser()
+ .verifyWith(secretKey)
+ .build()
+ .parseSignedClaims(token);
+ return jws.getPayload().getExpiration().before(new Date());
+ }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 2d08bcd..34aa94c 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -17,6 +17,10 @@ mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
+jwt:
+ secret: "DlHaPUePiN6MyvpMpsMq/t6swzMHqtrRFd2YnofKz4k=" # JWT密钥 使用官方推荐方式生成 Base64.getEncoder().encodeToString(Keys.secretKeyFor(SignatureAlgorithm.HS256).getEncoded());
+ expiration: 3600000 # 1小时:3600000 1天:86400000
+
heygem:
server:
ip: 192.168.10.96