登录接口RSA加密

v1.4.1
Parker 5 years ago
parent b10d524526
commit df689c7ffd

@ -95,6 +95,7 @@ public class ShiroConfig {
// 登录接口拦截 // 登录接口拦截
filterMap.put("/sys/login", "anon"); filterMap.put("/sys/login", "anon");
filterMap.put("/sys/publicKey", "anon");
filterMap.put("/sys/slipCount", "anon"); filterMap.put("/sys/slipCount", "anon");
filterMap.put("/captcha.jpg", "anon"); filterMap.put("/captcha.jpg", "anon");

@ -69,6 +69,9 @@ public enum CoreMsg implements BaseMsg {
/** 其他 */ /** 其他 */
OTHER_EXCEPTION_LIMITER(10700,"当前系统繁忙,请稍后再试"), OTHER_EXCEPTION_LIMITER(10700,"当前系统繁忙,请稍后再试"),
OTHER_EXCEPTION_RSA_EN(10701,"RSA非对称加密失败"),
OTHER_EXCEPTION_RSA_DE(10701,"RSA非对称解密失败"),
OTHER_EXCEPTION_RSA_REFLEX(10702,"RSA非对称解密反射失败"),
; ;

@ -47,6 +47,7 @@ public enum TokenMsg implements BaseMsg {
EXCEPTION_LOGIN_ACCOUNT_LOCK(12104,"账号已锁定,请{}后,再次尝试"), EXCEPTION_LOGIN_ACCOUNT_LOCK(12104,"账号已锁定,请{}后,再次尝试"),
EXCEPTION_LOGIN_TENANT_NOT_USABLE(12105,"租户未启用,请联系管理员"), EXCEPTION_LOGIN_TENANT_NOT_USABLE(12105,"租户未启用,请联系管理员"),
EXCEPTION_LOGIN_NULL(12106,"请输入账号密码"), EXCEPTION_LOGIN_NULL(12106,"请输入账号密码"),
EXCEPTION_LOGIN_DECRYPT(12107,"登录账号密码解析失败"),
/** /**
* *

@ -0,0 +1,173 @@
/**
* Copyright 2020 OPSLI https://www.opsli.com
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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;
}
}

@ -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<String, Object> 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<String, Object> stringObjectMap = Convert.toMap(String.class, Object.class, decryptedData);
//Map<String, Object> stringObjectMap = Convert.toMap(String.class, Object.class, decryptedDataToObj);
System.out.println(123);
}
}
@Data
class Test1 {
private String id;
private String name;
private Integer age;
}

@ -47,7 +47,6 @@ public class LoginForm {
/** 验证码 */ /** 验证码 */
@ApiModelProperty(value = "验证码") @ApiModelProperty(value = "验证码")
@ValidationArgs(ValiArgsType.IS_NOT_NULL)
@ValidationArgsLenMax(30) @ValidationArgsLenMax(30)
private String captcha; private String captcha;

@ -0,0 +1,41 @@
/**
* Copyright 2020 OPSLI https://www.opsli.com
* <p>
* 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
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* 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;
}

@ -15,12 +15,15 @@
*/ */
package org.opsli.modulars.system.login.web; 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 com.google.common.collect.Maps;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.tomcat.util.http.fileupload.IOUtils; import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.opsli.api.base.result.ResultVo; 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.tenant.TenantModel;
import org.opsli.api.wrapper.system.user.UserModel; import org.opsli.api.wrapper.system.user.UserModel;
import org.opsli.common.annotation.Limiter; 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.common.utils.OutputStreamUtil;
import org.opsli.core.msg.TokenMsg; import org.opsli.core.msg.TokenMsg;
import org.opsli.core.security.shiro.realm.JwtRealm; import org.opsli.core.security.shiro.realm.JwtRealm;
import org.opsli.core.utils.CaptchaUtil; import org.opsli.core.utils.*;
import org.opsli.core.utils.TenantUtil;
import org.opsli.core.utils.UserTokenUtil;
import org.opsli.core.utils.UserUtil;
import org.opsli.modulars.system.login.entity.LoginForm; 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.opsli.modulars.system.user.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired; 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.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -65,6 +67,10 @@ import java.util.Map;
@RestController @RestController
public class LoginRestController { public class LoginRestController {
/** 登录是否开启 RSA加密 */
@Value("${opsli.login.loginRsa:false}")
private boolean loginRsa;
@Autowired @Autowired
private IUserService iUserService; private IUserService iUserService;
@ -75,11 +81,36 @@ public class LoginRestController {
@Limiter @Limiter
@ApiOperation(value = "登录", notes = "登录") @ApiOperation(value = "登录", notes = "登录")
@PostMapping("/sys/login") @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){ if(form == null){
throw new TokenException(TokenMsg.EXCEPTION_LOGIN_NULL); throw new TokenException(TokenMsg.EXCEPTION_LOGIN_NULL);
} }
// 验证登录对象
ValidationUtil.verify(form);
// 判断账号是否临时锁定 // 判断账号是否临时锁定
UserTokenUtil.verifyLockAccount(form.getUsername()); 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) { public static void main(String[] args) {
String passwordStr = "Aa123456"; String passwordStr = "Aa123456";

@ -36,7 +36,6 @@ import java.util.Set;
** **
*/ */
@Slf4j @Slf4j
@RestController
@ApiRestController("/tools/searchhis") @ApiRestController("/tools/searchhis")
public class SearchHisRestController { public class SearchHisRestController {

@ -35,7 +35,7 @@ spring:
master: master:
url: jdbc:mysql://127.0.0.1:3306/opsli-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&serverTimezone=Asia/Shanghai url: jdbc:mysql://127.0.0.1:3306/opsli-boot?characterEncoding=UTF-8&useUnicode=true&useSSL=false&tinyInt1isBit=false&serverTimezone=Asia/Shanghai
username: root username: root
password: 12345678 password: MYSQL0p9o8i7u
driver-class-name: com.mysql.cj.jdbc.Driver driver-class-name: com.mysql.cj.jdbc.Driver
# 多数据源配置 # 多数据源配置
#slave-datasource: #slave-datasource:

@ -194,6 +194,8 @@ opsli:
# 登录设置 # 登录设置
login: login:
# 登录开启RSA加密
loginRsa: true
# 失败次数 # 失败次数
slip-count: 5 slip-count: 5
# 失败N次后弹出验证码 (超过验证码阈值 弹出验证码) # 失败N次后弹出验证码 (超过验证码阈值 弹出验证码)
@ -201,6 +203,16 @@ opsli:
# 失败锁定时间(秒) # 失败锁定时间(秒)
slip-lock-speed: 300 slip-lock-speed: 300
# 登录
login:
# 登录开启RSA加密
loginRsa: true
# 失败次数
slip-count: 5
# 锁定时间(秒)
slip-lock-speed: 300
# Excel 最大操作数量 防止OOM # Excel 最大操作数量 防止OOM
excel-max-count: 20000 excel-max-count: 20000

Loading…
Cancel
Save