update 租赁系统1.1.0 新增谷歌验证相关代码

This commit is contained in:
yyb
2025-12-26 10:07:49 +08:00
parent 379e102ab4
commit b210b114d5
11 changed files with 615 additions and 14 deletions

View File

@@ -1,20 +1,27 @@
package com.m2pool.lease.controller;
import com.m2pool.common.security.annotation.RequiresLogin;
import com.m2pool.lease.annotation.Decrypt;
import com.m2pool.lease.annotation.LoginRequired;
import com.m2pool.lease.dto.*;
import com.m2pool.lease.dto.Result;
import com.m2pool.lease.dto.v2.GoogleInfoDto;
import com.m2pool.lease.service.LeaseUserService;
import com.m2pool.lease.vo.*;
import com.m2pool.lease.vo.EmailCodeVo;
import com.m2pool.lease.vo.UserLoginVo;
import com.m2pool.lease.vo.UserRegisterVo;
import com.m2pool.lease.vo.v2.GoogleBindVo;
import com.m2pool.lease.vo.v2.GoogleCloseVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
import java.util.Map;
import java.util.Objects;
/**
* <p>
@@ -79,5 +86,51 @@ public class LeaseAuthController {
public Result<String> sendUpdatePwdCode(@RequestBody @Valid EmailCodeVo emailCodeVo){
return leaseUserService.sendUpdatePwdCode(emailCodeVo);
}
/*-----------------------------谷歌双重验证接口----------------------------*/
@Decrypt
@LoginRequired
@PostMapping("/bindGoogle")
@ApiOperation(value = "绑定开启谷歌验证")
public Result<String> bindGoogle(@RequestBody @Valid GoogleBindVo googleBindVo){
return leaseUserService.bindGoogle(googleBindVo);
}
@LoginRequired
@PostMapping("/getBindInfo")
@ApiOperation(value = "获取谷歌验证器安全码和验证码")
public Result<GoogleInfoDto> getBindInfo(){
return leaseUserService.getBindInfo();
}
@LoginRequired
@PostMapping("/sendOpenGoogleCode")
@ApiOperation(value = "开启谷歌验证器 发送邮箱验证码")
public Result<String> sendOpenGoogleCode(){
return leaseUserService.sendOpenGoogleCode();
}
@LoginRequired
@PostMapping("/sendCloseGoogleCode")
@ApiOperation(value = "关闭谷歌验证器 发送邮箱验证码")
public Result<String> sendCloseGoogleCode(){
return leaseUserService.sendCloseGoogleCode();
}
@LoginRequired
@PostMapping("/closeStepTwo")
@ApiOperation(value = "关闭双重验证")
public Result<String> closeStepTwo(@Valid @RequestBody GoogleCloseVo vo){
return leaseUserService.closeStepTwo(vo);
}
/*-----------------------------谷歌双重验证接口----------------------------*/
}

View File

@@ -0,0 +1,15 @@
package com.m2pool.lease.dto.v2;
import lombok.Data;
import java.io.Serializable;
@Data
public class GoogleInfoDto implements Serializable {
/** 谷歌验证码 */
private String secret;
/** 二维码 */
private String img;
}

View File

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.m2pool.common.datasource.annotation.GAuth;
import com.m2pool.lease.entity.GoogleInfo;
import com.m2pool.lease.entity.LeaseUser;
import com.m2pool.lease.vo.v2.UserGoogleAuthInfo;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -17,6 +18,41 @@ import org.apache.ibatis.annotations.Param;
*/
@Mapper
public interface LeaseUserMapper extends BaseMapper<LeaseUser> {
@GAuth
public GoogleInfo getGoogleInfoByEmail(@Param("email") String email);
/**
* 获取谷歌验证相关信息
* @param email
* @return
*/
GoogleInfo getGoogleInfoByEmail(@Param("email") String email);
/**
* 验证码是否已存在
* @param secret
* @return
*/
boolean checkSecretIfExist(@Param("secret") String secret);
/**
* 获取用户谷歌验证码相关信息(包括密码)
* @param email
* @return
*/
UserGoogleAuthInfo getUserInfoByEmail(@Param("email") String email);
/**
* 绑定谷歌验证码
* @param info
* @return
*/
boolean bingGoogleAuthenticator(@Param("info") UserGoogleAuthInfo info);
/**
* 关闭谷歌验证码
* @param info
* @return
*/
boolean closeGoogleAuthenticator(@Param("info") UserGoogleAuthInfo info);
}

View File

@@ -19,6 +19,11 @@ public class RedisAuthKey {
private static final String USER_LOGIN_KEY= "user:login";
private static final String GOOGLE_OPEN_CODE_KEY= "google:open:code";
private static final String GOOGLE_CLOSE_CODE_KEY= "google:close:code";
/**
* 获取登录验证码key
* @param email
@@ -73,4 +78,22 @@ public class RedisAuthKey {
return USER_LOGIN_KEY + ":" + email;
}
/**
* 获取用户谷歌验证码 key
* @param email
* @return
*/
public static String getGoogleOpenCodeKey(String email) {
return GOOGLE_OPEN_CODE_KEY + ":" + email;
}
/**
* 获取用户谷歌关闭验证码 key
* @param email
* @return
*/
public static String getGoogleCloseCodeKey(String email) {
return GOOGLE_CLOSE_CODE_KEY + ":" + email;
}
}

View File

@@ -2,12 +2,16 @@ package com.m2pool.lease.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.m2pool.lease.dto.*;
import com.m2pool.lease.dto.v2.GoogleInfoDto;
import com.m2pool.lease.entity.LeaseUser;
import com.m2pool.lease.vo.*;
import com.m2pool.lease.vo.v2.GoogleBindVo;
import com.m2pool.lease.vo.v2.GoogleCloseVo;
import org.springframework.web.bind.annotation.RequestBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.validation.Valid;
import java.util.List;
import java.util.Map;
@@ -21,6 +25,8 @@ import java.util.Map;
*/
public interface LeaseUserService extends IService<LeaseUser> {
/*---------------用户权限-----------------------*/
/**
* 用户登录/注册
* @param userLoginVo
@@ -74,9 +80,50 @@ public interface LeaseUserService extends IService<LeaseUser> {
*/
Result<String> logout();
//---------------用户权限-----------------------
/*---------------用户权限-----------------------*/
/*---------------谷歌双重验证-----------------------*/
/**
* 绑定谷歌验证码
* @param googleBindVo
* @return
*/
Result<String> bindGoogle( GoogleBindVo googleBindVo);
/**
* 获取谷歌验证器安全码和验证码
* @return
*/
Result<GoogleInfoDto> getBindInfo();
/**
* 发送谷歌验证器邮箱验证码
* @return
*/
Result<String> sendOpenGoogleCode();
/**
* 发送关闭谷歌验证 邮箱验证码
* @return
*/
Result<String> sendCloseGoogleCode();
/**
* 关闭双重验证
* @param vo
* @return
*/
Result<String> closeStepTwo(GoogleCloseVo vo);
/*---------------谷歌双重验证-----------------------*/
/*---------------用户信息-----------------------*/
/**
* 绑定钱包
* @param chainAndCoinVo
@@ -154,4 +201,7 @@ public interface LeaseUserService extends IService<LeaseUser> {
* @return
*/
String getClientVersion();
/*---------------用户信息-----------------------*/
}

View File

@@ -1,7 +1,6 @@
package com.m2pool.lease.service.impl;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.dynamic.datasource.annotation.DSTransactional;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
@@ -10,14 +9,16 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.m2pool.common.core.Result.R;
import com.m2pool.common.core.exception.ServiceException;
import com.m2pool.common.core.text.Convert;
import com.m2pool.common.core.utils.CodeUtils;
import com.m2pool.common.core.utils.GoogleAuthenticator;
import com.m2pool.common.core.utils.StringUtils;
import com.m2pool.common.core.utils.uuid.IdUtils;
import com.m2pool.common.core.web.Result.AjaxResult;
import com.m2pool.common.redis.service.RedisService;
import com.m2pool.common.security.utils.SecurityUtils;
import com.m2pool.lease.dto.*;
import com.m2pool.lease.dto.v2.GoogleInfoDto;
import com.m2pool.lease.entity.*;
import com.m2pool.lease.exception.AuthException;
import com.m2pool.lease.exception.PaymentException;
@@ -29,9 +30,14 @@ import com.m2pool.lease.redis.RedisAuthKey;
import com.m2pool.lease.service.LeaseUserService;
import com.m2pool.lease.utils.*;
import com.m2pool.lease.vo.*;
import com.m2pool.lease.vo.v2.GoogleBindVo;
import com.m2pool.lease.vo.v2.GoogleCloseVo;
import com.m2pool.lease.vo.v2.UserGoogleAuthInfo;
import com.m2pool.system.api.entity.EmailCodeEntity;
import com.m2pool.system.api.entity.EmailEntity;
import com.m2pool.system.api.entity.EmailTemplateEntity;
import com.m2pool.system.api.entity.SysUser;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.javamail.JavaMailSenderImpl;
@@ -554,6 +560,305 @@ public class LeaseUserServiceImpl extends ServiceImpl<LeaseUserMapper, LeaseUser
}
}
@Value("${myenv.domain}")
private String domain;
@Override
public Result<String> bindGoogle(GoogleBindVo googleBindVo) {
// 参数校验
if (StringUtils.isNull(googleBindVo)){
return Result.fail("未获取到请求参数");
}
String email = UserThreadLocal.getUserEmail();
if(StringUtils.isNull(email)){
return Result.fail("token解析异常");
}
// 根据邮箱查询是否已绑定
UserGoogleAuthInfo info = leaseUserMapper.getUserInfoByEmail(email);
if(StringUtils.isBlank(info.getSecret())){
//未绑定 正常走绑定流程
//校验gcode
boolean gResult = GoogleAuthenticator.checkCode(googleBindVo.getSecret(),
googleBindVo.getGCode(), System.currentTimeMillis());
if(!gResult){
return Result.fail("谷歌验证码错误");
}
//校验邮箱验证码
String redisKey = RedisAuthKey.getGoogleOpenCodeKey(email);
if(redisService.hasKey(redisKey)){
Object o = redisService.getCacheObject(redisKey);//google:email:code
EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class);
//验证验证码
if(!googleBindVo.getECode().equals(emailCodeEntity.getEmailCode())){
return Result.fail("邮箱验证码错误");
}
}else {
return Result.fail("邮箱验证码 未获取或已过期(有效期10分钟),请重新获取验证码");
}
//校验密码
if (!SecurityUtils.matchesPassword(googleBindVo.getPwd(), info.getPwd())){
return Result.fail("密码错误");
}
//入库
UserGoogleAuthInfo bingInfo = new UserGoogleAuthInfo();
bingInfo.setSecret(googleBindVo.getSecret());
bingInfo.setStatus(1);
bingInfo.setEmail(info.getEmail());
boolean bingResult = leaseUserMapper.bingGoogleAuthenticator(bingInfo);
if(bingResult){
return Result.success("绑定成功");
}else {
return Result.fail("请求服务器失败,请重试");
}
}else {
if(info.getStatus() == 2){
return Result.fail("您绑定但是关闭了双重验证,请重新打开");
}if(info.getStatus() == 1){
return Result.fail("当前用户已绑定过谷歌登录器");
}else {
return Result.fail("当前用户已绑定过谷歌登录器.");
}
}
}
@Override
public Result<GoogleInfoDto> getBindInfo() {
String email = UserThreadLocal.getUserEmail();
if(StringUtils.isNull(email)){
return Result.fail("token解析异常");
}
GoogleInfo info = leaseUserMapper.getGoogleInfoByEmail(email);
GoogleInfoDto dto = new GoogleInfoDto();
if(StringUtils.isBlank(info.getSecret())){
//生成谷歌验证码
String secret = GoogleAuthenticator.getSecretKey();
String qrCodeText = GoogleAuthenticator.getQrCodeText(secret, email, domain);
String img = QrCodeUtils.creatRrCode(qrCodeText, 200, 200);
boolean ifExist = leaseUserMapper.checkSecretIfExist(secret);
if(ifExist){
secret = GoogleAuthenticator.getSecretKey();
qrCodeText = GoogleAuthenticator.getQrCodeText(secret, email, domain);
img = QrCodeUtils.creatRrCode(qrCodeText,200,200);
ifExist = leaseUserMapper.checkSecretIfExist(secret);
if(ifExist){
return Result.fail("网络异常,请稍后再试");
}
}
dto.setSecret(secret);
dto.setImg(img);
}else {
//数据库已有谷歌验证码 直接返回
BeanUtils.copyProperties(info,dto);
dto.setImg(QrCodeUtils.creatRrCode( GoogleAuthenticator.getQrCodeText(dto.getSecret(),email , domain),200,200));
}
return Result.success(dto);
}
@Override
@Transactional
public Result<String> sendOpenGoogleCode() {
String email = UserThreadLocal.getUserEmail();
if(StringUtils.isNull(email)){
return Result.fail("token解析异常");
}
boolean success = true;
//判断用户是不是恶意刷邮箱,在规定时间内进行的
String redisKey = RedisAuthKey.getGoogleOpenCodeKey( email);
if (redisService.hasKey(redisKey)) {
Object o = redisService.getCacheObject(redisKey);
EmailCodeValue emailCodeValue = JSON.parseObject(JSON.toJSONString(o), EmailCodeValue.class);
if (emailCodeValue.getTimes() > 4) {
return Result.fail("请求次数过多请10分钟后再试,(同一用户10分钟内只能请求4次)");
} else {
String emailCode = CodeUtils.creatCode(6);
emailCodeValue.setEmailCode(emailCode);
emailCodeValue.setTimes(emailCodeValue.getTimes() + 1);
long overTime = redisService.getExpire(redisKey);
redisService.setCacheObject(redisKey, emailCodeValue, overTime, TimeUnit.SECONDS
);
success = sendEmail(email,emailCode);
}
} else {
String emailCode = CodeUtils.creatCode(6);
// 最多允许用户在10分钟内发送2次的邮箱验证
// 0s倒计时后用户可以再发送验证码但是间隔在10分钟内只能再发送1次
EmailCodeValue emailCodeValue = new EmailCodeValue();
emailCodeValue.setEmail(email);
emailCodeValue.setEmailCode(emailCode);
emailCodeValue.setTimes(1);
//设置失效时间10分钟
redisService.setCacheObject(redisKey, emailCodeValue,
10L, TimeUnit.MINUTES
);
success = sendEmail(email,emailCode);
}
return success ? Result.success("发送邮箱验证码成功") : Result.fail("发送邮箱验证码失败,请稍后重试");
}
public boolean sendEmail(String email,String code){
String text = "您正在绑定谷歌验证器!\n" +
"您的邮箱验证验证码是:\n\t"+code +
"\n此验证码10分钟有效。";
try {
//true 代表支持复杂的类型
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true);
//邮件发信人
mimeMessageHelper.setFrom(sendMailer);
//邮件收信人 1或多个
mimeMessageHelper.setTo(email);
//邮件主题
mimeMessageHelper.setSubject("[M2Pool] 邮箱验证码");
//邮件内容
mimeMessageHelper.setText(text);
//邮件发送时间
mimeMessageHelper.setSentDate(new Date());
//发送邮件
javaMailSender.send(mimeMessageHelper.getMimeMessage());
//System.out.println("发送邮件成功:"+sendMailer+"->"+to);
return true;
} catch (Exception e) {
System.out.println("发送邮件失败:"+e.getMessage());
return false;
}
}
@Override
public Result<String> sendCloseGoogleCode() {
String email = SecurityUtils.getUsername();
if(StringUtils.isNull(email)){
return Result.fail("token解析异常");
}
boolean success;
//判断用户是不是恶意刷邮箱,在规定时间内进行的
String redisKey = RedisAuthKey.getGoogleCloseCodeKey( email);
if (redisService.hasKey(redisKey)) {
Object o = redisService.getCacheObject(redisKey);
EmailCodeValue emailCodeValue = JSON.parseObject(JSON.toJSONString(o), EmailCodeValue.class);
if (emailCodeValue.getTimes() > 4) {
return Result.fail("请求次数过多请10分钟后再试,(同一用户10分钟内只能请求4次)");
} else {
String emailCode = CodeUtils.creatCode(6);
emailCodeValue.setEmailCode(emailCode);
emailCodeValue.setTimes(emailCodeValue.getTimes() + 1);
long overTime = redisService.getExpire(redisKey);
redisService.setCacheObject(redisKey, emailCodeValue, overTime, TimeUnit.SECONDS
);
success = sendEmail(email,emailCode);
}
} else {
String emailCode = CodeUtils.creatCode(6);
// 最多允许用户在10分钟内发送2次的邮箱验证
// 0s倒计时后用户可以再发送验证码但是间隔在10分钟内只能再发送1次
EmailCodeEntity emailCodeEntity = new EmailCodeEntity();
emailCodeEntity.setEmail(email);
emailCodeEntity.setEmailCode(emailCode);
emailCodeEntity.setTimes(1);
//设置失效时间10分钟
redisService.setCacheObject(redisKey, emailCodeEntity,
10L, TimeUnit.MINUTES
);
success = sendCloseEmail(email,emailCode);
}
return success ? Result.success("发送邮箱验证码成功") : Result.fail("发送邮箱验证码失败,请稍后重试");
}
public boolean sendCloseEmail(String email,String code){
String text = "M2Pool:\n" +
"您正在解绑谷歌验证器!\n" +
"您的邮箱验证验证码是:\n\t"+code +
"\n此验证码10分钟有效。";
try {
//true 代表支持复杂的类型
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true);
//邮件发信人
mimeMessageHelper.setFrom(sendMailer);
//邮件收信人 1或多个
mimeMessageHelper.setTo(email);
//邮件主题
mimeMessageHelper.setSubject("[M2Pool] 邮箱验证码");
//邮件内容
mimeMessageHelper.setText(text);
//邮件发送时间
mimeMessageHelper.setSentDate(new Date());
//发送邮件
javaMailSender.send(mimeMessageHelper.getMimeMessage());
//System.out.println("发送邮件成功:"+sendMailer+"->"+to);
return true;
} catch (Exception e) {
System.out.println("发送邮件失败:"+e.getMessage());
return false;
}
}
@Override
public Result<String> closeStepTwo(GoogleCloseVo vo) {
// 参数校验
if (StringUtils.isNull(vo)){
return Result.fail("未获取到请求参数");
}
String email = SecurityUtils.getUsername();
if(StringUtils.isNull(email)){
return Result.fail("token解析异常");
}
// 根据邮箱查询是否已绑定
UserGoogleAuthInfo info = leaseUserMapper.getUserInfoByEmail(email);
if(StringUtils.isBlank(info.getSecret())){
return Result.success();
}else {
//已绑定 正常走关闭谷歌验证流程
//校验gcode
boolean gResult = GoogleAuthenticator.checkCode(info.getSecret(), vo.getGCode(), System.currentTimeMillis());
if(!gResult){
return Result.fail("谷歌验证码错误");
}
//校验邮箱验证码
String redisKey = RedisAuthKey.getGoogleCloseCodeKey( email);
if(redisService.hasKey(redisKey)){
Object o = redisService.getCacheObject(redisKey);//google:email:close
EmailCodeValue emailCodeValue = JSON.parseObject(JSON.toJSONString(o), EmailCodeValue.class);
//验证验证码
if(!vo.getECode().equals(emailCodeValue.getEmailCode())){
return Result.fail("邮箱验证码错误");
}
}else {
return Result.fail("邮箱验证码 未获取或已过期(有效期10分钟),请重新获取验证码");
}
//入库
boolean bingResult = leaseUserMapper.closeGoogleAuthenticator(info);
if(bingResult){
return Result.success();
}else {
return Result.fail("请求服务器失败,请重试");
}
}
}
@Override
@Transactional

View File

@@ -0,0 +1,37 @@
package com.m2pool.lease.vo.v2;
import com.m2pool.lease.annotation.EncryptedField;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class GoogleBindVo {
/**
* 谷歌验证器 安全码
*/
@NotBlank
public String secret;
/**
* 谷歌验证器验证码
*/
public long gCode;
/**
* 邮箱验证码
*/
@NotBlank
public String eCode;
/**
* 当前用户密码
*/
@EncryptedField
@NotBlank
public String pwd;
}

View File

@@ -0,0 +1,22 @@
package com.m2pool.lease.vo.v2;
import lombok.Data;
import javax.validation.constraints.NotBlank;
@Data
public class GoogleCloseVo {
/**
* 谷歌验证器验证码
*/
public long gCode;
/**
* 邮箱验证码
*/
@NotBlank
public String eCode;
}

View File

@@ -0,0 +1,23 @@
package com.m2pool.lease.vo.v2;
import lombok.Data;
import java.io.Serializable;
@Data
public class UserGoogleAuthInfo implements Serializable {
/** 邮箱 */
private String email;
/** 谷歌验证码 */
private String secret;
private String pwd;
/** 谷歌验证码 */
private int status;
}

View File

@@ -96,3 +96,9 @@ netty:
port: 2345
image:
prefix: https://test.m2pool.com
myenv:
domain: https://www.m2pool.com
path: /var/www/html/web
img: /img
filepath: /home/ubuntu/prod

View File

@@ -16,16 +16,47 @@
id, user_id, password, create_time, update_time
</sql>
<select id="getGoogleInfoByEmail" resultType="com.m2pool.lease.entity.GoogleInfo">
select
email,
user_id as email,
google_auth secret,
google_status status
from
sys_user
lease_user
where
email = #{email}
user_id = #{email}
limit 1
</select>
<select id="checkSecretIfExist" resultType="java.lang.Boolean">
select COUNT(1) from lease_user where google_auth = #{secret}
</select>
<select id="getUserInfoByEmail" resultType="com.m2pool.lease.vo.v2.UserGoogleAuthInfo">
select
user_id email,
password pwd,
google_auth secret,
google_status status
from
lease_user
where
user_id = #{email}
limit 1
</select>
<update id="bingGoogleAuthenticator">
update lease_user
set
google_auth = #{info.secret},
google_status = #{info.status},
update_time = sysdate()
where user_id = #{info.email}
</update>
<update id="closeGoogleAuthenticator">
update lease_user
set
google_auth = '',
google_status = 0
where user_id = #{info.email}
</update>
</mapper>