From df689c7ffda84c9573ac82c09d2d3d709ab7b8f2 Mon Sep 17 00:00:00 2001 From: Parker Date: Sat, 23 Jan 2021 00:41:04 +0800 Subject: [PATCH] =?UTF-8?q?=E7=99=BB=E5=BD=95=E6=8E=A5=E5=8F=A3RSA?= =?UTF-8?q?=E5=8A=A0=E5=AF=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/org/opsli/core/conf/ShiroConfig.java | 1 + .../main/java/org/opsli/core/msg/CoreMsg.java | 3 + .../java/org/opsli/core/msg/TokenMsg.java | 1 + .../core/utils/AsymmetricCryptoUtil.java | 173 ++++++++++++++++++ .../opsli-core/src/test/java/RsaTest.java | 71 +++++++ .../system/login/entity/LoginForm.java | 1 - .../system/login/entity/LoginFormStr.java | 41 +++++ .../system/login/web/LoginRestController.java | 55 +++++- .../web/SearchHisRestController.java | 1 - .../src/main/resources/application-beta.yaml | 2 +- .../src/main/resources/application.yaml | 12 ++ 11 files changed, 353 insertions(+), 8 deletions(-) create mode 100644 opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/AsymmetricCryptoUtil.java create mode 100644 opsli-base-support/opsli-core/src/test/java/RsaTest.java create mode 100644 opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/entity/LoginFormStr.java diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/ShiroConfig.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/ShiroConfig.java index 5c694354..740dc833 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/ShiroConfig.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/conf/ShiroConfig.java @@ -95,6 +95,7 @@ public class ShiroConfig { // 登录接口拦截 filterMap.put("/sys/login", "anon"); + filterMap.put("/sys/publicKey", "anon"); filterMap.put("/sys/slipCount", "anon"); filterMap.put("/captcha.jpg", "anon"); diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/msg/CoreMsg.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/msg/CoreMsg.java index e34bc841..6b3153ec 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/msg/CoreMsg.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/msg/CoreMsg.java @@ -69,6 +69,9 @@ public enum CoreMsg implements BaseMsg { /** 其他 */ OTHER_EXCEPTION_LIMITER(10700,"当前系统繁忙,请稍后再试"), + OTHER_EXCEPTION_RSA_EN(10701,"RSA非对称加密失败"), + OTHER_EXCEPTION_RSA_DE(10701,"RSA非对称解密失败"), + OTHER_EXCEPTION_RSA_REFLEX(10702,"RSA非对称解密反射失败"), ; diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/msg/TokenMsg.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/msg/TokenMsg.java index 07aec728..5df7730a 100644 --- a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/msg/TokenMsg.java +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/msg/TokenMsg.java @@ -47,6 +47,7 @@ public enum TokenMsg implements BaseMsg { EXCEPTION_LOGIN_ACCOUNT_LOCK(12104,"账号已锁定,请{}后,再次尝试"), EXCEPTION_LOGIN_TENANT_NOT_USABLE(12105,"租户未启用,请联系管理员"), EXCEPTION_LOGIN_NULL(12106,"请输入账号密码"), + EXCEPTION_LOGIN_DECRYPT(12107,"登录账号密码解析失败"), /** * 其他 diff --git a/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/AsymmetricCryptoUtil.java b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/AsymmetricCryptoUtil.java new file mode 100644 index 00000000..f9b7a84b --- /dev/null +++ b/opsli-base-support/opsli-core/src/main/java/org/opsli/core/utils/AsymmetricCryptoUtil.java @@ -0,0 +1,173 @@ +/** + * Copyright 2020 OPSLI 快速开发平台 https://www.opsli.com + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.opsli.core.utils; + + +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.StrUtil; +import cn.hutool.crypto.asymmetric.KeyType; +import cn.hutool.crypto.asymmetric.RSA; +import com.alibaba.fastjson.JSONObject; +import org.opsli.common.exception.ServiceException; +import org.opsli.core.msg.CoreMsg; + +import java.util.Collection; + +/** + * 非对称加密 RSA 工具类 + * + * @author 周鹏程 + */ +public enum AsymmetricCryptoUtil { + + /** 实例 */ + INSTANCE; + + /** RSA KEY */ + private final String rsaKey = "data"; + + /** RSA对象 */ + private final RSA rsa; + + AsymmetricCryptoUtil(){ + // 初始化RSA对象 + this.rsa = this.createRsa(); + } + + /** + * 生成 RSA 对象 + * @return RSA + */ + public RSA createRsa(){ + return new RSA(); + } + + /** + * 生成 RSA 对象 + * @param publicKey 公钥 + * @param privateKey 私钥 + * @return RSA + */ + public RSA createRsa(String publicKey, String privateKey){ + return new RSA(privateKey, publicKey); + } + + + /** + * 获得公钥 + * @return String + */ + public String getPublicKey(){ + return rsa.getPublicKeyBase64(); + } + + /** + * RSA 加密数据 + * @param data 数据 + * @return String + */ + public String encryptedData(Object data){ + return this.encryptedData(this.rsa, data); + } + + + /** + * RSA 加密数据 + * @param rsa RSA + * @param data 数据 + * @return String + */ + public String encryptedData(RSA rsa, Object data){ + String encryptedStr; + try { + JSONObject jsonObject = new JSONObject(); + jsonObject.put(rsaKey, data); + encryptedStr = rsa.encryptBase64(StrUtil.bytes(jsonObject.toString(), CharsetUtil.CHARSET_UTF_8), KeyType.PublicKey); + }catch (Exception e){ + // 加密失败 + throw new ServiceException(CoreMsg.OTHER_EXCEPTION_RSA_EN); + } + return encryptedStr; + } + + + /** + * RSA 解密数据 + * @param data 数据 + * @return Object + */ + public Object decryptedDataToObj(String data){ + return this.decryptedDataToObj(this.rsa, data); + } + + /** + * RSA 解密数据 + * @param rsa RSA + * @param data 数据 + * @return Object + */ + public Object decryptedDataToObj(RSA rsa, String data){ + Object obj; + String decryptedData = AsymmetricCryptoUtil.INSTANCE.decryptedData(rsa, data); + try{ + obj = JSONObject.parse(decryptedData); + }catch (Exception e){ + // RSA非对称解密反射失败 + throw new ServiceException(CoreMsg.OTHER_EXCEPTION_RSA_REFLEX); + } + return obj; + } + + + + /** + * RSA 解密数据 + * @param data + * @return String + */ + public String decryptedData(String data){ + return this.decryptedData(this.rsa, data); + } + + + + /** + * RSA 解密数据 + * @param rsa RSA + * @param data 数据 + * @return String + */ + public String decryptedData(RSA rsa,String data){ + //解密,因为编码传值时有空格出现 + data = data.replaceAll(" ", "+"); + String decryptStr; + try{ + String tmp = rsa.decryptStr(data, KeyType.PrivateKey); + JSONObject jsonObject = JSONObject.parseObject(tmp); + Object obj = jsonObject.get(rsaKey); + if(obj instanceof Collection){ + decryptStr = jsonObject.getJSONArray(rsaKey).toJSONString(); + }else{ + decryptStr = jsonObject.getJSONObject(rsaKey).toJSONString(); + } + }catch (Exception e){ + // 解密失败 + throw new ServiceException(CoreMsg.OTHER_EXCEPTION_RSA_DE); + } + return decryptStr; + } + +} diff --git a/opsli-base-support/opsli-core/src/test/java/RsaTest.java b/opsli-base-support/opsli-core/src/test/java/RsaTest.java new file mode 100644 index 00000000..3e11bc03 --- /dev/null +++ b/opsli-base-support/opsli-core/src/test/java/RsaTest.java @@ -0,0 +1,71 @@ +import cn.hutool.core.convert.Convert; +import com.alibaba.fastjson.JSONObject; +import com.google.common.collect.Maps; +import lombok.Data; +import org.junit.Test; +import org.opsli.core.utils.AsymmetricCryptoUtil; + +import java.util.Map; + +/** + * Rsa 加密认证 + */ +public class RsaTest { + + + /** + * 加密数据 + */ + @Test + public void getPublicKey(){ + System.out.println(AsymmetricCryptoUtil.INSTANCE.getPublicKey()); + + } + + /** + * 加密数据 + */ + @Test + public void test(){ + Map map = Maps.newHashMap(); + map.put("test1", 123); + map.put("test2", "aaa"); + +// Test1 t1 = new Test1(); +// t1.setId( "123"); +// t1.setName( "张三"); +// t1.setAge( 16); + + Object parse = JSONObject.parse("{\"username\":\"demo\",\"password\":\"Aa123456\",\"captcha\":\"\",\"uuid\":\"0d3eea43edf19e4ed0e88aae8d56878046a5\"}"); + + // 加密 + String encryptedData = AsymmetricCryptoUtil.INSTANCE.encryptedData(parse); + System.out.println(encryptedData); + + // 解密 + String decryptedData = AsymmetricCryptoUtil.INSTANCE.decryptedData(encryptedData); + Object decryptedDataToObj = AsymmetricCryptoUtil.INSTANCE.decryptedDataToObj(encryptedData); + System.out.println(decryptedData); + + // 解密 + //Map stringObjectMap = Convert.toMap(String.class, Object.class, decryptedData); + //Map stringObjectMap = Convert.toMap(String.class, Object.class, decryptedDataToObj); + + System.out.println(123); + + } + + + +} + +@Data +class Test1 { + + private String id; + + private String name; + + private Integer age; + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/entity/LoginForm.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/entity/LoginForm.java index e39f34d7..5432f559 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/entity/LoginForm.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/entity/LoginForm.java @@ -47,7 +47,6 @@ public class LoginForm { /** 验证码 */ @ApiModelProperty(value = "验证码") - @ValidationArgs(ValiArgsType.IS_NOT_NULL) @ValidationArgsLenMax(30) private String captcha; diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/entity/LoginFormStr.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/entity/LoginFormStr.java new file mode 100644 index 00000000..b95cd67d --- /dev/null +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/entity/LoginFormStr.java @@ -0,0 +1,41 @@ +/** + * Copyright 2020 OPSLI 快速开发平台 https://www.opsli.com + *

+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + *

+ * http://www.apache.org/licenses/LICENSE-2.0 + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package org.opsli.modulars.system.login.entity; + +import io.swagger.annotations.ApiModelProperty; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.opsli.common.annotation.validation.ValidationArgs; +import org.opsli.common.annotation.validation.ValidationArgsLenMax; +import org.opsli.common.annotation.validation.ValidationArgsLenMin; +import org.opsli.common.enums.ValiArgsType; + +/** + * 登录表单 + * + * @author liuzp + * @since 2.0.0 2018-01-25 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class LoginFormStr { + + /** 登录信息 */ + @ApiModelProperty(value = "登录信息") + @ValidationArgs({ValiArgsType.IS_NOT_NULL}) + private String loginFormStr; + +} diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/web/LoginRestController.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/web/LoginRestController.java index 648ec014..3c1f9e06 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/web/LoginRestController.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/system/login/web/LoginRestController.java @@ -15,12 +15,15 @@ */ package org.opsli.modulars.system.login.web; +import cn.hutool.core.convert.Convert; +import com.alibaba.fastjson.JSONObject; import com.google.common.collect.Maps; import io.swagger.annotations.ApiOperation; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.apache.tomcat.util.http.fileupload.IOUtils; import org.opsli.api.base.result.ResultVo; +import org.opsli.api.utils.ValidationUtil; import org.opsli.api.wrapper.system.tenant.TenantModel; import org.opsli.api.wrapper.system.user.UserModel; import org.opsli.common.annotation.Limiter; @@ -32,13 +35,12 @@ import org.opsli.common.utils.IPUtil; import org.opsli.common.utils.OutputStreamUtil; import org.opsli.core.msg.TokenMsg; import org.opsli.core.security.shiro.realm.JwtRealm; -import org.opsli.core.utils.CaptchaUtil; -import org.opsli.core.utils.TenantUtil; -import org.opsli.core.utils.UserTokenUtil; -import org.opsli.core.utils.UserUtil; +import org.opsli.core.utils.*; import org.opsli.modulars.system.login.entity.LoginForm; +import org.opsli.modulars.system.login.entity.LoginFormStr; import org.opsli.modulars.system.user.service.IUserService; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -65,6 +67,10 @@ import java.util.Map; @RestController public class LoginRestController { + /** 登录是否开启 RSA加密 */ + @Value("${opsli.login.loginRsa:false}") + private boolean loginRsa; + @Autowired private IUserService iUserService; @@ -75,11 +81,36 @@ public class LoginRestController { @Limiter @ApiOperation(value = "登录", notes = "登录") @PostMapping("/sys/login") - public ResultVo login(@RequestBody LoginForm form, HttpServletRequest request){ + public ResultVo login(@RequestBody LoginFormStr formStr, HttpServletRequest request){ + // 非空验证 + if(formStr == null || StringUtils.isBlank(formStr.getLoginFormStr())){ + throw new TokenException(TokenMsg.EXCEPTION_LOGIN_NULL); + } + + LoginForm form; + try{ + Object loginFromObj; + if(loginRsa){ + loginFromObj = AsymmetricCryptoUtil.INSTANCE.decryptedDataToObj(formStr.getLoginFormStr()); + }else { + loginFromObj = JSONObject.parse(formStr.getLoginFormStr()); + } + form = Convert.convert(LoginForm.class, loginFromObj); + }catch (Exception e){ + log.error("登录账号密码解析失败 - 解析值:["+formStr.getLoginFormStr()+"]",e.getMessage(), e); + // 登录账号密码解析失败 + throw new TokenException(TokenMsg.EXCEPTION_LOGIN_DECRYPT); + } + + // 非空验证 if(form == null){ throw new TokenException(TokenMsg.EXCEPTION_LOGIN_NULL); } + // 验证登录对象 + ValidationUtil.verify(form); + + // 判断账号是否临时锁定 UserTokenUtil.verifyLockAccount(form.getUsername()); @@ -194,6 +225,20 @@ public class LoginRestController { } } + /** + * 获得公钥 + */ + @Limiter + @ApiOperation(value = "获得公钥", notes = "获得公钥") + @GetMapping("/sys/publicKey") + public ResultVo getPublicKey(){ + return ResultVo.success( + "操作成功!", + AsymmetricCryptoUtil.INSTANCE.getPublicKey() + ); + } + + public static void main(String[] args) { String passwordStr = "Aa123456"; diff --git a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/tools/searchhis/web/SearchHisRestController.java b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/tools/searchhis/web/SearchHisRestController.java index c5b6a1b3..186bbf55 100644 --- a/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/tools/searchhis/web/SearchHisRestController.java +++ b/opsli-modulars/opsli-modulars-system/src/main/java/org/opsli/modulars/tools/searchhis/web/SearchHisRestController.java @@ -36,7 +36,6 @@ import java.util.Set; ** */ @Slf4j -@RestController @ApiRestController("/tools/searchhis") public class SearchHisRestController { diff --git a/opsli-starter/src/main/resources/application-beta.yaml b/opsli-starter/src/main/resources/application-beta.yaml index 8936e17a..02c2c330 100644 --- a/opsli-starter/src/main/resources/application-beta.yaml +++ b/opsli-starter/src/main/resources/application-beta.yaml @@ -35,7 +35,7 @@ spring: master: url: jdbc:mysql://127.0.0.1:3306/opsli-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&serverTimezone=Asia/Shanghai username: root - password: 12345678 + password: MYSQL0p9o8i7u driver-class-name: com.mysql.cj.jdbc.Driver # 多数据源配置 #slave-datasource: diff --git a/opsli-starter/src/main/resources/application.yaml b/opsli-starter/src/main/resources/application.yaml index 144872ed..7c932629 100644 --- a/opsli-starter/src/main/resources/application.yaml +++ b/opsli-starter/src/main/resources/application.yaml @@ -194,6 +194,8 @@ opsli: # 登录设置 login: + # 登录开启RSA加密 + loginRsa: true # 失败次数 slip-count: 5 # 失败N次后弹出验证码 (超过验证码阈值 弹出验证码) @@ -201,6 +203,16 @@ opsli: # 失败锁定时间(秒) slip-lock-speed: 300 + # 登录 + login: + # 登录开启RSA加密 + loginRsa: true + # 失败次数 + slip-count: 5 + # 锁定时间(秒) + slip-lock-speed: 300 + + # Excel 最大操作数量 防止OOM excel-max-count: 20000