fix(sms): 修复短信验证码发送功能的安全性和稳定性问题
- 添加了参数校验,防止空参数导致的异常 - 替换了硬编码的阿里云密钥配置,支持租户自定义配置 - 修复了随机数生成器的安全问题,使用ThreadLocalRandom替代Random - 添加了日志记录功能,便于问题排查和监控 - 优化了Redis缓存键的存储逻辑,兼容历史数据格式 - 增强了异常处理机制,提供更详细的错误信息反馈 - 修复了短信模板参数格式问题,确保验证码正确传递 - 添加了手机号脱敏处理,保护用户隐私安全
This commit is contained in:
@@ -47,6 +47,7 @@ import com.wf.captcha.SpecCaptcha;
|
||||
import io.jsonwebtoken.Claims;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.transaction.annotation.Isolation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@@ -61,8 +62,8 @@ import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
import static com.gxwebsoft.common.core.constants.WebsiteConstants.CACHE_KEY_UNIVERSAL_PASSWORD;
|
||||
import static com.gxwebsoft.common.core.constants.WebsiteConstants.CACHE_KEY_VERIFICATION_CODE_BY_DEV_SMS;
|
||||
@@ -73,6 +74,7 @@ import static com.gxwebsoft.common.core.constants.WebsiteConstants.CACHE_KEY_VER
|
||||
* @author WebSoft
|
||||
* @since 2018-12-24 16:10:11
|
||||
*/
|
||||
@Slf4j
|
||||
@Tag(name = "登录认证")
|
||||
@RestController
|
||||
@RequestMapping("/api")
|
||||
@@ -441,11 +443,15 @@ public class MainController extends BaseController {
|
||||
@Operation(summary = "发送短信验证码")
|
||||
@PostMapping("/sendSmsCaptcha")
|
||||
public ApiResult<?> sendSmsCaptcha(@RequestBody SmsCaptchaParam param) {
|
||||
// 默认配置
|
||||
String accessKeyId = "LTAI5tEsyhW4GCKbds1qsopg";
|
||||
String accessKeySecret = "zltFlQrYVAoq2KMFDWgLa3GhkMNeyO";
|
||||
String userTemplateId = "SMS_481670203";
|
||||
String sign = "网宿信息";
|
||||
if (param == null) {
|
||||
return fail("参数不能为空");
|
||||
}
|
||||
// 默认配置(当租户未配置短信服务时使用)
|
||||
String accessKeyId = "LTAI5t7jGTFTbpSLzzXY8HzP";
|
||||
String accessKeySecret = "Z22EPJyUhQaIZfEEmZ4Hdbw6xZibCb";
|
||||
String templateCode = "SMS_481670203";
|
||||
String signName = "网宿信息";
|
||||
String regionId = "cn-hangzhou";
|
||||
|
||||
if (!CommonUtil.isValidPhoneNumber(param.getPhone())) {
|
||||
return fail("请输入有效的手机号码");
|
||||
@@ -459,53 +465,90 @@ public class MainController extends BaseController {
|
||||
}
|
||||
|
||||
|
||||
// 读取租户的短信配置
|
||||
if (getTenantId() != null) {
|
||||
String string = redisUtil.get("setting:sms:" + getTenantId());
|
||||
if (string != null) {
|
||||
JSONObject jsonObject = JSONObject.parseObject(string);
|
||||
accessKeyId = jsonObject.getString("accessKeyId");
|
||||
accessKeySecret = jsonObject.getString("accessKeySecret");
|
||||
userTemplateId = jsonObject.getString("userTemplateId");
|
||||
sign = jsonObject.getString("sign");
|
||||
Integer tenantId = getTenantId();
|
||||
if (tenantId == null && StrUtil.isNotBlank(param.getTenantId())) {
|
||||
try {
|
||||
tenantId = Integer.valueOf(param.getTenantId());
|
||||
} catch (NumberFormatException e) {
|
||||
return fail("租户ID格式不正确");
|
||||
}
|
||||
}
|
||||
|
||||
DefaultProfile profile = DefaultProfile.getProfile("regionld", accessKeyId, accessKeySecret);
|
||||
// 读取租户的短信配置(SettingController写入的key格式为:sms:{tenantId})
|
||||
if (tenantId != null) {
|
||||
String settingJson = redisUtil.get("sms:" + tenantId);
|
||||
// 兼容历史key
|
||||
if (StrUtil.isBlank(settingJson)) {
|
||||
settingJson = redisUtil.get("setting:sms:" + tenantId);
|
||||
}
|
||||
if (StrUtil.isNotBlank(settingJson)) {
|
||||
JSONObject jsonObject = JSONObject.parseObject(settingJson);
|
||||
accessKeyId = StrUtil.blankToDefault(jsonObject.getString("accessKeyId"), accessKeyId);
|
||||
accessKeySecret = StrUtil.blankToDefault(jsonObject.getString("accessKeySecret"), accessKeySecret);
|
||||
signName = StrUtil.blankToDefault(
|
||||
StrUtil.blankToDefault(jsonObject.getString("signName"), jsonObject.getString("sign")),
|
||||
signName
|
||||
);
|
||||
templateCode = StrUtil.blankToDefault(
|
||||
StrUtil.blankToDefault(jsonObject.getString("templateCode"), jsonObject.getString("userTemplateId")),
|
||||
templateCode
|
||||
);
|
||||
regionId = StrUtil.blankToDefault(jsonObject.getString("regionId"), regionId);
|
||||
}
|
||||
}
|
||||
|
||||
if (StrUtil.isBlank(accessKeyId) || StrUtil.isBlank(accessKeySecret) || StrUtil.isBlank(signName) || StrUtil.isBlank(templateCode)) {
|
||||
return fail("短信服务未配置,请在系统设置中完善短信配置");
|
||||
}
|
||||
|
||||
DefaultProfile profile = DefaultProfile.getProfile(regionId, accessKeyId, accessKeySecret);
|
||||
IAcsClient client = new DefaultAcsClient(profile);
|
||||
CommonRequest request = new CommonRequest();
|
||||
request.setSysMethod(MethodType.POST);
|
||||
request.setSysDomain("dysmsapi.aliyuncs.com");
|
||||
request.setSysVersion("2017-05-25");
|
||||
request.setSysAction("SendSms");
|
||||
request.putQueryParameter("RegionId", "cn-hangzhou");
|
||||
request.putQueryParameter("RegionId", regionId);
|
||||
request.putQueryParameter("PhoneNumbers", param.getPhone());
|
||||
request.putQueryParameter("SignName", sign);
|
||||
request.putQueryParameter("TemplateCode", userTemplateId);
|
||||
request.putQueryParameter("SignName", signName);
|
||||
request.putQueryParameter("TemplateCode", templateCode);
|
||||
// 生成短信验证码
|
||||
Random randObj = new Random();
|
||||
String code = Integer.toString(100000 + randObj.nextInt(900000));
|
||||
request.putQueryParameter("TemplateParam", "{\"code\":" + code + "}");
|
||||
String code = Integer.toString(ThreadLocalRandom.current().nextInt(100000, 1000000));
|
||||
JSONObject templateParam = new JSONObject();
|
||||
templateParam.put("code", code);
|
||||
request.putQueryParameter("TemplateParam", templateParam.toJSONString());
|
||||
try {
|
||||
CommonResponse response = client.getCommonResponse(request);
|
||||
String json = response.getData();
|
||||
Gson g = new Gson();
|
||||
HashMap result = g.fromJson(json, HashMap.class);
|
||||
if ("OK".equals(result.get("Message"))) {
|
||||
System.out.println("短信发送成功========================" + result);
|
||||
if ("OK".equals(result.get("Message")) || "OK".equals(result.get("Code"))) {
|
||||
log.info("短信发送成功 phone={}, result={}", DesensitizedUtil.mobilePhone(param.getPhone()), result);
|
||||
cacheClient.set(param.getPhone(), code, 5L, TimeUnit.MINUTES);
|
||||
String key = "code:" + param.getPhone();
|
||||
redisUtil.set(key, code, 5L, TimeUnit.MINUTES);
|
||||
return success("发送成功", result.get("Message"));
|
||||
} else {
|
||||
log.warn("短信发送失败 phone={}, result={}", DesensitizedUtil.mobilePhone(param.getPhone()), result);
|
||||
return fail("发送失败");
|
||||
}
|
||||
} catch (ServerException e) {
|
||||
e.printStackTrace();
|
||||
log.error("短信发送失败(ServerException) phone={}", DesensitizedUtil.mobilePhone(param.getPhone()), e);
|
||||
return fail("发送失败");
|
||||
} catch (ClientException e) {
|
||||
e.printStackTrace();
|
||||
log.error(
|
||||
"短信发送失败(ClientException) phone={}, errCode={}, errMsg={}, requestId={}",
|
||||
DesensitizedUtil.mobilePhone(param.getPhone()),
|
||||
e.getErrCode(),
|
||||
e.getErrMsg(),
|
||||
e.getRequestId(),
|
||||
e
|
||||
);
|
||||
if ("InvalidAccessKeyId.NotFound".equals(e.getErrCode()) || "SignatureDoesNotMatch".equals(e.getErrCode())) {
|
||||
return fail("短信配置错误,请检查AccessKeyId/AccessKeySecret是否正确");
|
||||
}
|
||||
return fail("发送失败");
|
||||
}
|
||||
return fail("发送失败");
|
||||
}
|
||||
|
||||
@OperationLog
|
||||
|
||||
Reference in New Issue
Block a user