@@ -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); | ||||
// 登录前置校验 | // 登录前置校验 | ||||