鉴权
This commit is contained in:
BIN
.idea/.cache/.Apifox_Helper/.toolWindow.db
generated
BIN
.idea/.cache/.Apifox_Helper/.toolWindow.db
generated
Binary file not shown.
9
.idea/ApifoxUploaderProjectSetting.xml
generated
9
.idea/ApifoxUploaderProjectSetting.xml
generated
File diff suppressed because one or more lines are too long
31
pom.xml
31
pom.xml
@@ -90,7 +90,11 @@
|
||||
<artifactId>jdk</artifactId>
|
||||
<version>3.0</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.penggle</groupId>
|
||||
<artifactId>kaptcha</artifactId>
|
||||
<version>2.3.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.codehaus.jackson</groupId>
|
||||
<artifactId>jackson-mapper-asl</artifactId>
|
||||
@@ -102,6 +106,23 @@
|
||||
<artifactId>jackson-core-asl</artifactId>
|
||||
<version>1.9.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-api</artifactId>
|
||||
<version>0.11.5</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-impl</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.jsonwebtoken</groupId>
|
||||
<artifactId>jjwt-jackson</artifactId>
|
||||
<version>0.11.5</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Actuator -->
|
||||
<dependency>
|
||||
@@ -119,6 +140,10 @@
|
||||
<artifactId>httpclient</artifactId>
|
||||
<version>4.5.13</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-security</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
@@ -148,13 +173,13 @@
|
||||
<repository>
|
||||
<id>maven-local88</id>
|
||||
<name>Local Repository</name>
|
||||
<url>http://192.168.8.88:8081/repository/maven-local88/</url>
|
||||
<url>http://134.175.126.60:38081/repository/maven-local88/</url>
|
||||
<releases>
|
||||
<enabled>true</enabled>
|
||||
<updatePolicy>always</updatePolicy>
|
||||
</releases>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
<!-- RocketMQ 官方仓库 -->
|
||||
|
||||
27
src/main/java/cn/van/business/config/KaptchaConfig.java
Normal file
27
src/main/java/cn/van/business/config/KaptchaConfig.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package cn.van.business.config;
|
||||
|
||||
import com.google.code.kaptcha.impl.DefaultKaptcha;
|
||||
import com.google.code.kaptcha.util.Config;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
import java.util.Properties;
|
||||
|
||||
@Configuration
|
||||
public class KaptchaConfig {
|
||||
|
||||
@Bean
|
||||
public DefaultKaptcha kaptchaProducer() {
|
||||
DefaultKaptcha kaptcha = new DefaultKaptcha();
|
||||
Properties properties = new Properties();
|
||||
|
||||
// 设置验证码参数
|
||||
properties.setProperty("kaptcha.image.width", "150");
|
||||
properties.setProperty("kaptcha.image.height", "50");
|
||||
properties.setProperty("kaptcha.textproducer.char.string", "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ");
|
||||
properties.setProperty("kaptcha.textproducer.char.length", "4");
|
||||
|
||||
kaptcha.setConfig(new Config(properties));
|
||||
return kaptcha;
|
||||
}
|
||||
}
|
||||
74
src/main/java/cn/van/business/config/SecurityConfig.java
Normal file
74
src/main/java/cn/van/business/config/SecurityConfig.java
Normal file
@@ -0,0 +1,74 @@
|
||||
package cn.van.business.config;
|
||||
|
||||
import cn.van.business.filter.JwtAuthFilter;
|
||||
import cn.van.business.service.impl.UserDetailsServiceImpl;
|
||||
import cn.van.business.util.JwtUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
|
||||
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.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.core.session.SessionRegistryImpl;
|
||||
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;
|
||||
import org.springframework.security.web.session.HttpSessionEventPublisher;
|
||||
|
||||
@Configuration
|
||||
@EnableWebSecurity
|
||||
public class SecurityConfig {
|
||||
|
||||
private final UserDetailsServiceImpl userDetailsService;
|
||||
private final JwtAuthFilter jwtAuthFilter;
|
||||
|
||||
public SecurityConfig(UserDetailsServiceImpl userDetailsService, JwtAuthFilter jwtAuthFilter) {
|
||||
this.userDetailsService = userDetailsService;
|
||||
this.jwtAuthFilter = jwtAuthFilter;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SecurityFilterChain filterChain(HttpSecurity http,
|
||||
PasswordEncoder passwordEncoder) throws Exception {
|
||||
DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
|
||||
provider.setUserDetailsService(userDetailsService);
|
||||
provider.setPasswordEncoder(passwordEncoder);
|
||||
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
.authenticationProvider(provider)
|
||||
.authorizeHttpRequests(auth -> auth
|
||||
.requestMatchers("/login", "/captcha").permitAll()
|
||||
.anyRequest().authenticated());
|
||||
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public PasswordEncoder passwordEncoder() {
|
||||
return new BCryptPasswordEncoder();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
|
||||
return config.getAuthenticationManager();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public SessionRegistry sessionRegistry() {
|
||||
return new SessionRegistryImpl();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HttpSessionEventPublisher httpSessionEventPublisher() {
|
||||
return new HttpSessionEventPublisher();
|
||||
}
|
||||
|
||||
}
|
||||
67
src/main/java/cn/van/business/controller/BaseController.java
Normal file
67
src/main/java/cn/van/business/controller/BaseController.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package cn.van.business.controller;
|
||||
|
||||
import cn.van.business.model.user.UserInfo;
|
||||
import cn.van.business.util.JwtUtils;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.servlet.ModelAndView;
|
||||
import org.springframework.web.servlet.mvc.AbstractController;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class BaseController extends AbstractController {
|
||||
|
||||
@Autowired
|
||||
private JwtUtils jwtUtils;
|
||||
|
||||
// 获取当前用户信息
|
||||
protected UserInfo getCurrentUser(HttpServletRequest request) {
|
||||
String token = extractToken(request);
|
||||
if (token != null && jwtUtils.validateToken(token)) {
|
||||
String username = jwtUtils.extractUsername(token);
|
||||
// 从数据库获取用户信息
|
||||
return loadUserInfo(username);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 检查用户是否已登录
|
||||
protected boolean isLogin(HttpServletRequest request) {
|
||||
return getCurrentUser(request) != null;
|
||||
}
|
||||
|
||||
// 获取当前用户ID
|
||||
protected String getCurrentUserId(HttpServletRequest request) {
|
||||
UserInfo user = getCurrentUser(request);
|
||||
return user != null ? user.getId() : null;
|
||||
}
|
||||
|
||||
// 获取当前用户角色
|
||||
protected List<String> getCurrentUserRoles(HttpServletRequest request) {
|
||||
UserInfo user = getCurrentUser(request);
|
||||
return user != null ? user.getRoles() : Collections.emptyList();
|
||||
}
|
||||
|
||||
// 提取token
|
||||
private String extractToken(HttpServletRequest request) {
|
||||
String bearerToken = request.getHeader("Authorization");
|
||||
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
|
||||
return bearerToken.substring(7);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 加载用户信息
|
||||
private UserInfo loadUserInfo(String username) {
|
||||
// 实现用户信息加载
|
||||
return new UserInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package cn.van.business.controller.jd;
|
||||
|
||||
import cn.van.business.filter.RequireAuth;
|
||||
import cn.van.business.model.ApiResponse;
|
||||
import cn.van.business.model.jd.ProductOrder;
|
||||
import cn.van.business.mq.MessageProducerService;
|
||||
@@ -21,35 +22,18 @@ import java.util.List;
|
||||
*/
|
||||
@RestController
|
||||
@RequestMapping("/recordOrder")
|
||||
@RequireAuth
|
||||
public class OrderController {
|
||||
|
||||
public static String TOKEN = "dd7f298cc465e7a9791796ae1303a1f6c031a18d4894ecd9e75d44fdb0ef32d80a49e4c5811b98d8ac65d73d22a54083630d5329c7c9de2276317669ecb2e544";
|
||||
@Autowired
|
||||
private JDUtil jdUtils;
|
||||
@Autowired
|
||||
private MessageProducerService messageProducerService;
|
||||
@Autowired
|
||||
private ProductOrderRepository productOrderRepository;
|
||||
|
||||
public boolean checkToken(String token) {
|
||||
return TOKEN.equals(token);
|
||||
}
|
||||
|
||||
@RequestMapping("/testToken")
|
||||
@ResponseBody
|
||||
public ApiResponse testToken(String token) {
|
||||
if (!checkToken(token)){
|
||||
return ApiResponse.unauthorized();
|
||||
}
|
||||
return ApiResponse.success();
|
||||
}
|
||||
|
||||
@RequestMapping("/list")
|
||||
@ResponseBody
|
||||
public ApiResponse list(String token) throws Exception {
|
||||
if (!checkToken(token)){
|
||||
return ApiResponse.unauthorized();
|
||||
}
|
||||
public ApiResponse list() throws Exception {
|
||||
try {
|
||||
|
||||
List<ProductOrder> all = productOrderRepository.findAll();
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
package cn.van.business.controller.login;
|
||||
|
||||
import cn.van.business.model.user.LoginRequest;
|
||||
import cn.van.business.model.user.LoginResponse;
|
||||
import cn.van.business.service.CaptchaService;
|
||||
import cn.van.business.util.JwtUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/api/auth")
|
||||
public class AuthController {
|
||||
|
||||
private final CaptchaService captchaService;
|
||||
private final JwtUtils jwtUtils;
|
||||
private final AuthenticationManager authenticationManager;
|
||||
private final StringRedisTemplate redisTemplate;
|
||||
|
||||
public AuthController(CaptchaService captchaService, JwtUtils jwtUtils, AuthenticationManager authenticationManager, StringRedisTemplate redisTemplate) {
|
||||
this.captchaService = captchaService;
|
||||
this.jwtUtils = jwtUtils;
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.redisTemplate = redisTemplate;
|
||||
}
|
||||
|
||||
private static final String USER_TOKEN_PREFIX = "user:token:";
|
||||
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request) {
|
||||
// 1. 基础校验
|
||||
if (StringUtils.isBlank(request.getUsername()) || StringUtils.isBlank(request.getPassword())) {
|
||||
throw new RuntimeException("用户名或密码不能为空");
|
||||
}
|
||||
|
||||
// 2. 验证码校验
|
||||
if (!captchaService.validateCaptcha(request.getCaptcha(), request.getGeneratedCaptcha())) {
|
||||
throw new RuntimeException("验证码错误");
|
||||
}
|
||||
// 在验证码校验后、认证前添加防爆破逻辑
|
||||
String loginFailKey = "login:fail:" + request.getUsername();
|
||||
Long failCount = redisTemplate.opsForValue().increment(loginFailKey);
|
||||
if (failCount != null && failCount > 5) {
|
||||
throw new RuntimeException("尝试次数过多,请稍后再试");
|
||||
}
|
||||
|
||||
// 3. 用户认证(Spring Security 标准流程)
|
||||
Authentication authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
|
||||
|
||||
// 4. 生成 JWT Token
|
||||
String token = jwtUtils.generateToken(request.getUsername());
|
||||
String refreshToken = generateRefreshToken(request.getUsername());
|
||||
|
||||
// 5. 存入 Redis
|
||||
saveUserToken(request.getUsername(), token, jwtUtils.getExpiration(token));
|
||||
|
||||
// 6. 构造响应
|
||||
LoginResponse response = new LoginResponse();
|
||||
response.setToken(token);
|
||||
response.setRefreshToken(refreshToken);
|
||||
response.setUsername(request.getUsername());
|
||||
|
||||
return ResponseEntity.ok(response);
|
||||
}
|
||||
|
||||
|
||||
@PostMapping("/logout")
|
||||
public ResponseEntity<Void> logout(@RequestHeader("Authorization") String token) {
|
||||
String actualToken = token.startsWith("Bearer ") ? token.substring(7) : token;
|
||||
String username = jwtUtils.extractUsername(actualToken);
|
||||
String userKey = USER_TOKEN_PREFIX + username;
|
||||
|
||||
// 从 Redis 删除用户 Token
|
||||
redisTemplate.delete(userKey);
|
||||
|
||||
// 添加 Token 到黑名单
|
||||
addToBlacklist(actualToken, jwtUtils.getRemainingExpirationTime(actualToken));
|
||||
|
||||
return ResponseEntity.ok().build();
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("/captcha")
|
||||
public ResponseEntity<String> getCaptcha() throws Exception {
|
||||
String captchaImage = captchaService.generateCaptchaImage();
|
||||
return ResponseEntity.ok(captchaImage);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将用户 Token 存入 Redis,并设置相同有效期
|
||||
*/
|
||||
private void saveUserToken(String username, String token, long expiration) {
|
||||
String key = USER_TOKEN_PREFIX + username;
|
||||
redisTemplate.opsForValue().set(key, token, expiration, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 将 Token 加入黑名单
|
||||
*/
|
||||
private void addToBlacklist(String token, long remainingTime) {
|
||||
String blacklistKey = "blacklist:token:" + token;
|
||||
redisTemplate.opsForValue().set(blacklistKey, "invalid", remainingTime, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成刷新 Token(Refresh Token)
|
||||
*/
|
||||
private String generateRefreshToken(String username) {
|
||||
return jwtUtils.generateToken(username);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package cn.van.business.controller.login;
|
||||
|
||||
import cn.van.business.service.CaptchaService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/captcha")
|
||||
public class CaptchaController {
|
||||
|
||||
private final CaptchaService captchaService;
|
||||
|
||||
public CaptchaController(CaptchaService captchaService) {
|
||||
this.captchaService = captchaService;
|
||||
}
|
||||
|
||||
@GetMapping("/generate")
|
||||
public ResponseEntity<String> generateCaptcha() throws Exception {
|
||||
String base64Image = captchaService.generateCaptchaImage();
|
||||
return ResponseEntity.ok(base64Image);
|
||||
}
|
||||
}
|
||||
62
src/main/java/cn/van/business/filter/JwtAuthFilter.java
Normal file
62
src/main/java/cn/van/business/filter/JwtAuthFilter.java
Normal file
@@ -0,0 +1,62 @@
|
||||
package cn.van.business.filter;
|
||||
|
||||
import cn.van.business.service.impl.UserDetailsServiceImpl;
|
||||
import cn.van.business.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.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class JwtAuthFilter extends OncePerRequestFilter {
|
||||
|
||||
private final JwtUtils jwtUtils;
|
||||
private final UserDetailsServiceImpl userDetailsService;
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
try {
|
||||
String jwt = parseJwt(request);
|
||||
if (jwt != null && jwtUtils.validateToken(jwt)) {
|
||||
String username = jwtUtils.extractUsername(jwt);
|
||||
|
||||
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
|
||||
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
|
||||
userDetails, null, userDetails.getAuthorities());
|
||||
|
||||
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
logger.error("无法认证用户: {}");
|
||||
}
|
||||
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
|
||||
private String parseJwt(HttpServletRequest request) {
|
||||
String headerAuth = request.getHeader("Authorization");
|
||||
|
||||
if (StringUtils.hasText(headerAuth) && headerAuth.startsWith("Bearer ")) {
|
||||
return headerAuth.substring(7);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
13
src/main/java/cn/van/business/filter/RequireAuth.java
Normal file
13
src/main/java/cn/van/business/filter/RequireAuth.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package cn.van.business.filter;
|
||||
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@PreAuthorize("hasAnyRole('ADMIN', 'USER')")
|
||||
public @interface RequireAuth {}
|
||||
46
src/main/java/cn/van/business/model/user/AdminUser.java
Normal file
46
src/main/java/cn/van/business/model/user/AdminUser.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package cn.van.business.model.user;
|
||||
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.GenerationType;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import jakarta.persistence.*;
|
||||
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author Leo
|
||||
* @version 1.0
|
||||
* @create 2025/6/16 10:55
|
||||
* @description:
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@Entity
|
||||
@Table(name = "admin_users")
|
||||
public class AdminUser {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(unique = true, nullable = false)
|
||||
private String username;
|
||||
|
||||
private String password;
|
||||
|
||||
private String nickname;
|
||||
|
||||
private String avatarUrl;
|
||||
|
||||
private String roles; // 角色列表,可用逗号分隔
|
||||
|
||||
private LocalDateTime lastLoginTime;
|
||||
|
||||
private LocalDateTime createTime;
|
||||
|
||||
private LocalDateTime updateTime;
|
||||
}
|
||||
|
||||
13
src/main/java/cn/van/business/model/user/LoginRequest.java
Normal file
13
src/main/java/cn/van/business/model/user/LoginRequest.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package cn.van.business.model.user;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class LoginRequest {
|
||||
private String username;
|
||||
private String password;
|
||||
private String captcha; // 验证码字段(如果需要)
|
||||
private String generatedCaptcha;
|
||||
}
|
||||
14
src/main/java/cn/van/business/model/user/LoginResponse.java
Normal file
14
src/main/java/cn/van/business/model/user/LoginResponse.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package cn.van.business.model.user;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class LoginResponse {
|
||||
private String token;
|
||||
private Long expiresIn;
|
||||
private String refreshToken;
|
||||
private UserInfo userInfo;
|
||||
private String username;
|
||||
}
|
||||
16
src/main/java/cn/van/business/model/user/UserInfo.java
Normal file
16
src/main/java/cn/van/business/model/user/UserInfo.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package cn.van.business.model.user;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class UserInfo {
|
||||
private String username;
|
||||
private String nickname;
|
||||
private String id;
|
||||
private List<String> roles;
|
||||
private String avatarUrl;
|
||||
}
|
||||
29
src/main/java/cn/van/business/service/AdminUserService.java
Normal file
29
src/main/java/cn/van/business/service/AdminUserService.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package cn.van.business.service;
|
||||
|
||||
import cn.van.business.model.user.AdminUser;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@Service
|
||||
public class AdminUserService {
|
||||
|
||||
|
||||
// 模拟数据库存储
|
||||
private static final Map<String, AdminUser> users = new HashMap<>();
|
||||
|
||||
static {
|
||||
// 初始化一个测试用户(生产环境应从数据库加载)
|
||||
AdminUser user = new AdminUser();
|
||||
user.setUsername("van");
|
||||
user.setPassword(new BCryptPasswordEncoder().encode("LK.807878712"));
|
||||
users.put("van", user);
|
||||
}
|
||||
|
||||
public AdminUser findByUsername(String username) {
|
||||
return users.get(username);
|
||||
}
|
||||
}
|
||||
32
src/main/java/cn/van/business/service/CaptchaService.java
Normal file
32
src/main/java/cn/van/business/service/CaptchaService.java
Normal file
@@ -0,0 +1,32 @@
|
||||
package cn.van.business.service;
|
||||
|
||||
import com.google.code.kaptcha.Producer;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.imageio.ImageIO;
|
||||
import java.awt.image.BufferedImage;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.util.Base64;
|
||||
|
||||
@Service
|
||||
public class CaptchaService {
|
||||
|
||||
private final Producer kaptchaProducer;
|
||||
|
||||
public CaptchaService(Producer kaptchaProducer) {
|
||||
this.kaptchaProducer = kaptchaProducer;
|
||||
}
|
||||
|
||||
public String generateCaptchaImage() throws Exception {
|
||||
String text = kaptchaProducer.createText();
|
||||
BufferedImage image = kaptchaProducer.createImage(text);
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
ImageIO.write(image, "PNG", outputStream);
|
||||
return Base64.getEncoder().encodeToString(outputStream.toByteArray());
|
||||
}
|
||||
|
||||
public boolean validateCaptcha(String userInput, String generatedCaptcha) {
|
||||
return userInput.equalsIgnoreCase(generatedCaptcha);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package cn.van.business.service.impl;
|
||||
|
||||
import cn.van.business.model.user.AdminUser;
|
||||
import cn.van.business.service.AdminUserService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
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.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
@Service
|
||||
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||
|
||||
@Autowired
|
||||
private AdminUserService adminUserService;
|
||||
|
||||
@Override
|
||||
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
|
||||
AdminUser user = adminUserService.findByUsername(username);
|
||||
if (user == null) {
|
||||
throw new UsernameNotFoundException("用户不存在");
|
||||
}
|
||||
|
||||
return User.builder()
|
||||
.username(user.getUsername())
|
||||
.password(user.getPassword())
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
18
src/main/java/cn/van/business/util/AuthEntryPoint.java
Normal file
18
src/main/java/cn/van/business/util/AuthEntryPoint.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package cn.van.business.util;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.AuthenticationEntryPoint;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class AuthEntryPoint implements AuthenticationEntryPoint {
|
||||
|
||||
@Override
|
||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException {
|
||||
response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "需要登录认证");
|
||||
}
|
||||
}
|
||||
@@ -2058,6 +2058,9 @@ public class JDUtil {
|
||||
|
||||
public LocalDate getDateFromLD(String dateStr) {
|
||||
logger.info("开始解析日期 {}", dateStr);
|
||||
if (dateStr.equals("昨天")){
|
||||
return getEffectiveToday().minusDays(1).toLocalDate();
|
||||
}
|
||||
// 定义支持的日期格式
|
||||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMdd");
|
||||
|
||||
|
||||
107
src/main/java/cn/van/business/util/JwtUtils.java
Normal file
107
src/main/java/cn/van/business/util/JwtUtils.java
Normal file
@@ -0,0 +1,107 @@
|
||||
package cn.van.business.util;
|
||||
|
||||
import io.jsonwebtoken.Jwts;
|
||||
import io.jsonwebtoken.SignatureAlgorithm;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* JWT 工具类
|
||||
* 提供 token 的生成、解析、验证和有效期管理功能
|
||||
*/
|
||||
@Component
|
||||
public class JwtUtils {
|
||||
|
||||
private final String secret = "your-secret-key"; // 应配置在 application.yml 中
|
||||
private final long expiration = 86400000 * 7; // 24小时
|
||||
|
||||
/**
|
||||
* 生成一个新的 JWT Token
|
||||
*
|
||||
* @param username 用户名
|
||||
* @param roles 用户角色(可选)
|
||||
* @return 返回生成的 JWT Token 字符串
|
||||
*/
|
||||
// 生成 Token 时去掉 roles 参数
|
||||
public String generateToken(String username) {
|
||||
return Jwts.builder()
|
||||
.setSubject(username)
|
||||
.setExpiration(new Date(System.currentTimeMillis() + expiration))
|
||||
.signWith(SignatureAlgorithm.HS512, secret)
|
||||
.compact();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 从给定的 JWT Token 中提取用户名
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 提取的用户名
|
||||
*/
|
||||
public String extractUsername(String token) {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(secret)
|
||||
.parseClaimsJws(token)
|
||||
.getBody()
|
||||
.getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 JWT Token 的过期时间
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 返回 token 的过期时间 (Date)
|
||||
*/
|
||||
public Date extractExpiration(String token) {
|
||||
return Jwts.parser()
|
||||
.setSigningKey(secret)
|
||||
.parseClaimsJws(token)
|
||||
.getBody()
|
||||
.getExpiration();
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证指定的 JWT Token 是否有效
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 如果 token 有效则返回 true,否则返回 false
|
||||
*/
|
||||
public Boolean validateToken(String token) {
|
||||
return !isTokenExpired(token);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 检查 token 是否已过期
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 如果已过期返回 true,否则返回 false
|
||||
*/
|
||||
private Boolean isTokenExpired(String token) {
|
||||
return extractExpiration(token).before(new Date());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 JWT Token 的剩余有效时间(毫秒)
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 剩余的有效时间(毫秒)
|
||||
*/
|
||||
public long getRemainingExpirationTime(String token) {
|
||||
long now = System.currentTimeMillis();
|
||||
return Math.max(0, extractExpiration(token).getTime() - now);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 JWT Token 的过期时间(毫秒)
|
||||
*
|
||||
* @param token JWT Token
|
||||
* @return 返回 token 的过期时间戳(毫秒)
|
||||
*/
|
||||
public long getExpiration(String token) {
|
||||
return extractExpiration(token).getTime();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@ spring:
|
||||
#数据源配置
|
||||
datasource:
|
||||
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||
url: jdbc:mysql://192.168.8.88:3306/jd?characterEncoding=utf-8&useSSL=true&serverTimezone=GMT%2B8
|
||||
url: jdbc:mysql://134.175.126.60:33306/jd?characterEncoding=utf-8&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: mysql_7sjTXH
|
||||
hikari:
|
||||
@@ -38,10 +38,10 @@ spring:
|
||||
basename: i18n/messages
|
||||
data:
|
||||
redis:
|
||||
host: 192.168.8.88
|
||||
host: 134.175.126.60
|
||||
password: redis_6PZ52S
|
||||
timeout: 1800000
|
||||
port: 6379
|
||||
port: 36379
|
||||
database: 7
|
||||
|
||||
# 日志配置
|
||||
@@ -53,7 +53,7 @@ config:
|
||||
WX_BASE_URL: http://192.168.8.6:7777/qianxun/httpapi?wxid=wxid_kr145nk7l0an31
|
||||
QL_BASE_URL: http://134.175.126.60:35700
|
||||
rocketmq:
|
||||
name-server: 192.168.8.88:9876 # RocketMQ Name Server 地址
|
||||
name-server: 192.168.8.88:39876 # RocketMQ Name Server 地址
|
||||
producer:
|
||||
group: wx_producer # 生产者组名
|
||||
send-msg-timeout: 1000 # 发送消息超时时间
|
||||
@@ -63,5 +63,5 @@ rocketmq:
|
||||
consume-thread-max: 64 # 消费线程池最大线程数
|
||||
consume-message-batch-max-size: 64 # 批量消费最大消息数
|
||||
isRunning:
|
||||
wx: true
|
||||
jd: true
|
||||
wx: false
|
||||
jd: false
|
||||
|
||||
Reference in New Issue
Block a user