tsurumure vor 9 Monaten
Ursprung
Commit
cd472efe0a

+ 30 - 0
src/main/java/com/backendsys/modules/common/config/security/utils/CaptchaUtil.java

@@ -0,0 +1,30 @@
+package com.backendsys.modules.common.config.security.utils;
+
+import com.backendsys.modules.common.config.redis.utils.RedisUtil;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.env.Environment;
+import org.springframework.stereotype.Component;
+
+@Component
+public class CaptchaUtil {
+
+    @Autowired
+    private Environment env;
+
+    @Autowired
+    private RedisUtil redisUtil;
+
+    public Boolean isCaptchaValid(String captcha, String captchaRedisKey) {
+        // 如果不是本地开发环境,则执行以下判断
+        String profileActive = env.getProperty("spring.profiles.active");
+        if (!("local".equals(profileActive))) {
+            // 判断验证码是否正确 (是否与Redis中的验证码匹配) (测试环境忽略)
+            String captchaRedisValue = redisUtil.getCacheObject(captchaRedisKey);
+            if (captchaRedisValue == null || !captchaRedisValue.equalsIgnoreCase(captcha)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}

+ 53 - 0
src/main/java/com/backendsys/modules/common/config/security/utils/CountUtilV2.java

@@ -0,0 +1,53 @@
+package com.backendsys.modules.common.config.security.utils;
+
+import com.backendsys.exception.CustException;
+import com.backendsys.modules.common.config.redis.utils.RedisUtil;
+import com.backendsys.utils.response.ResultEnum;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+
+import java.util.concurrent.TimeUnit;
+
+@Component
+public class CountUtilV2 {
+
+//    @Autowired
+//    private StringRedisTemplate stringRedisTemplate;
+
+    @Autowired
+    private RedisUtil redisUtil;
+
+    /**
+     * 判断 2分钟内错误 5次,则出现提示
+     */
+    public void setErrorCount(String key, String tag) {
+        Integer timeout = 2;
+        String errKey = key + "-" + tag;
+//        String errValue = stringRedisTemplate.opsForValue().get(errKey);
+        String errValue = redisUtil.getCacheObject(errKey);
+
+        if (errValue == null) {
+            errValue = "1";
+        } else if (Integer.valueOf(errValue) >= 5) {
+            throw new CustException("错误次数过多,为账号安全,请等待" + timeout + "分钟后重新尝试", ResultEnum.LOCK_CREDENTIALS.getCode());
+        } else {
+            errValue = String.valueOf((Integer.valueOf(errValue) + 1));
+        }
+//        stringRedisTemplate.opsForValue().set(errKey, errValue, timeout, TimeUnit.MINUTES);
+        redisUtil.setCacheObject(errKey, errValue, timeout, TimeUnit.MINUTES);
+
+    }
+    /**
+     * 判断是否处于 5次的错误状态
+     */
+    public void checkErrorStatus(String key, String tag) {
+        Integer timeout = 2;
+        String errKey = key + "-" + tag;
+//        String errValue = stringRedisTemplate.opsForValue().get(errKey);
+        String errValue = redisUtil.getCacheObject(errKey);
+        if (errValue != null && Integer.valueOf(errValue) >= 5) {
+            throw new CustException("错误次数过多,为账号安全,请等待" + timeout + "分钟后重新尝试", ResultEnum.LOCK_CREDENTIALS.getCode());
+        }
+    }
+
+}

+ 107 - 0
src/main/java/com/backendsys/modules/common/config/security/utils/HttpRequestUtil.java

@@ -0,0 +1,107 @@
+package com.backendsys.modules.common.config.security.utils;
+
+import cn.hutool.core.convert.Convert;
+import com.backendsys.utils.CommonUtil;
+import io.jsonwebtoken.Claims;
+import jakarta.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Component;
+import org.springframework.web.context.request.RequestContextHolder;
+import org.springframework.web.context.request.ServletRequestAttributes;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+@Component
+public class HttpRequestUtil {
+
+    @Autowired
+    private TokenUtil tokenUtil;
+
+    /**
+     * 获取HttpServletRequest 对象
+     */
+    private HttpServletRequest getRequest() {
+        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
+        HttpServletRequest request = attributes != null ? attributes.getRequest() : null;
+        return request;
+    }
+
+    /**
+     * 从 Token 中获得 user_id
+     */
+    public Long getUserId() {
+        HttpServletRequest request = getRequest();
+        if (request != null) {
+            Claims tokenInfo = tokenUtil.getTokenInfo(request);
+            if (tokenInfo != null) {
+                return Convert.toLong(tokenInfo.get("user_id"));
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 从 Token 中获得 member_id
+     */
+    public Long getMemberId() {
+        HttpServletRequest request = getRequest();
+        if (request != null) {
+            Claims tokenInfo = tokenUtil.getTokenInfo(request);
+            if (tokenInfo != null) {
+                return Convert.toLong(tokenInfo.get("member_id"));
+            }
+        }
+        return null;
+    }
+
+    public String getIpAddr() {
+        HttpServletRequest request = getRequest();
+        String ipAddress = null;
+        try {
+            ipAddress = request.getHeader("x-forwarded-for");
+            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+                ipAddress = request.getHeader("Proxy-Client-IP");
+            }
+            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+                ipAddress = request.getHeader("WL-Proxy-Client-IP");
+            }
+            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
+                ipAddress = request.getRemoteAddr();
+                if (ipAddress.equals("127.0.0.1")) {
+                    // 根据网卡取本机配置的IP
+                    InetAddress inet = null;
+                    try {
+                        inet = InetAddress.getLocalHost();
+                    } catch (UnknownHostException e) {
+                        e.printStackTrace();
+                    }
+                    ipAddress = inet.getHostAddress();
+                }
+            }
+            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
+            if (ipAddress != null && ipAddress.length() > 15) {
+                // "***.***.***.***".length()
+                // = 15
+                if (ipAddress.indexOf(",") > 0) {
+                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
+                }
+            }
+        } catch (Exception e) {
+            ipAddress = "";
+        }
+        return ipAddress;
+    }
+
+    /**
+     * 获得验证码 RedisKey (ua + ip)
+     */
+    public String getKaptchaKey() {
+        HttpServletRequest request = getRequest();
+        String ip = CommonUtil.getIpAddr(request);
+        String userAgent = request.getHeader("User-Agent");
+        return "captcha:ua:ip:" + CommonUtil.MD5(ip+userAgent);
+    }
+
+
+}

+ 14 - 2
src/main/java/com/backendsys/modules/system/controller/SysAuthV2Controller.java

@@ -1,6 +1,9 @@
 package com.backendsys.modules.system.controller;
 
+import com.backendsys.aspect.RateLimiting;
+import com.backendsys.entity.System.SysUserDTO;
 import com.backendsys.modules.common.utils.Result;
+import com.backendsys.modules.system.entity.SysAuth;
 import com.backendsys.modules.system.entity.SysMobileArea;
 import com.backendsys.modules.system.service.SysAuthV2Service;
 import io.swagger.v3.oas.annotations.Operation;
@@ -10,6 +13,8 @@ import jakarta.servlet.http.HttpServletResponse;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 
 import java.io.IOException;
@@ -24,8 +29,8 @@ public class SysAuthV2Controller {
 
     @Operation(summary = "获取图形验证码")
     @GetMapping("/api/v2/system/auth/getCaptcha")
-    public void getCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
-        sysAuthV2Service.renderCaptcha(request, response);
+    public void getCaptcha(HttpServletResponse response) throws IOException {
+        sysAuthV2Service.renderCaptcha(response);
     }
 
     @Operation(summary = "获取手机号地区列表")
@@ -34,4 +39,11 @@ public class SysAuthV2Controller {
         return Result.success().put("data", sysAuthV2Service.getMobileAreaList(sysMobileArea));
     }
 
+    @Operation(summary = "系统用户登录")
+    @PostMapping(value = "/api/v2/system/auth/login")
+    // @RateLimiting(key = "systemLogin", limit = 5) // 限流?
+    public Result systemLogin(@Validated(SysAuth.Login.class) @RequestBody SysAuth sysAuth) {
+        return Result.success().put("data", sysAuthV2Service.login(sysAuth)); // sysAuthService.login(request, sysUserDTO));
+    }
+
 }

+ 27 - 0
src/main/java/com/backendsys/modules/system/entity/SysAuth.java

@@ -0,0 +1,27 @@
+package com.backendsys.modules.system.entity;
+
+import com.backendsys.entity.System.SysUserDTO;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Data
+public class SysAuth {
+
+    public static interface Login{}
+    public static interface Register{}
+
+
+    private Long user_id;
+    @NotEmpty(message="用户名不能为空", groups = { Login.class })
+    @Size(min = 2, max = 20, message = "用户名长度在 {min}-{max} 字符", groups = { Login.class })
+    private String username;
+
+    @NotEmpty(message="密码不能为空", groups = { Login.class })
+    private String password;
+
+    @NotEmpty(message="验证码不能为空", groups = { Login.class, Register.class })
+    private String captcha;
+
+    private Integer is_remember;
+}

+ 5 - 1
src/main/java/com/backendsys/modules/system/service/SysAuthV2Service.java

@@ -1,15 +1,19 @@
 package com.backendsys.modules.system.service;
 
+import com.backendsys.modules.system.entity.SysAuth;
 import com.backendsys.modules.system.entity.SysMobileArea;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 
 public interface SysAuthV2Service {
 
-    void renderCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException;
+    void renderCaptcha(HttpServletResponse response) throws IOException;
     List<SysMobileArea> getMobileAreaList(SysMobileArea sysMobileArea);
 
+    Map<String, Object> login(SysAuth sysAuth);
+
 }

+ 64 - 4
src/main/java/com/backendsys/modules/system/service/impl/SysAuthV2ServiceImpl.java

@@ -1,12 +1,18 @@
 package com.backendsys.modules.system.service.impl;
 
 import com.backendsys.config.Kaptcha.KaptchaUtil;
+import com.backendsys.exception.CustException;
 import com.backendsys.modules.common.config.redis.utils.RedisUtil;
+import com.backendsys.modules.common.config.security.utils.CaptchaUtil;
+import com.backendsys.modules.common.config.security.utils.CountUtilV2;
+import com.backendsys.modules.common.config.security.utils.HttpRequestUtil;
 import com.backendsys.modules.system.dao.SysMobileAreaDao;
+import com.backendsys.modules.system.dao.SysUserDao;
+import com.backendsys.modules.system.entity.SysAuth;
 import com.backendsys.modules.system.entity.SysMobileArea;
+import com.backendsys.modules.system.entity.SysUser;
 import com.backendsys.modules.system.service.SysAuthV2Service;
-import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.backendsys.utils.response.ResultEnum;
 import com.google.code.kaptcha.Producer;
 import jakarta.servlet.ServletOutputStream;
 import jakarta.servlet.http.HttpServletRequest;
@@ -20,29 +26,39 @@ import java.awt.image.BufferedImage;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 @Service
 public class SysAuthV2ServiceImpl implements SysAuthV2Service {
 
+    @Autowired
+    private HttpRequestUtil httpRequestUtil;
     @Autowired
     private RedisUtil redisUtil;
+    @Autowired
+    private CountUtilV2 countUtilV2;
+    @Autowired
+    private CaptchaUtil captchaUtil;
+
     @Autowired
     private Producer captchaProducer;
     @Value("${CAPTCHA_DURATION}")
     private Integer CAPTCHA_DURATION;
 
+    @Autowired
+    private SysUserDao sysUserDao;
     @Autowired
     private SysMobileAreaDao sysMobileAreaDao;
 
     @Override
-    public void renderCaptcha(HttpServletRequest request, HttpServletResponse response) throws IOException {
+    public void renderCaptcha(HttpServletResponse response) throws IOException {
         byte[] captchaChallengeAsJpeg;
         ByteArrayOutputStream jpegOutputStream = new ByteArrayOutputStream();
         try {
             String createText = captchaProducer.createText();
             // 获得当前 (UA + IP) 生成的 Key
-            String captchaRedisKey = KaptchaUtil.getKaptchaKey(request);
+            String captchaRedisKey = httpRequestUtil.getKaptchaKey();
             // 保存 验证码字符串 到 redis 中
             redisUtil.setCacheObject(captchaRedisKey, createText, this.CAPTCHA_DURATION, TimeUnit.MILLISECONDS);
             // 返回 BufferedImage 对象并转为 byte 写入到 byte 数组中
@@ -68,4 +84,48 @@ public class SysAuthV2ServiceImpl implements SysAuthV2Service {
         return sysMobileAreaDao.selectMobileAreaList(sysMobileArea);
     }
 
+    @Override
+    public Map<String, Object> login(SysAuth sysAuth) {
+
+        System.out.println(sysAuth);
+        System.out.println(httpRequestUtil.getIpAddr());
+
+        String username = sysAuth.getUsername();
+        String password = sysAuth.getPassword();
+        String captcha = sysAuth.getCaptcha();
+
+        // 判断是否处于 5次的错误状态
+        countUtilV2.checkErrorStatus("login-error", sysAuth.getUsername());
+
+        // [Method] 判断验证码是否正确 (RedisKey: (ua + ip))
+        String captchaRedisKey = httpRequestUtil.getKaptchaKey();
+        if (!captchaUtil.isCaptchaValid(captcha, captchaRedisKey)) {
+            redisUtil.delete(captchaRedisKey);
+            throw new CustException("验证码错误", ResultEnum.INVALID_CREDENTIALS.getCode());
+        }
+
+        // [Method] 判断 用户 是否存在 && 密码是否正确
+//        SysUser sysUser = sysUserDao.selectOne()
+
+//        Map<String, Object> sysUserSimple = sysUserMapper.queryUserByIdOrName(null, username, null, null);
+//        if (sysUserSimple != null) {
+//            sysUserDTO.setUser_id((Long) sysUserSimple.get("id"));
+//        }
+//        if (!(sysUserSimple != null && isUserPasswordValid(sysUserSimple, password))) {
+//            stringRedisTemplate.delete(captchaRedisKey);
+//            // 添加错误标记 (2分钟内错误5次,则出现提示)
+//            countUtil.setErrorCount("login-error", username);
+//            //
+//            throw new CustException("用户名或密码错误", ResultEnum.INVALID_CREDENTIALS.getCode());
+//        }
+//
+//        // 1.作废验证码密钥
+//        stringRedisTemplate.delete(captchaRedisKey);
+//
+//        // [登录成功]
+//        Map<String, Object> result = loginSuccess(request, sysUserDTO);
+
+        return null;
+    }
+
 }