鉴权
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.
@@ -10,6 +10,7 @@ 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.annotation.web.configurers.AbstractHttpConfigurer;
|
||||
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||
import org.springframework.security.core.session.SessionRegistry;
|
||||
import org.springframework.security.core.session.SessionRegistryImpl;
|
||||
@@ -39,7 +40,7 @@ public class SecurityConfig {
|
||||
provider.setPasswordEncoder(passwordEncoder);
|
||||
|
||||
http
|
||||
.csrf(csrf -> csrf.disable())
|
||||
.csrf(AbstractHttpConfigurer::disable)
|
||||
.sessionManagement(session -> session
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||
.addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class)
|
||||
|
||||
@@ -15,6 +15,7 @@ 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.security.core.AuthenticationException;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@@ -41,7 +42,7 @@ public class AuthController {
|
||||
private static final String USER_TOKEN_PREFIX = "user:token:";
|
||||
|
||||
@PostMapping("/login")
|
||||
public ApiResponse<LoginResponse> login(@RequestBody LoginRequest request) {
|
||||
public ApiResponse login(@RequestBody LoginRequest request) {
|
||||
logger.info("用户登录");
|
||||
logger.info("用户名:{}", request.getUsername());
|
||||
logger.info("密码:{}", request.getPassword());
|
||||
@@ -49,28 +50,39 @@ public class AuthController {
|
||||
logger.info("生成的验证码:{}", request.getGeneratedCaptcha());
|
||||
// 1. 基础校验
|
||||
if (StringUtils.isBlank(request.getUsername()) || StringUtils.isBlank(request.getPassword())) {
|
||||
throw new RuntimeException("用户名或密码不能为空");
|
||||
logger.error("用户名或密码不能为空");
|
||||
return ApiResponse.badRequest();
|
||||
}
|
||||
|
||||
// 2. 验证码校验
|
||||
if (!captchaService.validateCaptcha(request.getCaptcha(), request.getGeneratedCaptcha())) {
|
||||
throw new RuntimeException("验证码错误");
|
||||
logger.error("验证码错误");
|
||||
return ApiResponse.badRequest();
|
||||
}
|
||||
// 在验证码校验后、认证前添加防爆破逻辑
|
||||
// 在验证码校验后、认证前添加防爆破逻辑
|
||||
String loginFailKey = "login:fail:" + request.getUsername();
|
||||
Long failCount = redisTemplate.opsForValue().increment(loginFailKey);
|
||||
if (failCount != null && failCount > 5) {
|
||||
throw new RuntimeException("尝试次数过多,请稍后再试");
|
||||
logger.error("尝试次数过多,请稍后再试");
|
||||
return ApiResponse.badRequest();
|
||||
}else {
|
||||
redisTemplate.expire(loginFailKey, 5, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
// 3. 用户认证(Spring Security 标准流程)
|
||||
Authentication authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
|
||||
logger.info("3. 用户认证(Spring Security 标准流程)");
|
||||
try {
|
||||
Authentication authentication = authenticationManager.authenticate(
|
||||
new UsernamePasswordAuthenticationToken(request.getUsername(), request.getPassword()));
|
||||
} catch (Exception e) {
|
||||
logger.error("用户认证失败:{}", e.getMessage());
|
||||
}
|
||||
|
||||
// 4. 生成 JWT Token
|
||||
logger.info("用户认证成功");
|
||||
String token = jwtUtils.generateToken(request.getUsername());
|
||||
String refreshToken = generateRefreshToken(request.getUsername());
|
||||
logger.info("生成 JWT Token:{}", token);
|
||||
|
||||
// 5. 存入 Redis
|
||||
saveUserToken(request.getUsername(), token, jwtUtils.getExpiration(token));
|
||||
|
||||
@@ -31,6 +31,7 @@ public class JwtAuthFilter extends OncePerRequestFilter {
|
||||
@NonNull FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
try {
|
||||
logger.debug("JwtAuthFilter.doFilterInternal");
|
||||
String jwt = parseJwt(request);
|
||||
if (jwt != null && jwtUtils.validateToken(jwt)) {
|
||||
String username = jwtUtils.extractUsername(jwt);
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package cn.van.business.service;
|
||||
|
||||
import cn.hutool.crypto.digest.MD5;
|
||||
import cn.van.business.model.user.AdminUser;
|
||||
import cn.van.business.util.Util;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@@ -19,7 +21,8 @@ public class AdminUserService {
|
||||
// 初始化一个测试用户(生产环境应从数据库加载)
|
||||
AdminUser user = new AdminUser();
|
||||
user.setUsername("van");
|
||||
user.setPassword(new BCryptPasswordEncoder().encode("LK.807878712"));
|
||||
String password = Util.md5("LK.807878712");
|
||||
user.setPassword(new BCryptPasswordEncoder().encode(password));
|
||||
users.put("van", user);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import java.util.Date;
|
||||
@Component
|
||||
public class JwtUtils {
|
||||
|
||||
private final String secret = "your-secret-key"; // 应配置在 application.yml 中
|
||||
private final String secret = "7b78f5cc9735091442361c78b863607d"; // 应配置在 application.yml 中
|
||||
private final long expiration = 86400000 * 7; // 24小时
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,8 @@ import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
@@ -23,6 +25,36 @@ Util {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(Util.class);
|
||||
|
||||
/**
|
||||
* 将字符串转换为 MD5 摘要
|
||||
*
|
||||
* @param input 原始字符串
|
||||
* @return MD5 加密后的十六进制字符串
|
||||
*/
|
||||
public static String md5(String input) {
|
||||
try {
|
||||
// 创建 MessageDigest 实例,指定 MD5 算法
|
||||
MessageDigest md = MessageDigest.getInstance("MD5");
|
||||
|
||||
// 将输入字符串转换为字节数组并进行哈希计算
|
||||
byte[] messageDigest = md.digest(input.getBytes());
|
||||
|
||||
// 将字节数组转换为十六进制字符串
|
||||
StringBuilder hexString = new StringBuilder();
|
||||
for (byte b : messageDigest) {
|
||||
String hex = Integer.toHexString(0xff & b);
|
||||
if (hex.length() == 1)
|
||||
hexString.append('0'); // 补零
|
||||
hexString.append(hex);
|
||||
}
|
||||
|
||||
return hexString.toString();
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("MD5加密失败", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* byte数组倒序
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user