Selaa lähdekoodia

1、加入手机号登录
2、IP校验,不同网段不允许登录,跳到手机号登录

liyanbo 7 kuukautta sitten
vanhempi
sitoutus
31b54c9e9f

+ 1 - 0
byzs-framework/byzs-spring-boot-starter-biz-tenant/src/main/java/cn/iocoder/byzs/framework/tenant/core/security/TenantDataSecurityFilter.java

@@ -29,6 +29,7 @@ public class TenantDataSecurityFilter extends OncePerRequestFilter {
     // 不参与SQL拼接的特定方法路径
     private static final Set<String> EXCLUDED_METHOD_PATHS = new HashSet<>(Arrays.asList(
             "/system/",//系统请求
+            "/bjdxWeb/web/",//系统请求
 
 //            "/bjdxWeb/questionnaire/",//问卷试题
 //            "/bjdxReport/progress/",//评估

+ 2 - 1
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/enums/ErrorCodeConstants.java

@@ -16,7 +16,8 @@ public interface ErrorCodeConstants {
     ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1_002_000_005, "未绑定账号,需要进行绑定");
     ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1_002_000_007, "手机号不存在");
     ErrorCode AUTH_REGISTER_CAPTCHA_CODE_ERROR = new ErrorCode(1_002_000_008, "验证码不正确,原因:{}");
-    ErrorCode AUTH_LOGIN_IP_NOT_AUTHORIZED = new ErrorCode(1_002_000_009, "登录IP未被授权");
+    ErrorCode AUTH_LOGIN_IP_NOT_AUTHORIZED = new ErrorCode(1_002_000_009, "登录IP未被授权,请使用手机号校验登录!");
+    ErrorCode AUTH_LOGIN_IP_NOT_AUTHORIZED_NOT_MOBILE_NOT_EXISTS = new ErrorCode(1_002_000_010, "登录IP未被授权,账号未绑定手机号,请联系管理员绑定手机号!");
 
     // ========== 菜单模块 1-002-001-000 ==========
     ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1_002_001_000, "已经存在该名字的菜单");

+ 1 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/enums/sms/SmsSceneEnum.java

@@ -21,6 +21,7 @@ public enum SmsSceneEnum implements ArrayValuable<Integer> {
     MEMBER_UPDATE_PASSWORD(3, "user-update-password", "会员用户 - 修改密码"),
     MEMBER_RESET_PASSWORD(4, "user-reset-password", "会员用户 - 忘记密码"),
 
+    AI_MEMBER_LOGIN(20, "ai-sms-login", "前台用户 - 手机号登录"),
     ADMIN_MEMBER_LOGIN(21, "admin-sms-login", "后台用户 - 手机号登录"),
     ADMIN_MEMBER_REGISTER(22, "admin-sms-register", "后台用户 - 手机号注册"),
     ADMIN_MEMBER_RESET_PASSWORD(23, "admin-reset-password", "后台用户 - 忘记密码");

+ 34 - 18
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/auth/AdminAuthServiceImpl.java

@@ -27,12 +27,14 @@ import cn.iocoder.byzs.module.system.service.member.MemberService;
 import cn.iocoder.byzs.module.system.service.oauth2.OAuth2TokenService;
 import cn.iocoder.byzs.module.system.service.social.SocialUserService;
 import cn.iocoder.byzs.module.system.service.user.AdminUserService;
+import cn.iocoder.byzs.module.system.util.IpUtil;
 import com.anji.captcha.model.common.ResponseModel;
 import com.anji.captcha.model.vo.CaptchaVO;
 import com.anji.captcha.service.CaptchaService;
 import com.google.common.annotations.VisibleForTesting;
 import jakarta.annotation.Resource;
 import jakarta.validation.Validator;
+import jodd.util.StringUtil;
 import lombok.Setter;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
@@ -115,10 +117,42 @@ public class AdminAuthServiceImpl implements AdminAuthService {
             socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),
                     reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));
         }
+
+        // 校验登录ip是否已被授权
+        validateLongIp(user);
+
         // 创建 Token 令牌,记录登录日志
         return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);
     }
 
+    //判断是否需要校验验证码
+    private void validateLongIp(AdminUserDO user) {
+
+        // 删除用户之前的所有令牌,实现单点登录(默认租户用户可以多设备登录)
+        if (!Objects.equals(TenantContextHolder.getTenantId(), WebFrameworkUtils.DEFAULT_TENANT_ID)){
+
+            //校验手机号是否存在
+            if(StringUtil.isBlank(user.getMobile())){
+                throw exception(AUTH_LOGIN_IP_NOT_AUTHORIZED_NOT_MOBILE_NOT_EXISTS);
+            }
+
+            //查看登录ip是否已被授权
+
+            if (StringUtil.isNotBlank(user.getLoginIp()) && !IpUtil.isSameNetworkSegment(user.getLoginIp(), getClientIP())) {
+
+                //发送手机验证码
+//                AuthSmsLoginReqVO authSmsLoginReqVO = new AuthSmsLoginReqVO().setMobile(user.getNickname());
+//                smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(authSmsLoginReqVO));
+
+                throw exception(AUTH_LOGIN_IP_NOT_AUTHORIZED);
+            }
+
+
+            // 删除用户之前的所有令牌,实现单点登录(默认租户用户可以多设备登录)
+            oauth2TokenService.removeUserTokens(user.getId(), getUserType().getValue());
+        }
+    }
+
     @Override
     public void sendSmsCode(AuthSmsSendReqVO reqVO) {
         // 如果是重置密码场景,需要校验图形验证码是否正确
@@ -214,24 +248,6 @@ public class AdminAuthServiceImpl implements AdminAuthService {
 
     private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) {
 
-        // 删除用户之前的所有令牌,实现单点登录(默认租户用户可以多设备登录)
-        if (!Objects.equals(TenantContextHolder.getTenantId(), WebFrameworkUtils.DEFAULT_TENANT_ID)){
-            // 删除用户之前的所有令牌,实现单点登录(默认租户用户可以多设备登录)
-            oauth2TokenService.removeUserTokens(userId, getUserType().getValue());
-
-            //查看登录ip是否已被授权
-            Set<String> userLoginIpSet = loginLogService.selectIpListByUserId(userId);
-            if(!userLoginIpSet.contains(ServletUtils.getClientIP())){
-
-                //需要手机号校验
-                //发送手机验证码
-                AuthSmsLoginReqVO authSmsLoginReqVO = new AuthSmsLoginReqVO().setMobile(username);
-//                smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(authSmsLoginReqVO));
-
-                throw exception(AUTH_LOGIN_IP_NOT_AUTHORIZED);
-            }
-        }
-
         // 插入登陆日志
         createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);
 

+ 3 - 1
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/service/sms/SmsCodeServiceImpl.java

@@ -51,6 +51,8 @@ public class SmsCodeServiceImpl implements SmsCodeService {
     }
 
     private String createSmsCode(String mobile, Integer scene, String ip) {
+        String code2 = String.format("%0" + smsCodeProperties.getEndCode().toString().length() + "d",
+                randomInt(0, smsCodeProperties.getEndCode() + 1));
         // 校验是否可以发送验证码,不用筛选场景
         SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null, null);
         if (lastSmsCode != null) {
@@ -68,7 +70,7 @@ public class SmsCodeServiceImpl implements SmsCodeService {
 
         // 创建验证码记录
         String code = String.format("%0" + smsCodeProperties.getEndCode().toString().length() + "d",
-                randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));
+                randomInt(0, smsCodeProperties.getEndCode() + 1));
         SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code).scene(scene)
                 .todayIndex(lastSmsCode != null && isToday(lastSmsCode.getCreateTime()) ? lastSmsCode.getTodayIndex() + 1 : 1)
                 .createIp(ip).used(false).build();

+ 43 - 0
byzs-module-system/src/main/java/cn/iocoder/byzs/module/system/util/IpUtil.java

@@ -0,0 +1,43 @@
+package cn.iocoder.byzs.module.system.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
+public class IpUtil {
+
+    /**
+     * 判断两个IP地址是否在同一网段(C类网络)
+     * @param ip1 第一个IP地址
+     * @param ip2 第二个IP地址
+     * @return 是否在同一网段
+     */
+    public static boolean isSameNetworkSegment(String ip1, String ip2) {
+        // 如果IP相同,直接返回true
+        if (ip1.equals(ip2)) {
+            return true;
+        }
+
+        try {
+            // 将IP地址转换为字节数组
+            String[] ip1Parts = ip1.split("\\.");
+            String[] ip2Parts = ip2.split("\\.");
+
+            // 检查IP格式是否正确
+            if (ip1Parts.length != 4 || ip2Parts.length != 4) {
+                return false;
+            }
+
+            // 比较前三个部分(C类网络)
+            for (int i = 0; i < 3; i++) {
+                if (!ip1Parts[i].equals(ip2Parts[i])) {
+                    return false;
+                }
+            }
+
+            return true;
+        } catch (Exception e) {
+            log.warn("比较IP地址网段时发生异常, ip1={}, ip2={}", ip1, ip2, e);
+            return false;
+        }
+    }
+}

+ 16 - 1
byzs-web/src/main/java/cn/iocoder/byzs/module/web/controller/admin/login/WebLoginController.java

@@ -7,6 +7,8 @@ import cn.iocoder.byzs.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.byzs.framework.tenant.core.aop.TenantIgnore;
 import cn.iocoder.byzs.module.system.controller.admin.auth.vo.AuthLoginReqVO;
 import cn.iocoder.byzs.module.system.controller.admin.auth.vo.AuthLoginRespVO;
+import cn.iocoder.byzs.module.system.controller.admin.auth.vo.AuthSmsLoginReqVO;
+import cn.iocoder.byzs.module.system.controller.admin.auth.vo.AuthSmsSendReqVO;
 import cn.iocoder.byzs.module.system.dal.dataobject.tenant.TenantDO;
 import cn.iocoder.byzs.module.system.enums.logger.LoginLogTypeEnum;
 import cn.iocoder.byzs.module.system.service.auth.AdminAuthService;
@@ -45,7 +47,6 @@ public class WebLoginController {
         return success(tenant != null ? tenant.getId() : null);
     }
 
-
     @PostMapping("/login")
     @PermitAll
     @Operation(summary = "使用账号密码登录")
@@ -53,6 +54,20 @@ public class WebLoginController {
         return success(authService.login(reqVO));
     }
 
+    @PostMapping("/sms-login")
+    @PermitAll
+    @Operation(summary = "使用短信验证码登录")
+    public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {
+        return success(authService.smsLogin(reqVO));
+    }
+
+    @PostMapping("/send-sms-code")
+    @PermitAll
+    @Operation(summary = "发送手机验证码")
+    public CommonResult<Boolean> sendLoginSmsCode(@RequestBody @Valid AuthSmsSendReqVO reqVO) {
+        authService.sendSmsCode(reqVO);
+        return success(true);
+    }
 
     @PostMapping("/logout")
     @PermitAll