| @@ -36,7 +36,7 @@ public class AgentCenterController extends BaseController | |||||
| NSSDKServer server; | NSSDKServer server; | ||||
| NSReportObject<NSContractionMessage> recv; | NSReportObject<NSContractionMessage> recv; | ||||
| NSSDK.InitServer(RuoYiConfig.Secret.privateKey); | |||||
| NSSDK.InitServer(RuoYiConfig.AgentCenter.privateKey); | |||||
| server = NSSDK.InstanceServer(); | server = NSSDK.InstanceServer(); | ||||
| recv = server.Recv(request, NSContractionMessage.class); | recv = server.Recv(request, NSContractionMessage.class); | ||||
| @@ -49,7 +49,7 @@ public class AgentCenterController extends BaseController | |||||
| NSSDKServer server; | NSSDKServer server; | ||||
| Session<?> session; | Session<?> session; | ||||
| NSSDK.InitServer(RuoYiConfig.Secret.privateKey); | |||||
| NSSDK.InitServer(RuoYiConfig.AgentCenter.privateKey); | |||||
| server = NSSDK.InstanceServer(); | server = NSSDK.InstanceServer(); | ||||
| try | try | ||||
| { | { | ||||
| @@ -130,8 +130,16 @@ xss: | |||||
| # 匹配链接 | # 匹配链接 | ||||
| urlPatterns: /system/*,/monitor/*,/tool/* | urlPatterns: /system/*,/monitor/*,/tool/* | ||||
| # 传输加密(公钥,私钥) | |||||
| secret: | secret: | ||||
| # 是否启用 | |||||
| enabled: true | |||||
| # 是否兼容不加密明文 | |||||
| compat: false | |||||
| publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== | |||||
| privateKey: MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKNPuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gAkM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWowcSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99EcvDQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthhYhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3UP8iWi1Qw0Y= | |||||
| agentcenter: | |||||
| disabled: false # UNUSED | disabled: false # UNUSED | ||||
| # 客户端公钥 | # 客户端公钥 | ||||
| publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== | publicKey: MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ== | ||||
| @@ -1,5 +1,6 @@ | |||||
| package com.ruoyi.common.config; | package com.ruoyi.common.config; | ||||
| import com.ruoyi.common.utils.SecretUtils; | |||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import org.springframework.stereotype.Component; | import org.springframework.stereotype.Component; | ||||
| @@ -137,14 +138,42 @@ public class RuoYiConfig | |||||
| @Component | @Component | ||||
| @ConfigurationProperties(prefix = "secret") | |||||
| public static class Secret { | |||||
| @ConfigurationProperties(prefix = "agentcenter") | |||||
| public static class AgentCenter { | |||||
| public static Boolean disabled; | public static Boolean disabled; | ||||
| public static String publicKey; | public static String publicKey; | ||||
| public static String privateKey; | public static String privateKey; | ||||
| public void setDisabled(Boolean disabled) { | public void setDisabled(Boolean disabled) { | ||||
| Secret.disabled = disabled; | |||||
| AgentCenter.disabled = disabled; | |||||
| } | |||||
| public void setPublicKey(String publicKey) { | |||||
| AgentCenter.publicKey = publicKey; | |||||
| } | |||||
| public void setPrivateKey(String privateKey) { | |||||
| AgentCenter.privateKey = privateKey; | |||||
| } | |||||
| public static boolean isDisabled() { | |||||
| return null != AgentCenter.disabled /*默认加密*/ && AgentCenter.disabled; | |||||
| } | |||||
| } | |||||
| @Component | |||||
| @ConfigurationProperties(prefix = "secret") | |||||
| public static class Secret { | |||||
| public static Boolean enabled; | |||||
| public static String publicKey; | |||||
| public static String privateKey; | |||||
| public static Boolean compat; | |||||
| public void setCompat(Boolean compat) { | |||||
| Secret.compat = compat; | |||||
| } | |||||
| public void setEnabled(Boolean enabled) { | |||||
| Secret.enabled = enabled; | |||||
| } | } | ||||
| public void setPublicKey(String publicKey) { | public void setPublicKey(String publicKey) { | ||||
| @@ -156,7 +185,21 @@ public class RuoYiConfig | |||||
| } | } | ||||
| public static boolean isDisabled() { | public static boolean isDisabled() { | ||||
| return null != Secret.disabled /*默认加密*/ && Secret.disabled; | |||||
| return null == Secret.enabled /*默认不加密*/ || !Secret.enabled; | |||||
| } | |||||
| public static boolean isCompat() { | |||||
| return null != Secret.compat /*默认不兼容*/ && Secret.compat; | |||||
| } | |||||
| // 私钥解密 | |||||
| public static String decrypt(String inStr) { | |||||
| try { | |||||
| return SecretUtils.decrypt(inStr, Secret.privateKey, StandardCharsets.UTF_8.name()); | |||||
| } catch (Exception e) { | |||||
| //e.printStackTrace(); | |||||
| throw new RuntimeException(e); | |||||
| } | |||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| @@ -0,0 +1,146 @@ | |||||
| package com.ruoyi.common.exception; | |||||
| import java.util.Map; | |||||
| import java.util.Objects; | |||||
| /** | |||||
| * 断言异常 | |||||
| * | |||||
| * @author zhao | |||||
| */ | |||||
| public final class ASSERT extends RuntimeException | |||||
| { | |||||
| private static final long serialVersionUID = 1L; | |||||
| public final String message; | |||||
| public ASSERT(String message) | |||||
| { | |||||
| this.message = message; | |||||
| } | |||||
| @Override | |||||
| public String getMessage() | |||||
| { | |||||
| return message; | |||||
| } | |||||
| public static String Output(ASSERT e) | |||||
| { | |||||
| StringBuilder sb = new StringBuilder(); | |||||
| sb.append(e.message); | |||||
| StackTraceElement[] stackTrace = e.getStackTrace(); | |||||
| if(null != stackTrace) | |||||
| { | |||||
| for(int i = 0; i < stackTrace.length; i++) | |||||
| { | |||||
| StackTraceElement st = stackTrace[i]; | |||||
| if(st.getClassName().startsWith("com.ruoyi.")) | |||||
| { | |||||
| sb.append("\n").append("\t").append(i).append(": ").append(st.toString()); | |||||
| } | |||||
| } | |||||
| } | |||||
| return sb.toString(); | |||||
| } | |||||
| public static void THROW(String message, Object...args) | |||||
| { | |||||
| throw new ASSERT(null != message ? String.format(message, args) : "断言失败"); | |||||
| } | |||||
| public static void EXP(boolean expression, String errorMsgTemplate, Object... params) throws ASSERT { | |||||
| if(!expression) | |||||
| THROW(errorMsgTemplate, params); | |||||
| } | |||||
| public static void EXP(boolean expression) throws ASSERT { | |||||
| EXP(expression, "断言表达式为假"); | |||||
| } | |||||
| public static void TRUE(boolean expression, String errorMsgTemplate, Object... params) throws ASSERT { | |||||
| if(!expression) | |||||
| THROW(errorMsgTemplate, params); | |||||
| } | |||||
| public static void TRUE(boolean expression) throws ASSERT { | |||||
| TRUE(expression, "[断言失败] - 表达式必须为true"); | |||||
| } | |||||
| public static void FALSE(boolean expression, String errorMsgTemplate, Object... params) throws ASSERT { | |||||
| if(expression) | |||||
| THROW(errorMsgTemplate, params); | |||||
| } | |||||
| public static void FALSE(boolean expression) throws ASSERT { | |||||
| FALSE(expression, "[断言失败] - 表达式必须false"); | |||||
| } | |||||
| public static void NULL(Object object, String errorMsgTemplate, Object... params) throws ASSERT { | |||||
| if(Objects.nonNull(object)) | |||||
| THROW(errorMsgTemplate, params); | |||||
| } | |||||
| public static void NULL(Object object) throws ASSERT { | |||||
| NULL(object, "[断言失败] - 对象必须为null"); | |||||
| } | |||||
| public static <T> T NONNULL(T object, String errorMsgTemplate, Object... params) throws ASSERT { | |||||
| if(Objects.isNull(object)) | |||||
| THROW(errorMsgTemplate, params); | |||||
| return object; | |||||
| } | |||||
| public static <T> T NONNULL(T object) throws ASSERT { | |||||
| return NONNULL(object, "[断言失败] - 对象必须非null"); | |||||
| } | |||||
| public static String NOTEMPTY(String text, String errorMsgTemplate, Object... params) throws ASSERT { | |||||
| if(null == text || text.isEmpty() || text.trim().isEmpty()) | |||||
| THROW(errorMsgTemplate, params); | |||||
| return text; | |||||
| } | |||||
| public static String NOTEMPTY(String text) throws ASSERT { | |||||
| return NOTEMPTY(text, "[断言失败] - 字符串必须有非空字符数"); | |||||
| } | |||||
| public static String EMPTY(String text, String errorMsgTemplate, Object... params) throws ASSERT { | |||||
| if(null != text && !text.isEmpty() && !text.trim().isEmpty()) | |||||
| THROW(errorMsgTemplate, params); | |||||
| return text; | |||||
| } | |||||
| public static String EMPTY(String text) throws ASSERT { | |||||
| return EMPTY(text, "[断言失败] - 字符串必须不能包含非空字符"); | |||||
| } | |||||
| public static <T> T[] NOTEMPTY(T[] array, String errorMsgTemplate, Object... params) throws ASSERT { | |||||
| if(null == array || array.length == 0) | |||||
| THROW(errorMsgTemplate, params); | |||||
| return array; | |||||
| } | |||||
| public static <T> T[] NOTEMPTY(T[] array) throws ASSERT { | |||||
| return NOTEMPTY(array, "[断言失败] - 数组必须非空"); | |||||
| } | |||||
| public static <T> Iterable<T> NOTEMPTY(Iterable<T> collection, String errorMsgTemplate, Object... params) throws ASSERT { | |||||
| if(null == collection || !collection.iterator().hasNext() ) | |||||
| THROW(errorMsgTemplate, params); | |||||
| return collection; | |||||
| } | |||||
| public static <T> Iterable<T> NOTEMPTY(Iterable<T> collection) throws ASSERT { | |||||
| return NOTEMPTY(collection, "[断言失败] - 集合容器必须非空"); | |||||
| } | |||||
| public static <K, V> Map<K, V> NOTEMPTY(Map<K, V> map, String errorMsgTemplate, Object... params) throws ASSERT { | |||||
| if(null == map || map.isEmpty() ) | |||||
| THROW(errorMsgTemplate, params); | |||||
| return map; | |||||
| } | |||||
| public static <K, V> Map<K, V> NOTEMPTY(Map<K, V> map) throws ASSERT { | |||||
| return NOTEMPTY(map, "[断言失败] - 关联容器必须非空"); | |||||
| } | |||||
| } | |||||
| @@ -0,0 +1,30 @@ | |||||
| package com.ruoyi.common.utils; | |||||
| import org.apache.commons.codec.binary.Base64; | |||||
| import javax.crypto.Cipher; | |||||
| import java.security.*; | |||||
| import java.security.interfaces.RSAPrivateKey; | |||||
| import java.security.spec.PKCS8EncodedKeySpec; | |||||
| /** | |||||
| * 密钥工具类 | |||||
| * | |||||
| * @author nsgk | |||||
| */ | |||||
| public class SecretUtils { | |||||
| public static String decrypt(String str, String privateKey, String...codec) throws Exception { | |||||
| //64位解码加密后的字符串 | |||||
| byte[] inputByte = Base64.decodeBase64(str); | |||||
| //base64编码的私钥 | |||||
| byte[] decoded = Base64.decodeBase64(privateKey); | |||||
| RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded)); | |||||
| //RSA解密 | |||||
| Cipher cipher = Cipher.getInstance("RSA"); | |||||
| cipher.init(Cipher.DECRYPT_MODE, priKey); | |||||
| //这里直接new String() , 不用传输,为了直接查看解密后的明文(base64加密后看不懂) | |||||
| String outStr = codec.length > 0 ? new String(cipher.doFinal(inputByte), codec[0]) : new String(cipher.doFinal(inputByte)); | |||||
| // byte[] decode = Base64.getDecoder().decode(inputByte); base64编码后的明文 | |||||
| return outStr; | |||||
| } | |||||
| } | |||||
| @@ -1,6 +1,8 @@ | |||||
| package com.ruoyi.framework.web.exception; | package com.ruoyi.framework.web.exception; | ||||
| import javax.servlet.http.HttpServletRequest; | import javax.servlet.http.HttpServletRequest; | ||||
| import com.ruoyi.common.exception.ASSERT; | |||||
| import org.slf4j.Logger; | import org.slf4j.Logger; | ||||
| import org.slf4j.LoggerFactory; | import org.slf4j.LoggerFactory; | ||||
| import org.springframework.security.access.AccessDeniedException; | import org.springframework.security.access.AccessDeniedException; | ||||
| @@ -111,4 +113,15 @@ public class GlobalExceptionHandler | |||||
| { | { | ||||
| return AjaxResult.error("演示模式,不允许操作"); | return AjaxResult.error("演示模式,不允许操作"); | ||||
| } | } | ||||
| /** | |||||
| * 拦截断言异常: 不输出异常栈 | |||||
| */ | |||||
| @ExceptionHandler(ASSERT.class) | |||||
| public AjaxResult handleAssertFail(ASSERT e, HttpServletRequest request) | |||||
| { | |||||
| String requestURI = request.getRequestURI(); | |||||
| log.error("请求地址'{}',发生未知异常: {}.", requestURI, ASSERT.Output(e)); | |||||
| return AjaxResult.error(e.getMessage()); | |||||
| } | |||||
| } | } | ||||
| @@ -1,6 +1,9 @@ | |||||
| package com.ruoyi.framework.web.service; | package com.ruoyi.framework.web.service; | ||||
| import javax.annotation.Resource; | import javax.annotation.Resource; | ||||
| import com.ruoyi.common.config.RuoYiConfig; | |||||
| import com.ruoyi.common.exception.ASSERT; | |||||
| import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
| import org.springframework.security.authentication.AuthenticationManager; | import org.springframework.security.authentication.AuthenticationManager; | ||||
| import org.springframework.security.authentication.BadCredentialsException; | import org.springframework.security.authentication.BadCredentialsException; | ||||
| @@ -52,6 +55,23 @@ public class SysLoginService | |||||
| @Autowired | @Autowired | ||||
| private ISysConfigService configService; | private ISysConfigService configService; | ||||
| private String D(String inStr) | |||||
| { | |||||
| if(RuoYiConfig.Secret.isDisabled()) | |||||
| return inStr; | |||||
| try | |||||
| { | |||||
| return RuoYiConfig.Secret.decrypt(inStr); | |||||
| } | |||||
| catch(Exception e) | |||||
| { | |||||
| if(RuoYiConfig.Secret.isCompat()) | |||||
| return inStr; | |||||
| else | |||||
| throw new ASSERT("无效登录凭据"); | |||||
| } | |||||
| } | |||||
| /** | /** | ||||
| * 登录验证 | * 登录验证 | ||||
| * | * | ||||
| @@ -63,6 +83,7 @@ public class SysLoginService | |||||
| */ | */ | ||||
| public String login(String username, String password, String code, String uuid) | public String login(String username, String password, String code, String uuid) | ||||
| { | { | ||||
| username = D(username); password = D(password); | |||||
| // 验证码校验 | // 验证码校验 | ||||
| validateCaptcha(username, code, uuid); | validateCaptcha(username, code, uuid); | ||||
| // 登录前置校验 | // 登录前置校验 | ||||