Bladeren bron

merge from release_v4.1.2

deason 3 jaren geleden
bovenliggende
commit
5900f227a4

+ 44 - 0
examcloud-core-basic-api-provider/src/main/java/cn/com/qmth/examcloud/core/basic/api/controller/CryptoConfigController.java

@@ -0,0 +1,44 @@
+package cn.com.qmth.examcloud.core.basic.api.controller;
+
+import cn.com.qmth.examcloud.api.commons.security.bean.User;
+import cn.com.qmth.examcloud.api.commons.security.enums.RoleMeta;
+import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.core.basic.service.CryptoConfigService;
+import cn.com.qmth.examcloud.core.basic.service.bean.CryptoConfigInfo;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+@RestController
+@Api(tags = "加密方案组合配置相关接口")
+@RequestMapping("${$rmp.ctr.basic}/crypto/config")
+public class CryptoConfigController extends ControllerSupport {
+
+    @Autowired
+    private CryptoConfigService cryptoConfigService;
+
+    @PostMapping("/list")
+    @ApiOperation(value = "获取所有组合配置列表")
+    public List<CryptoConfigInfo> list() {
+        return cryptoConfigService.getAllCryptoConfigs();
+    }
+
+    @PostMapping("/update")
+    @ApiOperation(value = "启用、禁用某些组合")
+    public void update(@RequestBody List<CryptoConfigInfo> cryptoConfigs) {
+        User accessUser = getAccessUser();
+        if (!hasAnyRoles(accessUser, RoleMeta.SUPER_ADMIN, RoleMeta.ORG_ADMIN)) {
+            throw new StatusException("500403", "没有数据操作权限!");
+        }
+
+        cryptoConfigService.updateCryptoConfigs(cryptoConfigs);
+    }
+
+}

+ 52 - 0
examcloud-core-basic-api-provider/src/main/java/cn/com/qmth/examcloud/core/basic/api/controller/VerifyCodeController.java

@@ -2,11 +2,16 @@ package cn.com.qmth.examcloud.core.basic.api.controller;
 
 import cn.com.qmth.examcloud.api.commons.security.bean.User;
 import cn.com.qmth.examcloud.commons.exception.StatusException;
+import cn.com.qmth.examcloud.commons.util.JsonMapper;
 import cn.com.qmth.examcloud.core.basic.dao.enums.LoginRuleType;
 import cn.com.qmth.examcloud.core.basic.service.LoginRuleService;
 import cn.com.qmth.examcloud.core.basic.service.VerifyCodeService;
 import cn.com.qmth.examcloud.core.basic.service.bean.ClientLoginInfo;
 import cn.com.qmth.examcloud.core.basic.service.bean.GeetestLoginInfo;
+import cn.com.qmth.examcloud.starters.crypto.common.CryptoConstant;
+import cn.com.qmth.examcloud.starters.crypto.common.CryptoHelper;
+import cn.com.qmth.examcloud.starters.crypto.common.FieldPair;
+import cn.com.qmth.examcloud.starters.crypto.utils.AesUtil;
 import cn.com.qmth.examcloud.starters.greetest.model.RegisterReq;
 import cn.com.qmth.examcloud.starters.greetest.model.RegisterResp;
 import cn.com.qmth.examcloud.starters.greetest.model.ValidateResp;
@@ -17,12 +22,16 @@ import cn.com.qmth.examcloud.web.support.StatusResponseX;
 import cn.com.qmth.examcloud.web.support.WithoutStackTrace;
 import io.swagger.annotations.Api;
 import io.swagger.annotations.ApiOperation;
+import org.apache.commons.lang3.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RestController;
 
 import javax.servlet.http.HttpServletRequest;
+import java.nio.charset.StandardCharsets;
 
 /**
  * 验证码相关接口
@@ -31,6 +40,8 @@ import javax.servlet.http.HttpServletRequest;
 @Api(tags = "验证码登录相关接口")
 public class VerifyCodeController extends ControllerSupport {
 
+    private static final Logger log = LoggerFactory.getLogger(VerifyCodeController.class);
+
     @Autowired
     private LoginRuleService loginRuleService;
 
@@ -70,6 +81,47 @@ public class VerifyCodeController extends ControllerSupport {
         return new StatusResponseX<>(user);
     }
 
+    @Naked
+    @WithoutStackTrace
+    @ApiOperation(value = "登录接口(新)")
+    @PostMapping(value = "/api/ecs_core/login")
+    public StatusResponseX<String> login(@RequestBody GeetestLoginInfo info, HttpServletRequest request) {
+        setAlwaysOKResponse();
+
+        if (info.getRootOrgId() == null) {
+            throw new StatusException("400", "顶级机构ID不能为空");
+        }
+
+        String timestampStr = request.getHeader(CryptoConstant.TIMESTAMP);
+        if (StringUtils.isEmpty(timestampStr)) {
+            log.error("[header] timestamp is wrong... {}", timestampStr);
+            throw new StatusException("400X01", CryptoConstant.REQUEST_PARAM_ERROR);
+        }
+
+        // 当前机构是否“开放极验验证码登录”
+        boolean open = loginRuleService.isLoginRuleAllow(info.getRootOrgId(), LoginRuleType.GEETEST_LOGIN);
+        if (open) {
+            info.setIp_address(super.getIp(request));
+            ValidateResp resp = geetestService.validate(info);
+            if (!resp.getSuccess()) {
+                throw new StatusException("400", resp.getMsg());
+            }
+        }
+
+        User user = verifyCodeService.geetestLogin(info);
+        log.info("[LOGIN_IN] user = {}, salt = {}", user.buildKey(), user.getSalt());
+
+        String key = CryptoHelper.buildKey(
+                new FieldPair("accountValue", info.getAccountValue()),
+                new FieldPair("password", info.getPassword()),
+                new FieldPair("timestamp", timestampStr)
+        );
+
+        String json = new JsonMapper().toJson(user);
+        String content = AesUtil.encrypt(json, key.getBytes(StandardCharsets.UTF_8));
+        return new StatusResponseX<>(content);
+    }
+
     @Naked
     @WithoutStackTrace
     @ApiOperation(value = "极验-验证码登录接口")

+ 32 - 0
examcloud-core-basic-api-provider/src/main/java/cn/com/qmth/examcloud/core/basic/api/provider/CryptoConfigCloudProvider.java

@@ -0,0 +1,32 @@
+package cn.com.qmth.examcloud.core.basic.api.provider;
+
+import cn.com.qmth.examcloud.core.basic.api.CryptoConfigCloudService;
+import cn.com.qmth.examcloud.core.basic.api.request.CheckCryptoConfigReq;
+import cn.com.qmth.examcloud.core.basic.api.response.CheckCryptoConfigResp;
+import cn.com.qmth.examcloud.core.basic.service.CryptoConfigService;
+import cn.com.qmth.examcloud.web.support.ControllerSupport;
+import io.swagger.annotations.Api;
+import io.swagger.annotations.ApiOperation;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@Api(tags = "RPC-加密方案组合配置相关接口")
+@RequestMapping("${$rmp.cloud.basic}/crypto/config")
+public class CryptoConfigCloudProvider extends ControllerSupport implements CryptoConfigCloudService {
+
+    @Autowired
+    private CryptoConfigService cryptoConfigService;
+
+    @Override
+    @PostMapping("/check")
+    @ApiOperation(value = "判断某个组合是否启用")
+    public CheckCryptoConfigResp checkCryptoConfig(@RequestBody CheckCryptoConfigReq req) {
+        boolean enable = cryptoConfigService.isEnableCryptoCombination(req.getCombination());
+        return new CheckCryptoConfigResp(enable);
+    }
+
+}

+ 5 - 0
examcloud-core-basic-base/pom.xml

@@ -32,6 +32,11 @@
             <artifactId>examcloud-geetest-starter</artifactId>
             <version>${project.version}</version>
         </dependency>
+        <dependency>
+            <groupId>cn.com.qmth.examcloud.starters</groupId>
+            <artifactId>examcloud-crypto-starter</artifactId>
+            <version>${project.version}</version>
+        </dependency>
         <dependency>
             <groupId>com.github.penggle</groupId>
             <artifactId>kaptcha</artifactId>

+ 37 - 0
examcloud-core-basic-service/src/main/java/cn/com/qmth/examcloud/core/basic/service/CryptoConfigService.java

@@ -0,0 +1,37 @@
+package cn.com.qmth.examcloud.core.basic.service;
+
+import cn.com.qmth.examcloud.core.basic.service.bean.CryptoConfigInfo;
+
+import java.util.List;
+
+/**
+ * 加密方案组合配置相关接口
+ */
+public interface CryptoConfigService {
+
+    /**
+     * 初始化默认组合配置
+     */
+    void initCryptoConfigs();
+
+    /**
+     * 获取所有组合配置列表
+     */
+    List<CryptoConfigInfo> getAllCryptoConfigs();
+
+    /**
+     * 启用、禁用某些组合
+     */
+    void updateCryptoConfigs(List<CryptoConfigInfo> cryptoConfigs);
+
+    /**
+     * 判断某个组合是否启用
+     */
+    boolean isEnableCryptoCombination(String combination);
+
+    /**
+     * 按用户随机获取一个组合
+     */
+    String randomCryptoCombination(Long userId);
+
+}

+ 54 - 0
examcloud-core-basic-service/src/main/java/cn/com/qmth/examcloud/core/basic/service/bean/CryptoConfigInfo.java

@@ -0,0 +1,54 @@
+package cn.com.qmth.examcloud.core.basic.service.bean;
+
+import io.swagger.annotations.ApiModelProperty;
+
+import java.io.Serializable;
+
+public class CryptoConfigInfo implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    @ApiModelProperty(value = "组合")
+    private String combination;
+
+    @ApiModelProperty(value = "是否启用")
+    private boolean enable;
+
+    @ApiModelProperty(value = "更新时间", hidden = true)
+    private long updateTime;
+
+    public CryptoConfigInfo(String combination, boolean enable, long updateTime) {
+        this.combination = combination;
+        this.enable = enable;
+        this.updateTime = updateTime;
+    }
+
+    public CryptoConfigInfo() {
+
+    }
+
+    public String getCombination() {
+        return combination;
+    }
+
+    public void setCombination(String combination) {
+        this.combination = combination;
+    }
+
+    public boolean isEnable() {
+        return enable;
+    }
+
+    public void setEnable(boolean enable) {
+        this.enable = enable;
+    }
+
+    public long getUpdateTime() {
+        return updateTime;
+    }
+
+    public void setUpdateTime(long updateTime) {
+        this.updateTime = updateTime;
+    }
+
+}

+ 6 - 0
examcloud-core-basic-service/src/main/java/cn/com/qmth/examcloud/core/basic/service/impl/AuthServiceImpl.java

@@ -83,6 +83,9 @@ public class AuthServiceImpl implements AuthService {
     @Autowired
     SmsCodeService smsCodeService;
 
+    @Autowired
+    private CryptoConfigService cryptoConfigService;
+
     @Override
     public User login(LoginInfo loginInfo, boolean checkLoginRule) {
         String accountType = loginInfo.getAccountType();
@@ -315,6 +318,9 @@ public class AuthServiceImpl implements AuthService {
 
         user.setClientIp(loginInfo.getClientIp());
 
+        // 按用户随机获取一个加密方案组合(未启用任何加密方案组合时登录,salt值为空)
+        user.setSalt(cryptoConfigService.randomCryptoCombination(user.getUserId()));
+
         OrgEntity org = null;
         if (null != orgId) {
             org = GlobalHelper.getEntity(orgRepo, orgId, OrgEntity.class);

+ 158 - 0
examcloud-core-basic-service/src/main/java/cn/com/qmth/examcloud/core/basic/service/impl/CryptoConfigServiceImpl.java

@@ -0,0 +1,158 @@
+package cn.com.qmth.examcloud.core.basic.service.impl;
+
+import cn.com.qmth.examcloud.core.basic.service.CryptoConfigService;
+import cn.com.qmth.examcloud.core.basic.service.bean.CryptoConfigInfo;
+import cn.com.qmth.examcloud.starters.crypto.CryptoProperties;
+import cn.com.qmth.examcloud.web.redis.RedisClient;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang3.ArrayUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+/**
+ * 加密方案组合配置相关接口
+ */
+@Service
+public class CryptoConfigServiceImpl implements CryptoConfigService {
+
+    private static final Logger log = LoggerFactory.getLogger(CryptoConfigServiceImpl.class);
+
+    private static final String CRYPTO_CONFIG_CACHE_KEY = "$CRYPTO_CONFIG";
+
+    @Autowired
+    private CryptoProperties cryptoProperties;
+
+    @Autowired
+    private RedisClient redisClient;
+
+    /**
+     * 初始化默认组合配置
+     */
+    @Override
+    public void initCryptoConfigs() {
+        List<CryptoConfigInfo> configCaches = this.getAllCryptoConfigs();
+        if (CollectionUtils.isNotEmpty(configCaches)) {
+            log.warn("CryptoConfig has initialized.");
+            return;
+        }
+
+        String[] groups = cryptoProperties.getGroups();
+        if (ArrayUtils.isEmpty(groups)) {
+            log.warn("CryptoConfig groups is empty.");
+            return;
+        }
+
+        log.info("CryptoConfig init..");
+
+        List<CryptoConfigInfo> newConfigCaches = new ArrayList<>();
+        for (String group : groups) {
+            newConfigCaches.add(new CryptoConfigInfo(group, true, System.currentTimeMillis()));
+        }
+
+        redisClient.set(CRYPTO_CONFIG_CACHE_KEY, newConfigCaches);
+    }
+
+    /**
+     * 获取所有组合配置列表
+     */
+    @Override
+    public List<CryptoConfigInfo> getAllCryptoConfigs() {
+        return redisClient.get(CRYPTO_CONFIG_CACHE_KEY, List.class);
+    }
+
+    /**
+     * 启用、禁用某些组合
+     */
+    @Override
+    public void updateCryptoConfigs(List<CryptoConfigInfo> cryptoConfigs) {
+        if (CollectionUtils.isEmpty(cryptoConfigs)) {
+            return;
+        }
+
+        List<CryptoConfigInfo> configCaches = this.getAllCryptoConfigs();
+        if (CollectionUtils.isEmpty(configCaches)) {
+            return;
+        }
+
+        Map<String, Boolean> map = cryptoConfigs.stream().collect(
+                Collectors.toMap(CryptoConfigInfo::getCombination, CryptoConfigInfo::isEnable, (value1, value2) -> value2)
+        );
+
+        boolean needUpdate = false;
+        for (CryptoConfigInfo info : configCaches) {
+            Boolean enable = map.get(info.getCombination());
+            if (enable != null && enable != info.isEnable()) {
+                needUpdate = true;
+                info.setEnable(enable);
+                info.setUpdateTime(System.currentTimeMillis());
+                log.warn("updateCryptoConfigs {}={}", info.getCombination(), enable);
+            }
+        }
+
+        if (needUpdate) {
+            redisClient.set(CRYPTO_CONFIG_CACHE_KEY, configCaches);
+        }
+    }
+
+    /**
+     * 判断某个组合是否启用
+     */
+    @Override
+    public boolean isEnableCryptoCombination(String combination) {
+        List<CryptoConfigInfo> configCaches = this.getAllCryptoConfigs();
+        if (CollectionUtils.isEmpty(configCaches)) {
+            return false;
+        }
+
+        for (CryptoConfigInfo info : configCaches) {
+            if (info.getCombination().equals(combination)) {
+                return info.isEnable();
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * 按用户随机获取一个组合
+     * 规则:采用一定机制保证某个用户在“某个时间段”不经常改变,防止恶意频繁刷登录来过滤和锁定组合
+     */
+    @Override
+    public String randomCryptoCombination(Long userId) {
+        List<CryptoConfigInfo> configCaches = this.getAllCryptoConfigs();
+        if (CollectionUtils.isEmpty(configCaches)) {
+            log.warn("CryptoConfigs is empty. {}", userId);
+            return null;
+        }
+
+        // 启用的组合列表
+        List<String> combinations = new ArrayList<>();
+        for (CryptoConfigInfo info : configCaches) {
+            if (info.isEnable()) {
+                combinations.add(info.getCombination());
+            }
+        }
+
+        if (CollectionUtils.isEmpty(combinations)) {
+            log.warn("CryptoCombinations is empty. {}", userId);
+            return null;
+        }
+
+        SimpleDateFormat sdf = new SimpleDateFormat("MMddHH");
+        Long timestamp = Long.parseLong(sdf.format(new Date()));
+
+        //(用户ID + MMddHH格式时间戳)% 当前启用的组合数  --> 某个用户在同个小时内所取到的组合一致
+        Long index = (userId + timestamp) % combinations.size();
+        return combinations.get(index.intValue());
+    }
+
+}

+ 1 - 0
examcloud-core-basic-starter/pom.xml

@@ -61,6 +61,7 @@
                 <artifactId>maven-assembly-plugin</artifactId>
                 <configuration>
                     <finalName>examcloud-core-basic</finalName>
+                    <skipAssembly>${skipAssembly}</skipAssembly>
                     <descriptors>
                         <descriptor>assembly.xml</descriptor>
                     </descriptors>

+ 3 - 0
examcloud-core-basic-starter/src/main/java/cn/com/qmth/examcloud/core/basic/starter/CoreBasicApp.java

@@ -2,6 +2,7 @@ package cn.com.qmth.examcloud.core.basic.starter;
 
 import cn.com.qmth.examcloud.core.basic.dao.UniqueRuleHolder;
 import cn.com.qmth.examcloud.core.basic.dao.enums.OrgProperty;
+import cn.com.qmth.examcloud.core.basic.service.CryptoConfigService;
 import cn.com.qmth.examcloud.core.basic.service.LoginRuleService;
 import cn.com.qmth.examcloud.core.basic.service.cache.AppCache;
 import cn.com.qmth.examcloud.support.filestorage.FileStorageUtil;
@@ -74,6 +75,8 @@ public class CoreBasicApp {
                 SpringContextHolder.getBean(AppCache.class).loadAllFromResource();
 
                 SpringContextHolder.getBean(LoginRuleService.class).refreshAllLoginRule();
+
+                SpringContextHolder.getBean(CryptoConfigService.class).initCryptoConfigs();
             }
         }, "cache").start();
     }

+ 1 - 0
examcloud-core-basic-starter/src/main/resources/log4j2.xml

@@ -57,6 +57,7 @@
 
     <Loggers>
         <logger name="springfox.documentation" level="WARN"/>
+        <logger name="org.mongodb.driver" level="WARN"/>
         <logger name="org.springframework" level="WARN"/>
         <logger name="org.hibernate" level="WARN"/>
         <logger name="org.apache" level="WARN"/>