This commit is contained in:
yyb
2025-05-12 17:00:06 +08:00
parent 9c5b5eba7c
commit 9c93dc1e10
855 changed files with 163903 additions and 0 deletions

81
jxy-auth/pom.xml Normal file
View File

@@ -0,0 +1,81 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>marketall2</artifactId>
<groupId>com.jxy</groupId>
<version>3.5.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>jxy-auth</artifactId>
<description>认证模块:登录认证、权限鉴定等</description>
<dependencies>
<!-- SpringCloud Alibaba Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<!-- SpringCloud Alibaba Nacos Config -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- SpringBoot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringCloud Alibaba Sentinel -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<!-- SpringBoot Actuator -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok-maven-plugin</artifactId>
<scope>provided</scope>
</dependency>
<!-- JXY Common Security-->
<dependency>
<groupId>com.jxy</groupId>
<artifactId>common-security</artifactId>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,21 @@
package com.jxy.auth;
import com.jxy.common.security.annotation.EnableJXYFeignClients;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@EnableJXYFeignClients
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class JXYAuthApplication{
public static void main(String[] args) {
SpringApplication.run(JXYAuthApplication.class,args);
System.out.println("认证授权中心启动成功");
}
}

View File

@@ -0,0 +1,122 @@
package com.jxy.auth.controller;
import com.jxy.auth.entity.*;
import com.jxy.auth.service.SysLoginService;
import com.jxy.auth.service.impl.MaliServiceImpl;
import com.jxy.common.core.Result.R;
import com.jxy.common.core.utils.JwtUtils;
import com.jxy.common.core.utils.StringUtils;
import com.jxy.common.security.auth.AuthUtil;
import com.jxy.common.security.service.TokenService;
import com.jxy.common.security.utils.SecurityUtils;
import com.jxy.system.api.model.LoginUser;
import io.swagger.annotations.Api;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.validation.Valid;
/**
* @Description token 控制
* @Date 2022/5/12 14:59
* @Author 杜懿
*/
@RestController
@Api(tags = "token令牌")
public class TokenController {
@Autowired
private SysLoginService sysLoginService;
@Autowired
private TokenService tokenService;
@Autowired
private MaliServiceImpl maliService;
@PostMapping("login")
public R<?> login(@RequestBody LoginBody loginBody,@RequestHeader("User-Agent") String userAgent)
{
return sysLoginService.login(loginBody);
}
@PostMapping("emailcode")
public R<?> emailCode(@RequestBody GetEmailCodeEntity entity)
{
return maliService.emailCode(entity);
}
@PostMapping("addCreditEmailCode")
public R<?> addCreditEmailCode(@RequestBody GetEmailCodeEntity entity)
{
return maliService.AddCreditCode(entity);
}
@PostMapping("resetPwdCode")
public R<?> resetPwdCode(@RequestBody GetEmailCodeEntity entity)
{
return maliService.resetPwdCode(entity);
}
@GetMapping("hello")
public R<?> hello()
{
return R.success("浏览器安全认证已确认,请重新返回原页面进行登陆");
}
@DeleteMapping("logout")
public R<?> logout(HttpServletRequest request)
{
String token = SecurityUtils.getToken(request);
if (StringUtils.isNotEmpty(token))
{
String username = JwtUtils.getUserName(token);
//删除用户缓存记录
AuthUtil.logoutByToken(token);
// 记录用户退出日志
sysLoginService.logout(username);
}
return R.success("用户登出");
}
@PostMapping("refresh")
public R<?> refresh(HttpServletRequest request)
{
LoginUser loginUser = tokenService.getLoginUser(request);
if (StringUtils.isNotNull(loginUser)){
//刷新令牌有效期
tokenService.refreshToken(loginUser);
return R.success();
}
return R.success();
}
@PostMapping("register")
public R<?> register(@Valid() @RequestBody RegisterBody registerBody)
{
// 用户注册
sysLoginService.register(registerBody);
return R.success("用户"+registerBody.getUserName()+"注册成功");
}
@PostMapping("resetPwd")
public R<?> resetPwd(@Valid @RequestBody ResetPwdBody resetPwdBody)
{
// 重置密码
sysLoginService.resetPwd(resetPwdBody);
return R.success("账号"+ resetPwdBody.getEmail()+"密码重置成功");
}
@PostMapping("sendTextMail")
public R<?> sendTextMail(@Valid @RequestBody EmailEntity entity)
{
maliService.sendTextMailMessage(entity.getEmail(), entity.getSubject(),entity.getText());
return R.success("邮件已发送");
}
}

View File

@@ -0,0 +1,28 @@
package com.jxy.auth.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* @Description 用户登录对象
* @Date 2022/5/12 16:13
* @Author 杜懿
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmailCodeEntity implements Serializable {
/** 用户名或邮箱 */
private String userName;
/** 邮箱 */
private String email;
private String emailCode;
private int times;
}

View File

@@ -0,0 +1,27 @@
package com.jxy.auth.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Email;
import java.io.Serializable;
/**
* @Description 用户登录对象
* @Date 2022/5/12 16:13
* @Author 杜懿
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class EmailEntity implements Serializable {
/** 邮箱 */
@Email
private String email;
private String subject;
private String text;
}

View File

@@ -0,0 +1,36 @@
package com.jxy.auth.entity;
import com.jxy.common.core.xss.Xss;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
/**
* @Description 用户登录对象
* @Date 2022/5/12 16:13
* @Author 杜懿
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class GetEmailCodeEntity {
/** 用户名或邮箱 */
private String userName;
/** 邮箱 */
@NotNull(message = "用户邮箱不能为空")
@Email(message = "邮箱格式错误")
private String email;
//@NotNull(message = "用户密码不能为空")
//@Size(min=6, max=15,message="密码长度必须在 5 ~ 15 字符之间!")
////@Pattern(regexp="^[a-zA-Z0-9|_]+$",message="密码必须由字母、数字、下划线组成!")
//private String password;
}

View File

@@ -0,0 +1,24 @@
package com.jxy.auth.entity;
import lombok.Data;
/**
* @Description 用户登录对象
* @Date 2022/5/12 16:13
* @Author 杜懿
*/
@Data
public class LoginBody {
/** 用户名或邮箱 */
private String userName;
/** 密码 */
private String password;
private String code;
private String uuid;
//private boolean flag = false;
}

View File

@@ -0,0 +1,39 @@
package com.jxy.auth.entity;
import lombok.Data;
import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
/**
* @Description 用户注册对象
* @Date 2022/5/12 16:16
* @Author 杜懿
*/
@Data
public class RegisterBody{
//todo 添加注册详细信息
/** 手机号码 */
private String phone;
@Email(message = "邮箱格式错误")
private String email;
private String emailCode;
/** 用户名 */
@NotNull(message = "用户名不能为空")
@Size(min=3, max=16,message="密码长度必须在 3 ~ 16 字符之间!")
@Pattern(regexp="^[a-zA-Z][a-zA-Z0-9|_]+$",message="用户名需以字母开头")
@Pattern(regexp = "[a-zA-Z0-9|_]+$",message = "用户名仅允许使用字母、数字、下划线,不能包含其他特殊字符或空格")
private String userName;
/** 密码 */
@NotNull(message = "密码不能为空")
//@Size(min=8, max=32,message="密码长度必须在 5 ~ 15 字符之间!")
//@Pattern(regexp="^[a-zA-Z0-9|_]+$",message="密码必须由字母、数字、下划线组成!")
private String password;
}

View File

@@ -0,0 +1,24 @@
package com.jxy.auth.entity;
import lombok.Data;
import javax.validation.constraints.Email;
/**
* @Description 用户重置密码对象
* @Date 2022/5/12 16:16
* @Author 杜懿
*/
@Data
public class ResetPwdBody {
//todo 添加详细信息
@Email
private String email;
private String resetPwdCode;
/** 新密码 */
private String password;
}

View File

@@ -0,0 +1,58 @@
package com.jxy.auth.service;
import com.jxy.auth.entity.GetEmailCodeEntity;
import com.jxy.common.core.Result.R;
/**
* @Description TODO
* @Date 2022/11/18 14:56
* @Author 杜懿
*/
public interface MailService {
/**
* 发送纯文本邮件
* @param to
* @param subject
* @param text
*/
public void sendTextMailMessage(String to,String subject,String text);
/**
* 发送html邮件
* @param to
* @param subject
* @param content
*/
public void sendHtmlMailMessage(String to,String subject,String content);
/**
* 发送带附件的邮件
* @param to 邮件收信人
* @param subject 邮件主题
* @param content 邮件内容
* @param filePath 附件路径
*/
public void sendAttachmentMailMessage(String to,String subject,String content,String filePath);
/**
* 发送注册邮箱验证码
* @param to
* @param code
*/
public void sendCodeMailMessage(String to, String code);
/**
* 发送重置密码邮箱验证码
* @param to
* @param code
*/
public void sendResetPwdMailMessage(String to, String code);
public R<?> emailCode(GetEmailCodeEntity entity);
public R<?> AddCreditCode(GetEmailCodeEntity entity);
public R<?> resetPwdCode(GetEmailCodeEntity entity);
}

View File

@@ -0,0 +1,416 @@
package com.jxy.auth.service;
import com.alibaba.fastjson.JSON;
import com.jxy.auth.entity.EmailCodeEntity;
import com.jxy.auth.entity.LoginBody;
import com.jxy.auth.entity.RegisterBody;
import com.jxy.auth.entity.ResetPwdBody;
import com.jxy.common.core.RedisTransKey;
import com.jxy.common.core.Result.R;
import com.jxy.common.core.constant.Constants;
import com.jxy.common.core.constant.SecurityConstants;
import com.jxy.common.core.constant.UserConstants;
import com.jxy.common.core.enums.UserStatus;
import com.jxy.common.core.exception.ServiceException;
import com.jxy.common.core.text.Convert;
import com.jxy.common.core.utils.DateUtils;
import com.jxy.common.core.utils.ServletUtils;
import com.jxy.common.core.utils.StringUtils;
import com.jxy.common.core.utils.ip.IpUtils;
import com.jxy.common.core.utils.sign.RsaUtils;
import com.jxy.common.redis.service.RedisService;
import com.jxy.common.security.service.TokenService;
import com.jxy.common.security.utils.SecurityUtils;
import com.jxy.system.api.RemoteLogService;
import com.jxy.system.api.RemoteUserService;
import com.jxy.system.api.entity.SysLogininfor;
import com.jxy.system.api.entity.SysUser;
import com.jxy.system.api.model.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.concurrent.TimeUnit;
/**
* @Description 登录校验方法
* @Date 2022/5/12 16:19
* @Author 杜懿
*/
@Component
public class SysLoginService {
@Autowired
private RemoteLogService remoteLogService;
@Autowired
private RemoteUserService remoteUserService;
@Autowired
private TokenService tokenService;
@Autowired
private RedisService redisService;
//public static String PWD_REGEX="^(?![A-Za-z0-9]+$)(?![a-z0-9\\W]+$)(?![A-Za-z\\W]+$)(?![A-Z0-9\\W]+$)[a-zA-Z0-9\\W]{8,32}$";
public static String PWD_REGEX="^(?![a-zA-Z]+$)(?![A-Z0-9]+$)(?![A-Z\\W_]+$)(?![a-z0-9]+$)(?![a-z\\W_]+$)(?![0-9\\W_]+$)[a-zA-Z0-9\\W_]{8,32}$";
/**
* 登录
*/
public R<?> login(LoginBody loginBody)
{
String username = loginBody.getUserName();
//String password= loginBody.getPassword();
String password="";
try {
password = RsaUtils.decryptByPrivateKey(loginBody.getPassword());
password = StringUtils.clean(password);
}catch (Exception e){
return R.fail(401,"加密密码传参有误");
}
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password))
{
recordLogininfor(username, Constants.LOGIN_FAIL, "账号/密码必须填写");
throw new ServiceException("账号/密码必须填写");
}
// 密码如果不在指定范围内 错误
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码不在指定范围");
throw new ServiceException("用户密码不在指定范围");
}
if(username.matches("^[a-zA-Z0-9][\\w\\.-]*[a-zA-Z0-9]@[a-zA-Z0-9][\\w\\.-]*[a-zA-Z0-9]\\.[a-zA-Z][a-zA-Z\\.]*[a-zA-Z]$")){
//todo 验证邮箱
}else {
// 用户名不在指定范围内 错误
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
recordLogininfor(username, Constants.LOGIN_FAIL, "用户名不在指定范围");
throw new ServiceException("用户名不在指定范围");
}
}
if(!"admin".equals(username)){
if(!password.matches(PWD_REGEX)){
recordLogininfor(username, Constants.LOGIN_FAIL, "密码格式错误");
throw new ServiceException("密码格式错误,密码应包含大写字母、小写字母、数字以及特殊字符8到16位");
}
}
//todo 可以添加校验验证码的功能
// 查询用户信息
R<LoginUser> userResult = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
if (R.FAIL == userResult.getCode())
{
throw new ServiceException(userResult.getMsg());
}
if (StringUtils.isNull(userResult) || StringUtils.isNull(userResult.getData()))
{
recordLogininfor(username, Constants.LOGIN_FAIL, "登录用户不存在");
throw new ServiceException("登录用户:" + username + " 不存在");
}
LoginUser userInfo = userResult.getData();
Long userid = userInfo.getUserid();
SysUser user = userResult.getData().getSysUser();
if (UserStatus.DELETED.getCode().equals(user.getDelFlag()))
{
recordLogininfor(username, Constants.LOGIN_FAIL, "对不起,您的账号已被删除");
throw new ServiceException("对不起,您的账号:" + username + " 已被删除");
}
if (UserStatus.DISABLE.getCode().equals(user.getStatus()))
{
recordLogininfor(username, Constants.LOGIN_FAIL, "用户已停用,请联系管理员");
throw new ServiceException("对不起,您的账号:" + username + " 已停用");
}
//根据user:userId:lock
if(redisService.hasKey("user:"+userid+":lock")){
//账户输入密码错误
throw new ServiceException("该账户因密码错误多次被暂时限制登陆24h");
}
if (!SecurityUtils.matchesPassword(password, user.getPassword()))
{
recordLogininfor(username, Constants.LOGIN_FAIL, "用户密码错误");
//redis 记录用户密码错误次数 大于5时锁定账户24小时
int count = 0;
if(redisService.hasKey("user:"+userid+":pwdError")){
//判断key是否过期
long expire = redisService.getExpire("user:" + userid + ":pwdError", TimeUnit.SECONDS);
if(expire > 0){
count = Convert.toInt(redisService.getCacheObject("user:"+userid+":pwdError"));
}
}
count++;
if(count == 1){
redisService.setCacheObject("user:"+userid+":pwdError",count,1L,TimeUnit.HOURS);
}else if(count < 6){
long expire = redisService.getExpire("user:" + userid + ":pwdError", TimeUnit.SECONDS);
System.out.println(expire);
redisService.setCacheObject("user:"+userid+":pwdError",count,redisService.getExpire("user:"+userid+":pwdError",TimeUnit.SECONDS),TimeUnit.SECONDS);
}
if(count >= 5){
//锁定用户账号 并清除输入错误次数的记录
redisService.setCacheObject("user:"+userid+":lock", userid, 24L, TimeUnit.HOURS);
//清除输入错误次数的记录
redisService.deleteObject("user:"+userid+":pwdError");
}
throw new ServiceException("密码错误,一小时内错误五次之后锁定账户24h,当前已失败"+count+"");
}
//todo 判断当前ip和用户上一次登陆ip是否一致
//if(StringUtils.isNotNull(userResult.getData().getSysUser().getLoginIp()) && StringUtils.isNotBlank(userResult.getData().getSysUser().getLoginIp())){
// //上一次ip不为空 判断
// if(StringUtils.isBlank(loginBody.getUuid()) || StringUtils.isNull(loginBody.getUuid())){
//
// //没传uuid说明是刚发起的登录请求 需要判断ip是否发生变化
// String ipAddr = IpUtils.getIpAddr(ServletUtils.getRequest());
// if(!ipAddr.equals(userResult.getData().getSysUser().getLoginIp())){
// return R.fail(201,"登录ip发生变化请通过验证再登录");
// }
// }else {
// //传了uuid说明是验证登录 验证
// if (StringUtils.isBlank(loginBody.getCode()))
// {
// return R.fail("验证码不能为空");
// }
//
// String verifyKey = Constants.CAPTCHA_CODE_KEY + loginBody.getUuid();
// String captcha = redisService.getCacheObject(verifyKey);
//
// if(StringUtils.isNull(captcha)){
// return R.fail("验证码已失效");
// }
// //redisService.deleteObject(verifyKey);
//
// if (!loginBody.getCode().equalsIgnoreCase(captcha))
// {
// return R.fail("验证码错误");
// }
// redisService.deleteObject(verifyKey);
// }
//
//}
recordLogininfor(username, Constants.LOGIN_SUCCESS, "登录成功");
//修改数据库中的用户登录次数和最后一次登录的ip
user.setLoginCount(user.getLoginCount()+1);
user.setLoginIp(IpUtils.getIpAddr(ServletUtils.getRequest()));
user.setLoginDate(DateUtils.getNowDate());
//todo 单独存储用户的登录时间和ip
remoteUserService.updateUserInfo(user);
return R.success(tokenService.createToken(userInfo));
}
public void logout(String loginName)
{
recordLogininfor(loginName, Constants.LOGOUT, "退出成功");
}
/**
* 注册
*/
public void register(RegisterBody registerBody)
{
String username = registerBody.getUserName();
String password = registerBody.getPassword();
try {
password = RsaUtils.decryptByPrivateKey(registerBody.getPassword());
password = StringUtils.clean(password);
}catch (Exception e){
throw new ServiceException("加密密码传参有误",401);
}
String email = registerBody.getEmail();
String phone = registerBody.getPhone();
String emailCode = registerBody.getEmailCode();
//String key = username+"+"+email;
// 用户名或密码为空 错误
if (StringUtils.isAnyBlank(username, password))
{
throw new ServiceException("用户/密码必须填写");
}
if (username.length() < UserConstants.USERNAME_MIN_LENGTH
|| username.length() > UserConstants.USERNAME_MAX_LENGTH)
{
throw new ServiceException("账户长度必须在3到16个字符之间");
}
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
throw new ServiceException("密码长度必须在8到32个字符之间");
}
if(!password.matches(PWD_REGEX)){
throw new ServiceException("密码格式错误,密码应包含大写字母、小写字母、数字以及特殊字符8到16位");
}
//todo 手机号验证
if(!StringUtils.isEmpty(phone)){
String phoneRex = "^((13[0-9])|(14[5|7])|(15([0-3]|[5-9]))|(18[0,5-9]))\\d{8}$";
if (!phone.matches(phoneRex))
{
throw new ServiceException("手机号格式不正确");
}
}
if(!StringUtils.isEmpty(email)){
//todo 邮箱验证
}else {
throw new ServiceException("邮箱为必填项");
}
if(redisService.hasKey(RedisTransKey.getEmailKey(email))){
Object o = redisService.getCacheObject(RedisTransKey.getEmailKey(email));//user:emailCode:email
EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class);
//验证验证码
if(emailCode.equals(emailCodeEntity.getEmailCode())){
// 注册用户信息
SysUser sysUser = new SysUser();
sysUser.setUserName(username);
sysUser.setNickName(username);
sysUser.setPhone(phone);
sysUser.setEmail(email);
sysUser.setPassword(SecurityUtils.encryptPassword(password));
R<?> registerResult = remoteUserService.registerUserInfo(sysUser);
if (R.FAIL == registerResult.getCode())
{
throw new ServiceException(registerResult.getMsg());
}
recordLogininfor(username, Constants.REGISTER, "注册成功");
}else {
throw new ServiceException("验证码错误");
}
}else {
throw new ServiceException("验证码未获取或已过期,请重新获取验证码");
}
}
/**
* 重置密码
*/
public void resetPwd(ResetPwdBody resetPwdBody)
{
String password = resetPwdBody.getPassword();
try {
password = RsaUtils.decryptByPrivateKey(resetPwdBody.getPassword());
}catch (Exception e){
throw new ServiceException("加密密码传参有误",401);
//password = resetPwdBody.getPassword();
}
String email = resetPwdBody.getEmail();
String resetPwdCode = resetPwdBody.getResetPwdCode();
// 邮箱或密码为空 错误
if (StringUtils.isAnyBlank(email, password))
{
throw new ServiceException("邮箱/新密码不能为空");
}
if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
|| password.length() > UserConstants.PASSWORD_MAX_LENGTH)
{
throw new ServiceException("密码长度必须在8到32个字符之间");
}
//password格式校验
if(!password.matches(PWD_REGEX)){
throw new ServiceException("密码格式错误,密码应包含大写字母、小写字母、数字以及特殊字符8到16位");
}
R<LoginUser> userResult = remoteUserService.getUserInfo(email, SecurityConstants.INNER);
if (R.FAIL == userResult.getCode())
{
throw new ServiceException(userResult.getMsg());
}
if(SecurityUtils.matchesPassword(password,userResult.getData().getSysUser().getPassword())){
throw new ServiceException("新密码不能与原密码一致");
}
if(redisService.hasKey(RedisTransKey.getResetPwdKey(email))){
Object o = redisService.getCacheObject(RedisTransKey.getResetPwdKey(email));//user:emailCode:username
EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class);
if (email.equals(emailCodeEntity.getEmail())) {
//邮箱必须和刚刚传的一致
//验证验证码
if(resetPwdCode.equals(emailCodeEntity.getEmailCode())){
// 重置用户密码
SysUser sysUser = new SysUser();
sysUser.setEmail(email);
sysUser.setPassword(SecurityUtils.encryptPassword(password));
R<?> resetPwdResult = remoteUserService.resetPwdByEmail(sysUser);
if (R.FAIL == resetPwdResult.getCode())
{
throw new ServiceException(resetPwdResult.getMsg());
}
//recordLogininfor(username, Constants.REGISTER, "注册成功");
}else {
throw new ServiceException("邮箱验证码错误");
}
}else {
throw new ServiceException("请勿修改已输入的邮箱");
}
}else {
////判断是邮箱不存在还是操作超时
////通过邮箱获取用户
//R<LoginUser> userByEmail = remoteUserService.getUserInfo(email, SecurityConstants.INNER);
//
//if(StringUtils.isNull(userByEmail.getData())){
// throw new ServiceException("邮箱"+email+"未被注册,请检查邮箱是否正确");
//}
throw new ServiceException("验证码校验失败:操作超时,请重新操作");
}
}
/**
* 记录登录信息
*
* @param username 用户名
* @param status 状态
* @param message 消息内容
* @return
*/
public void recordLogininfor(String username, String status, String message)
{
SysLogininfor logininfor = new SysLogininfor();
logininfor.setUserName(username);
logininfor.setIpaddr(IpUtils.getIpAddr(ServletUtils.getRequest()));
logininfor.setMsg(message);
// 日志状态
if (StringUtils.equalsAny(status, Constants.LOGIN_SUCCESS, Constants.LOGOUT, Constants.REGISTER))
{
logininfor.setStatus(Constants.LOGIN_SUCCESS_STATUS);
}
else if (Constants.LOGIN_FAIL.equals(status))
{
logininfor.setStatus(Constants.LOGIN_FAIL_STATUS);
}
remoteLogService.saveLogininfor(logininfor, SecurityConstants.INNER);
}
}

View File

@@ -0,0 +1,373 @@
package com.jxy.auth.service.impl;
import com.alibaba.fastjson.JSON;
import com.jxy.auth.entity.EmailCodeEntity;
import com.jxy.auth.entity.GetEmailCodeEntity;
import com.jxy.auth.service.MailService;
import com.jxy.common.core.RedisTransKey;
import com.jxy.common.core.Result.R;
import com.jxy.common.core.constant.SecurityConstants;
import com.jxy.common.core.utils.CodeUtils;
import com.jxy.common.core.utils.StringUtils;
import com.jxy.common.redis.service.RedisService;
import com.jxy.system.api.RemoteUserService;
import com.jxy.system.api.model.LoginUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.FileSystemResource;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.Date;
import java.util.concurrent.TimeUnit;
/**
* @Description TODO
* @Date 2022/11/18 15:08
* @Author 杜懿
*/
@Service
public class MaliServiceImpl implements MailService {
/**
* 注入邮件工具类
*/
@Autowired
private JavaMailSenderImpl javaMailSender;
@Autowired
private RemoteUserService remoteUserService;
@Autowired
private RedisService redisService;
@Value("${spring.mail.username}")
private String sendMailer;
/**
* 检测邮件信息类
* @param to
* @param subject
* @param text
*/
private void checkMail(String to,String subject,String text){
if(StringUtils.isEmpty(to)){
throw new RuntimeException("邮件收信人不能为空");
}
if(StringUtils.isEmpty(subject)){
throw new RuntimeException("邮件主题不能为空");
}
if(StringUtils.isEmpty(text)){
throw new RuntimeException("邮件内容不能为空");
}
}
/**
* 发送纯文本邮件
* @param to
* @param subject
* @param text
*/
@Override
public void sendTextMailMessage(String to,String subject,String text){
try {
//true 代表支持复杂的类型
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true);
//邮件发信人
mimeMessageHelper.setFrom(sendMailer);
//邮件收信人 1或多个
mimeMessageHelper.setTo(to.split(","));
//邮件主题
mimeMessageHelper.setSubject(subject);
//邮件内容
mimeMessageHelper.setText(text);
//邮件发送时间
mimeMessageHelper.setSentDate(new Date());
//发送邮件
javaMailSender.send(mimeMessageHelper.getMimeMessage());
//System.out.println("发送邮件成功:"+sendMailer+"->"+to);
} catch (Exception e) {
e.printStackTrace();
//System.out.println("发送邮件失败:"+e.getMessage());
}
}
/**
* 发送html邮件
* @param to
* @param subject
* @param content
*/
@Override
public void sendHtmlMailMessage(String to,String subject,String content){
content="<!DOCTYPE html>\n" +
"<html>\n" +
"<head>\n" +
"<meta charset=\"utf-8\">\n" +
"<title>邮件</title>\n" +
"</head>\n" +
"<body>\n" +
"\t<h3>这是一封HTML邮件</h3>\n" +
"</body>\n" +
"</html>";
try {
//true 代表支持复杂的类型
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true);
//邮件发信人
mimeMessageHelper.setFrom(sendMailer);
//邮件收信人 1或多个
mimeMessageHelper.setTo(to.split(","));
//邮件主题
mimeMessageHelper.setSubject(subject);
//邮件内容 true 代表支持html
mimeMessageHelper.setText(content,true);
//邮件发送时间
mimeMessageHelper.setSentDate(new Date());
//发送邮件
javaMailSender.send(mimeMessageHelper.getMimeMessage());
System.out.println("发送邮件成功:"+sendMailer+"->"+to);
} catch (Exception e) {
e.printStackTrace();
System.out.println("发送邮件失败:"+e.getMessage());
}
}
/**
* 发送带附件的邮件
* @param to 邮件收信人
* @param subject 邮件主题
* @param content 邮件内容
* @param filePath 附件路径
*/
@Override
public void sendAttachmentMailMessage(String to,String subject,String content,String filePath){
try {
//true 代表支持复杂的类型
MimeMessageHelper mimeMessageHelper = new MimeMessageHelper(javaMailSender.createMimeMessage(),true);
//邮件发信人
mimeMessageHelper.setFrom(sendMailer);
//邮件收信人 1或多个
mimeMessageHelper.setTo(to.split(","));
//邮件主题
mimeMessageHelper.setSubject(subject);
//邮件内容 true 代表支持html
mimeMessageHelper.setText(content,true);
//邮件发送时间
mimeMessageHelper.setSentDate(new Date());
//添加邮件附件
FileSystemResource file = new FileSystemResource(new File(filePath));
String fileName = file.getFilename();
mimeMessageHelper.addAttachment(fileName, file);
//发送邮件
javaMailSender.send(mimeMessageHelper.getMimeMessage());
//System.out.println("发送邮件成功:"+sendMailer+"->"+to);
} catch (Exception e) {
e.printStackTrace();
//System.out.println("发送邮件失败:"+e.getMessage());
}
}
/**
* 发送账号注册邮箱验证码
* @param to
* @param code
*/
@Override
public void sendCodeMailMessage(String to, String code) {
String subject = "账号注册,邮箱验证码";
String text = "注册验证码10分钟内有效:\n\t"+code;
sendTextMailMessage(to,subject,text);
}
@Override
public void sendResetPwdMailMessage(String to, String code) {
String subject = "账号重置密码,邮箱验证码";
String text = "您正在重置密码如果不是您本人操作请忽略。验证码10分钟内有效:\n\t"+code;
sendTextMailMessage(to,subject,text);
}
@Override
public R<?> emailCode(GetEmailCodeEntity entity) {
String email = entity.getEmail();
String username = entity.getUserName();
if(StringUtils.isNull(email)){
return R.fail(601,"邮箱不能为空");
}
if(StringUtils.isNull(username)){
return R.fail(602,"用户名不能为空");
}
//判断用户是不是恶意刷邮箱,在规定时间内进行的
if (redisService.hasKey(RedisTransKey.getEmailKey(email))) {
Object o = redisService.getCacheObject(RedisTransKey.getEmailKey(email));//user:emailCode:email
EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class);
if (emailCodeEntity.getTimes() > 4) {
return R.fail("请求次数过多请10分钟后再试,(同一用户10分钟内只能请求4次)");
} else {
String emailCode = CodeUtils.creatCode(6);
emailCodeEntity.setEmailCode(emailCode);
emailCodeEntity.setUserName(username);
//if(!email.equals(emailCodeEntity.getEmail())){
// //email与缓存中所存邮箱不同 则请求次数重置为1
// emailCodeEntity.setTimes(1);
//}else {
// emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1);
//}
emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1);
long overTime = redisService.getExpire(RedisTransKey.setEmailKey(email));
redisService.setCacheObject(RedisTransKey.setEmailKey(email), emailCodeEntity, overTime, TimeUnit.SECONDS
);
sendCodeMailMessage(email, emailCodeEntity.getEmailCode());
}
} else {
R<LoginUser> userByName = remoteUserService.getUserInfo(username, SecurityConstants.INNER);
R<LoginUser> userByEmail = remoteUserService.getUserInfo(email, SecurityConstants.INNER);
if (userByName.getData() != null) {
return R.fail(603,"用户名"+username+"已被注册");
} else if(userByEmail.getData() !=null){
return R.fail(604,"邮箱"+email+"已被注册");
} else {
String emailCode = CodeUtils.creatCode(6);
// 最多允许用户在10分钟内发送2次的邮箱验证
// 0s倒计时后用户可以再发送验证码但是间隔在10分钟内只能再发送1次
EmailCodeEntity emailCodeEntity = new EmailCodeEntity(
username , email, emailCode,1
);
//设置失效时间10分钟
redisService.setCacheObject(RedisTransKey.getEmailKey(email), emailCodeEntity,
10L, TimeUnit.MINUTES
);
sendCodeMailMessage(email, emailCodeEntity.getEmailCode());
}
}
return R.success("请求成功,验证码已经发送至用户邮箱");
}
@Override
public R<?> AddCreditCode(GetEmailCodeEntity entity) {
String email = entity.getEmail();
String username = entity.getUserName();
if(StringUtils.isNull(email)){
return R.fail(601,"邮箱不能为空");
}
if(StringUtils.isNull(username)){
return R.fail(602,"用户名不能为空");
}
//判断用户是不是恶意刷邮箱,在规定时间内进行的
if (redisService.hasKey(RedisTransKey.getAddCreditEmailKey(email))) {
long t1 = System.currentTimeMillis();
Object o = redisService.getCacheObject(RedisTransKey.getAddCreditEmailKey(email));//user:addCreditEmailCode:email
com.jxy.system.api.entity.EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), com.jxy.system.api.entity.EmailCodeEntity.class);
if (emailCodeEntity.getTimes() > 4) {
return R.fail("请求次数过多请10分钟后再试,(同一用户10分钟内只能请求4次)");
} else {
String emailCode = CodeUtils.creatCode(6);
emailCodeEntity.setEmailCode(emailCode);
emailCodeEntity.setUserName(username);
emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1);
long overTime = redisService.getExpire(RedisTransKey.getAddCreditEmailKey(email));
redisService.setCacheObject(RedisTransKey.setAddCreditEmailKey(email), emailCodeEntity, overTime, TimeUnit.SECONDS
);
sendCodeMailMessage(email, emailCodeEntity.getEmailCode());
}
long t2 = System.currentTimeMillis();
System.out.println("处理业务1耗时"+(t2-t1));
} else {
long t1 = System.currentTimeMillis();
String emailCode = CodeUtils.creatCode(6);
// 最多允许用户在10分钟内发送2次的邮箱验证
// 0s倒计时后用户可以再发送验证码但是间隔在10分钟内只能再发送1次
com.jxy.system.api.entity.EmailCodeEntity emailCodeEntity = new com.jxy.system.api.entity.EmailCodeEntity (
username , email, emailCode,1
);
//设置失效时间10分钟
redisService.setCacheObject(RedisTransKey.getAddCreditEmailKey(email), emailCodeEntity,
10L, TimeUnit.MINUTES
);
String subject = "用户提现邮箱验证码";
String text = "提现验证码,10分钟内有效:\n\t"+emailCodeEntity.getEmailCode();
long t2 = System.currentTimeMillis();
System.out.println("处理业务2耗时"+(t2-t1));
sendTextMailMessage(email,subject,text);
long t3 = System.currentTimeMillis();
System.out.println("发送邮箱耗时"+(t3-t2));
}
return R.success("请求成功,验证码已经发送至用户邮箱");
}
@Override
public R<?> resetPwdCode(GetEmailCodeEntity entity) {
String email = entity.getEmail();
//通过邮箱获取用户
R<LoginUser> userByEmail = remoteUserService.getUserInfo(email, SecurityConstants.INNER);
if(StringUtils.isNull(userByEmail.getData())){
return R.fail("邮箱"+email+"未被注册,请检查邮箱是否正确");
}
String username = userByEmail.getData().getUsername();
//判断用户是不是恶意刷邮箱,在规定时间内进行的
if (redisService.hasKey(RedisTransKey.getResetPwdKey(email))) {
Object o = redisService.getCacheObject(RedisTransKey.getResetPwdKey(email));//user:restPwdCode:email
EmailCodeEntity emailCodeEntity = JSON.parseObject(JSON.toJSONString(o), EmailCodeEntity.class);
if (emailCodeEntity.getTimes() >= 5) {
return R.fail("请求次数过多请10分钟后再试");
} else {
//这里就不去判断两次绑定的邮箱是不是一样的了,不排除第一次输入错了邮箱的情况
String emailCode = CodeUtils.creatCode(6);
emailCodeEntity.setEmailCode(emailCode);
emailCodeEntity.setTimes(emailCodeEntity.getTimes() + 1);
long overTime = redisService.getExpire(RedisTransKey.getResetPwdKey(email));
redisService.setCacheObject(RedisTransKey.getResetPwdKey(email), emailCodeEntity, overTime, TimeUnit.SECONDS
);
sendResetPwdMailMessage(email, emailCodeEntity.getEmailCode());
}
} else {
String emailCode = CodeUtils.creatCode(6);
// 最多允许用户在10分钟内发送2次的邮箱验证
// 0s倒计时后用户可以再发送验证码但是间隔在10分钟内只能再发送1次
EmailCodeEntity emailCodeEntity = new EmailCodeEntity(
username , email, emailCode,1
);
//设置失效时间10分钟
redisService.setCacheObject(RedisTransKey.getResetPwdKey(email), emailCodeEntity,
10L, TimeUnit.MINUTES
);
sendResetPwdMailMessage(email, emailCodeEntity.getEmailCode());
}
return R.success("请求成功,重置密码验证码已经发送至用户邮箱");
}
}

View File

@@ -0,0 +1,62 @@
# Tomcat
server:
port: 9200
# Spring
spring:
#邮箱基本配置
mail:
# 配置在limit_time内用户可以发送limit次验证码
limit: 2 这个是我额外的配置,结合邮箱服务用的
limitTime: 10 这个是我额外的配置
#配置smtp服务主机地址
# sina smtp.sina.cn
# aliyun smtp.aliyun.com
# 163 smtp.163.com 端口号465或994
host: mail.privateemail.com
#发送者邮箱
username: support@coinbus.cc
#配置密码,注意不是真正的密码,而是刚刚申请到的授权码
# password:
# password: Jxys2023@
# password: axvm-zfgx-cgcg-qhhu
password: JXys2022!
#端口号
port: 587
#默认的邮件编码为UTF-8
default-encoding: UTF-8
#其他参数
properties:
mail:
#配置SSL 加密工厂
smtp:
ssl:
#本地测试先放开ssl
enable: false
required: false
#开启debug模式这样邮件发送过程的日志会在控制台打印出来方便排查错误
debug: false
socketFactory:
class: javax.net.ssl.SSLSocketFactory
application:
# 应用名称
name: jxy-auth
profiles:
# 环境配置
active: dev
cloud:
nacos:
discovery:
# 服务注册地址
server-addr: 127.0.0.1:8848
config:
# 配置中心地址
server-addr: 127.0.0.1:8848
# 配置文件格式
file-extension: yml
# 共享配置
shared-configs:
- application-${spring.profiles.active}.${spring.cloud.nacos.config.file-extension}

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<configuration scan="true" scanPeriod="60 seconds" debug="false">
<!-- 日志存放路径 -->
<property name="log.path" value="logs/jxy-auth" />
<!-- 日志输出格式 -->
<property name="log.pattern" value="%d{HH:mm:ss.SSS} [%thread] %-5level %logger{20} - [%method,%line] - %msg%n" />
<!-- 控制台输出 -->
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
</appender>
<!-- 系统日志输出 -->
<appender name="file_info" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/info.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/info.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>INFO</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${log.path}/error.log</file>
<!-- 循环政策:基于时间创建日志文件 -->
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<!-- 日志文件名格式 -->
<fileNamePattern>${log.path}/error.%d{yyyy-MM-dd}.log</fileNamePattern>
<!-- 日志最大的历史 60天 -->
<maxHistory>60</maxHistory>
</rollingPolicy>
<encoder>
<pattern>${log.pattern}</pattern>
</encoder>
<filter class="ch.qos.logback.classic.filter.LevelFilter">
<!-- 过滤的级别 -->
<level>ERROR</level>
<!-- 匹配时的操作:接收(记录) -->
<onMatch>ACCEPT</onMatch>
<!-- 不匹配时的操作:拒绝(不记录) -->
<onMismatch>DENY</onMismatch>
</filter>
</appender>
<!-- 系统模块日志级别控制 -->
<logger name="com.jxy" level="info" />
<!-- Spring日志级别控制 -->
<logger name="org.springframework" level="warn" />
<root level="info">
<appender-ref ref="console" />
</root>
<!--系统操作日志-->
<root level="info">
<appender-ref ref="file_info" />
<appender-ref ref="file_error" />
</root>
</configuration>