个人中心

pull/65/head
awenes 2023-10-06 07:08:07 +08:00
parent 890cb5c3f8
commit 0b2840e1f2
4 changed files with 835 additions and 0 deletions

View File

@ -0,0 +1,172 @@
/*
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.controller.user;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import cn.topiam.employee.audit.annotation.Audit;
import cn.topiam.employee.audit.event.type.EventType;
import cn.topiam.employee.console.pojo.update.user.*;
import cn.topiam.employee.console.service.user.UserProfileService;
import cn.topiam.employee.support.result.ApiRestResult;
import cn.topiam.employee.support.web.decrypt.DecryptRequestBody;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import static cn.topiam.employee.common.constant.UserConstants.*;
import static cn.topiam.employee.support.constant.EiamConstants.V1_API_PATH;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2021/9/12 21:39
*/
@RestController
@RequestMapping(value = V1_API_PATH + "/user/profile")
public class UserProfileController {
/**
*
*
* @return {@link ApiRestResult}
*/
@Audit(type = EventType.MODIFY_ACCOUNT_INFO_PORTAL)
@Operation(summary = "修改账户信息")
@PutMapping("/change_info")
public ApiRestResult<Boolean> changeInfo(@DecryptRequestBody @RequestBody @Validated UpdateUserInfoRequest param) {
Boolean result = userProfileService.changeInfo(param);
return ApiRestResult.ok(result);
}
/**
*
*
* @return {@link ApiRestResult}
*/
@Audit(type = EventType.PREPARE_MODIFY_PASSWORD)
@Operation(summary = "准备修改账户密码")
@PostMapping("/prepare_change_password")
public ApiRestResult<Boolean> prepareChangePassword(@DecryptRequestBody @RequestBody @Validated PrepareChangePasswordRequest param) {
return ApiRestResult.ok(userProfileService.prepareChangePassword(param));
}
/**
*
*
* @return {@link ApiRestResult}
*/
@Audit(type = EventType.MODIFY_USER_PASSWORD_PORTAL)
@Operation(summary = "修改账户密码")
@PutMapping("/change_password")
public ApiRestResult<Boolean> changePassword(@DecryptRequestBody @RequestBody @Validated ChangePasswordRequest param) {
return ApiRestResult.ok(userProfileService.changePassword(param));
}
/**
*
*
* @return {@link ApiRestResult}
*/
@Audit(type = EventType.PREPARE_MODIFY_PHONE)
@Operation(summary = "准备修改手机")
@PostMapping("/prepare_change_phone")
public ApiRestResult<Boolean> prepareChangePhone(@DecryptRequestBody @RequestBody @Validated PrepareChangePhoneRequest param) {
return ApiRestResult.ok(userProfileService.prepareChangePhone(param));
}
/**
*
*
* @return {@link ApiRestResult}
*/
@Audit(type = EventType.MODIFY_USER_PHONE_PORTAL)
@Operation(summary = "修改手机")
@PutMapping("/change_phone")
public ApiRestResult<Boolean> changePhone(@RequestBody @Validated ChangePhoneRequest param) {
return ApiRestResult.ok(userProfileService.changePhone(param));
}
/**
*
*
* @return {@link ApiRestResult}
*/
@Audit(type = EventType.PREPARE_MODIFY_EMAIL)
@Operation(summary = "准备修改邮箱")
@PostMapping("/prepare_change_email")
public ApiRestResult<Boolean> prepareChangeEmail(@DecryptRequestBody @RequestBody @Validated PrepareChangeEmailRequest param) {
return ApiRestResult.ok(userProfileService.prepareChangeEmail(param));
}
/**
*
*
* @return {@link ApiRestResult}
*/
@Audit(type = EventType.MODIFY_USER_EMAIL_PORTAL)
@Operation(summary = "修改邮箱")
@PutMapping("/change_email")
public ApiRestResult<Boolean> changeEmail(@RequestBody @Validated ChangeEmailRequest param) {
return ApiRestResult.ok(userProfileService.changeEmail(param));
}
/**
*
*
* @return {@link ApiRestResult}
*/
@Operation(summary = "忘记密码发送验证码")
@GetMapping(FORGET_PASSWORD_CODE)
public ApiRestResult<Boolean> forgetPasswordCode(@Parameter(description = "验证码接收者(邮箱/手机号)") @RequestParam String recipient) {
return ApiRestResult.ok(userProfileService.forgetPasswordCode(recipient));
}
/**
*
*
* @return {@link ApiRestResult}
*/
@Operation(summary = "忘记密码预认证")
@PostMapping(PREPARE_FORGET_PASSWORD)
public ApiRestResult<Boolean> prepareForgetPassword(@DecryptRequestBody @RequestBody @Validated PrepareForgetPasswordRequest param) {
return ApiRestResult
.ok(userProfileService.prepareForgetPassword(param.getRecipient(), param.getCode()));
}
/**
*
*
* @return {@link ApiRestResult}
*/
@Operation(summary = "忘记密码")
@PutMapping(FORGET_PASSWORD)
public ApiRestResult<Boolean> forgetPassword(@DecryptRequestBody @RequestBody @Validated ForgetPasswordRequest forgetPasswordRequest) {
return ApiRestResult.ok(userProfileService.forgetPassword(forgetPasswordRequest));
}
/**
* service
*/
private final UserProfileService userProfileService;
public UserProfileController(UserProfileService userProfileService) {
this.userProfileService = userProfileService;
}
}

View File

@ -0,0 +1,88 @@
/*
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.converter.user;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import cn.topiam.employee.common.entity.account.UserDetailEntity;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.console.pojo.update.user.UpdateUserInfoRequest;
/**
* AccountConverter
*
* @author TopIAM
* Created by support@topiam.cn on 2022/3/25 21:52
*/
@Mapper(componentModel = "spring")
public interface UserProfileConverter {
/**
*
*
* @param param {@link UpdateUserInfoRequest}
* @return {@link UserEntity}
*/
@Mapping(target = "passwordPlainText", ignore = true)
@Mapping(target = "deleted", ignore = true)
@Mapping(target = "identitySourceId", ignore = true)
@Mapping(target = "phoneVerified", ignore = true)
@Mapping(target = "phoneAreaCode", ignore = true)
@Mapping(target = "username", ignore = true)
@Mapping(target = "updateTime", ignore = true)
@Mapping(target = "updateBy", ignore = true)
@Mapping(target = "status", ignore = true)
@Mapping(target = "remark", ignore = true)
@Mapping(target = "phone", ignore = true)
@Mapping(target = "password", ignore = true)
@Mapping(target = "lastUpdatePasswordTime", ignore = true)
@Mapping(target = "lastAuthTime", ignore = true)
@Mapping(target = "lastAuthIp", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "externalId", ignore = true)
@Mapping(target = "expireDate", ignore = true)
@Mapping(target = "expand", ignore = true)
@Mapping(target = "emailVerified", ignore = true)
@Mapping(target = "email", ignore = true)
@Mapping(target = "dataOrigin", ignore = true)
@Mapping(target = "createTime", ignore = true)
@Mapping(target = "createBy", ignore = true)
@Mapping(target = "authTotal", ignore = true)
UserEntity userUpdateParamConvertToUserEntity(UpdateUserInfoRequest param);
/**
*
*
* @param param {@link UpdateUserInfoRequest}
* @return {@link UserDetailEntity}
*/
@Mapping(target = "deleted", ignore = true)
@Mapping(target = "website", ignore = true)
@Mapping(target = "idType", ignore = true)
@Mapping(target = "userId", ignore = true)
@Mapping(target = "remark", ignore = true)
@Mapping(target = "idCard", ignore = true)
@Mapping(target = "address", ignore = true)
@Mapping(target = "id", ignore = true)
@Mapping(target = "updateTime", ignore = true)
@Mapping(target = "updateBy", ignore = true)
@Mapping(target = "createTime", ignore = true)
@Mapping(target = "createBy", ignore = true)
UserDetailEntity userUpdateParamConvertToUserDetailsEntity(UpdateUserInfoRequest param);
}

View File

@ -0,0 +1,111 @@
/*
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.service.user;
import cn.topiam.employee.console.pojo.update.user.*;
/**
*
*
* @author TopIAM
* Created by support@topiam.cn on 2022/4/3 22:20
*/
public interface UserProfileService {
/**
*
*
* @param param {@link UpdateUserInfoRequest}
* @return {@link Boolean}
*/
Boolean changeInfo(UpdateUserInfoRequest param);
/**
*
*
* @param param {@link ChangePasswordRequest}
* @return Boolean
*/
Boolean changePassword(ChangePasswordRequest param);
/**
*
*
* @param param {@link PrepareChangePhoneRequest}
* @return {@link Boolean}
*/
Boolean prepareChangePhone(PrepareChangePhoneRequest param);
/**
*
*
* @param param {@link ChangePhoneRequest}
* @return {@link Boolean}
*/
Boolean changePhone(ChangePhoneRequest param);
/**
*
*
* @param param {@link PrepareChangeEmailRequest}
* @return {@link Boolean}
*/
Boolean prepareChangeEmail(PrepareChangeEmailRequest param);
/**
*
*
* @param param {@link ChangeEmailRequest}
* @return {@link Boolean}
*/
Boolean changeEmail(ChangeEmailRequest param);
/**
*
*
* @param param {@link PrepareChangePasswordRequest}
* @return {@link Boolean}
*/
Boolean prepareChangePassword(PrepareChangePasswordRequest param);
/**
*
*
* @param recipient {@link String} /
* @return {@link Boolean}
*/
Boolean forgetPasswordCode(String recipient);
/**
*
*
* @param recipient {@link String} /
* @param code {@link String}
* @return {@link Boolean} Token
*/
Boolean prepareForgetPassword(String recipient, String code);
/**
*
*
* @param forgetPasswordRequest {@link ForgetPasswordRequest}
* @return {@link Boolean}
*/
Boolean forgetPassword(ForgetPasswordRequest forgetPasswordRequest);
}

View File

@ -0,0 +1,464 @@
/*
* eiam-console - Employee Identity and Access Management
* Copyright © 2022-Present Jinan Yuanchuang Network Technology Co., Ltd. (support@topiam.cn)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cn.topiam.employee.console.service.user.impl;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.scheduling.annotation.AsyncConfigurer;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.google.common.collect.Maps;
import cn.topiam.employee.common.entity.account.UserDetailEntity;
import cn.topiam.employee.common.entity.account.UserEntity;
import cn.topiam.employee.common.enums.MailType;
import cn.topiam.employee.common.enums.MessageNoticeChannel;
import cn.topiam.employee.common.enums.SmsType;
import cn.topiam.employee.common.exception.PasswordValidatedFailException;
import cn.topiam.employee.common.exception.UserNotFoundException;
import cn.topiam.employee.common.repository.account.UserDetailRepository;
import cn.topiam.employee.common.repository.account.UserIdpRepository;
import cn.topiam.employee.common.repository.account.UserRepository;
import cn.topiam.employee.common.repository.authentication.IdentityProviderRepository;
import cn.topiam.employee.console.converter.user.UserProfileConverter;
import cn.topiam.employee.console.pojo.update.user.*;
import cn.topiam.employee.console.service.user.UserProfileService;
import cn.topiam.employee.core.message.sms.SmsMsgEventPublish;
import cn.topiam.employee.core.security.otp.OtpContextHelp;
import cn.topiam.employee.support.context.ServletContextHelp;
import cn.topiam.employee.support.exception.BadParamsException;
import cn.topiam.employee.support.exception.InfoValidityFailException;
import cn.topiam.employee.support.exception.TopIamException;
import cn.topiam.employee.support.security.password.PasswordPolicyManager;
import cn.topiam.employee.support.security.util.SecurityUtils;
import cn.topiam.employee.support.util.BeanUtils;
import cn.topiam.employee.support.util.PhoneNumberUtils;
import lombok.extern.slf4j.Slf4j;
import jakarta.servlet.http.HttpSession;
import static cn.topiam.employee.core.message.sms.SmsMsgEventPublish.USERNAME;
import static cn.topiam.employee.support.constant.EiamConstants.FORGET_PASSWORD_TOKEN_ID;
import static cn.topiam.employee.support.exception.enums.ExceptionStatus.EX000102;
import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_BY;
import static cn.topiam.employee.support.repository.domain.BaseEntity.LAST_MODIFIED_TIME;
import static cn.topiam.employee.support.util.EmailUtils.isEmailValidate;
import static cn.topiam.employee.support.util.PhoneNumberUtils.isPhoneValidate;
/**
* @author TopIAM
* Created by support@topiam.cn on 2022/4/3 22:20
*/
@Slf4j
@Service
public class UserProfileServiceImpl implements UserProfileService {
private final Logger logger = LoggerFactory.getLogger(UserProfileServiceImpl.class);
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean changeInfo(UpdateUserInfoRequest param) {
//用户信息
UserEntity toUserEntity = userProfileConverter.userUpdateParamConvertToUserEntity(param);
UserEntity user = userRepository
.findById(Long.valueOf(SecurityUtils.getCurrentUser().getId())).orElseThrow();
BeanUtils.merge(toUserEntity, user, LAST_MODIFIED_BY, LAST_MODIFIED_TIME);
userRepository.save(user);
//用户详情
String currentUserId = SecurityUtils.getCurrentUserId();
UserDetailEntity detail = userDetailsRepository.findByUserId(Long.valueOf(currentUserId))
.orElse(new UserDetailEntity().setUserId(user.getId()));
UserDetailEntity toUserDetailsEntity = userProfileConverter
.userUpdateParamConvertToUserDetailsEntity(param);
toUserDetailsEntity.setId(detail.getId());
BeanUtils.merge(toUserDetailsEntity, detail, LAST_MODIFIED_BY, LAST_MODIFIED_TIME);
userDetailsRepository.save(detail);
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean changePassword(ChangePasswordRequest param) {
//获取用户
UserEntity user = getCurrentUser();
Boolean checkOtp = otpContextHelp.checkOtp(
MessageNoticeChannel.SMS == param.getChannel() ? SmsType.UPDATE_PASSWORD.getCode()
: MailType.UPDATE_PASSWORD.getCode(),
param.getChannel(),
MessageNoticeChannel.SMS == param.getChannel() ? user.getPhone() : user.getEmail(),
param.getVerifyCode());
if (!checkOtp) {
throw new InfoValidityFailException(EX000102.getMessage());
}
// 校验密码
passwordPolicyManager.validate(user, param.getNewPassword());
//修改密码
userRepository.updateUserPassword(Long.valueOf(SecurityUtils.getCurrentUser().getId()),
passwordEncoder.encode(param.getNewPassword()), LocalDateTime.now());
logger.info("用户ID: [{}] 用户名: [{}] 修改密码成功", user.getId(), user.getUsername());
//异步下线所有用户
removeSession(SecurityUtils.getCurrentUserName());
//@formatter:on
return true;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean prepareChangePhone(PrepareChangePhoneRequest param) {
UserEntity user = validatedPassword(param.getPassword());
// 发送短信验证码
if (StringUtils.isNotBlank(user.getPhone())) {
otpContextHelp.sendOtp(param.getPhone(), SmsType.UPDATE_PHONE.getCode(),
MessageNoticeChannel.SMS);
} else {
otpContextHelp.sendOtp(param.getPhone(), SmsType.BIND_PHONE.getCode(),
MessageNoticeChannel.SMS);
}
return true;
}
/**
*
*
* @param param {@link ChangePhoneRequest}
* @return Boolean
*/
@Override
public Boolean changePhone(ChangePhoneRequest param) {
UserEntity user = getCurrentUser();
Boolean checkOtp;
if (StringUtils.isNotBlank(user.getPhone())) {
checkOtp = otpContextHelp.checkOtp(SmsType.UPDATE_PHONE.getCode(),
MessageNoticeChannel.SMS, param.getPhone(), param.getOtp());
} else {
checkOtp = otpContextHelp.checkOtp(SmsType.BIND_PHONE.getCode(),
MessageNoticeChannel.SMS, param.getPhone(), param.getOtp());
}
if (!checkOtp) {
throw new InfoValidityFailException(EX000102.getMessage());
}
// 校验是否已经存在
UserEntity byEmail = userRepository.findByPhone(param.getPhone());
if (Objects.nonNull(byEmail) && !user.getId().equals(byEmail.getId())) {
throw new TopIamException("系统中已存在[" + param.getPhone() + "]手机号, 请先解绑");
}
Long id = Long.valueOf(SecurityUtils.getCurrentUser().getId());
userRepository.updateUserPhone(id, param.getPhone());
// 修改手机号成功发送短信
LinkedHashMap<String, String> parameter = Maps.newLinkedHashMap();
parameter.put(USERNAME, user.getUsername());
smsMsgEventPublish.publish(SmsType.BIND_PHONE_SUCCESS, param.getPhone(), parameter);
return true;
}
/**
*
*
* @param param {@link PrepareChangeEmailRequest}
* @return {@link Boolean}
*/
@Override
public Boolean prepareChangeEmail(PrepareChangeEmailRequest param) {
UserEntity user = validatedPassword(param.getPassword());
// 发送邮箱验证码
if (StringUtils.isNotBlank(user.getEmail())) {
otpContextHelp.sendOtp(param.getEmail(), MailType.UPDATE_BIND_MAIL.getCode(),
MessageNoticeChannel.MAIL);
} else {
otpContextHelp.sendOtp(param.getEmail(), MailType.BIND_EMAIL.getCode(),
MessageNoticeChannel.MAIL);
}
return true;
}
/**
*
*
* @param param {@link ChangeEmailRequest}
* @return {@link Boolean}
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Boolean changeEmail(ChangeEmailRequest param) {
UserEntity user = getCurrentUser();
Boolean checkOtp;
if (StringUtils.isNotBlank(user.getEmail())) {
checkOtp = otpContextHelp.checkOtp(MailType.UPDATE_BIND_MAIL.getCode(),
MessageNoticeChannel.MAIL, param.getEmail(), param.getOtp());
} else {
checkOtp = otpContextHelp.checkOtp(MailType.BIND_EMAIL.getCode(),
MessageNoticeChannel.MAIL, param.getEmail(), param.getOtp());
}
if (!checkOtp) {
throw new InfoValidityFailException(EX000102.getMessage());
}
// 校验是否已经存在
UserEntity byEmail = userRepository.findByEmail(param.getEmail());
if (Objects.nonNull(byEmail) && !user.getId().equals(byEmail.getId())) {
throw new TopIamException("系统中已存在[" + param.getEmail() + "]邮箱, 请先解绑");
}
userRepository.updateUserEmail(Long.valueOf(SecurityUtils.getCurrentUser().getId()),
param.getEmail());
return true;
}
@Override
public Boolean prepareChangePassword(PrepareChangePasswordRequest param) {
UserEntity user = getCurrentUser();
// 发送短信验证码
if (MessageNoticeChannel.SMS == param.getChannel()) {
otpContextHelp.sendOtp(user.getPhone(), SmsType.UPDATE_PASSWORD.getCode(),
MessageNoticeChannel.SMS);
} else {
otpContextHelp.sendOtp(user.getEmail(), MailType.UPDATE_PASSWORD.getCode(),
MessageNoticeChannel.MAIL);
}
return true;
}
@Override
public Boolean forgetPasswordCode(String recipient) {
if (isEmailValidate(recipient)) {
// 验证在库中是否有邮箱
Optional<UserEntity> byEmail = Optional
.ofNullable(userRepository.findByEmail(recipient));
if (byEmail.isPresent()) {
otpContextHelp.sendOtp(recipient, MailType.FORGET_PASSWORD.getCode(),
MessageNoticeChannel.MAIL);
return true;
}
log.warn("忘记密码: : 邮箱: [{}] 不存在", recipient);
} else if (isPhoneValidate(recipient)) {
// 验证在库中是否有手机号
Optional<UserEntity> byPhone = Optional
.ofNullable(userRepository.findByPhone(PhoneNumberUtils.getPhoneNumber(recipient)));
if (byPhone.isPresent()) {
otpContextHelp.sendOtp(recipient, SmsType.FORGET_PASSWORD.getCode(),
MessageNoticeChannel.SMS);
return true;
}
log.warn("忘记密码: : 手机号: [{}] 不存在", recipient);
}
log.error("忘记密码: : 接受者: [{}] 格式错误", recipient);
throw new BadParamsException("请输入正确的手机号或邮箱");
}
@Override
public Boolean prepareForgetPassword(String recipient, String code) {
// 校验验证码
Boolean checkOtp = false;
Optional<UserEntity> user = Optional.empty();
if (isEmailValidate(recipient)) {
user = Optional.ofNullable(userRepository.findByEmail(recipient));
if (user.isPresent()) {
checkOtp = otpContextHelp.checkOtp(MailType.FORGET_PASSWORD.getCode(),
MessageNoticeChannel.MAIL, recipient, code);
}
} else if (isPhoneValidate(recipient)) {
user = Optional
.ofNullable(userRepository.findByPhone(PhoneNumberUtils.getPhoneNumber(recipient)));
if (user.isPresent()) {
checkOtp = otpContextHelp.checkOtp(SmsType.FORGET_PASSWORD.getCode(),
MessageNoticeChannel.SMS, recipient, code);
}
}
if (!checkOtp) {
throw new InfoValidityFailException(EX000102.getMessage());
}
// 生成忘记密码TOKEN ID
String tokenId = UUID.randomUUID().toString();
HttpSession session = ServletContextHelp.getSession();
// 保存用户ID到Redis, 有效期10分钟
stringRedisTemplate.opsForValue().set(session.getId() + tokenId,
String.valueOf(user.get().getId()), 10, TimeUnit.MINUTES);
// 保存TOKEN ID到会话
session.setAttribute(FORGET_PASSWORD_TOKEN_ID, tokenId);
return true;
}
@Override
public Boolean forgetPassword(ForgetPasswordRequest forgetPasswordRequest) {
// 验证TOKEN
HttpSession session = ServletContextHelp.getSession();
String redisTokenId = session.getId() + session.getAttribute(FORGET_PASSWORD_TOKEN_ID);
String userId = stringRedisTemplate.opsForValue().get(redisTokenId);
if (Objects.isNull(userId)) {
// 清除tokenId
session.removeAttribute(FORGET_PASSWORD_TOKEN_ID);
return false;
}
//修改密码
Optional<UserEntity> user = userRepository.findById(Long.valueOf(userId));
if (user.isPresent()) {
UserEntity userEntity = user.get();
userRepository.updateUserPassword(userEntity.getId(),
passwordEncoder.encode(forgetPasswordRequest.getNewPassword()),
LocalDateTime.now());
logger.info("忘记密码: 用户ID: [{}] 用户名: [{}] 修改密码成功", userEntity.getId(),
userEntity.getUsername());
removeSession(userEntity.getUsername());
stringRedisTemplate.delete(redisTokenId);
return true;
}
return false;
}
/**
* 线
*
* @param username {@link String}
*/
private void removeSession(String username) {
executor.execute(() -> {
List<SessionInformation> sessions = sessionRegistry.getAllSessions(username, false);
sessions.forEach(SessionInformation::expireNow);
//@formatter:on
});
SecurityContextHolder.clearContext();
}
/**
*
* 使
*
* @return {@link UserEntity}
*/
public UserEntity getCurrentUser() {
String userId = SecurityUtils.getCurrentUserId();
Optional<UserEntity> optional = userRepository.findById(Long.valueOf(userId));
if (optional.isPresent()) {
return optional.get();
}
SecurityContextHolder.clearContext();
logger.error("根据用户ID: [{}] 未查询到用户信息", userId);
throw new UserNotFoundException();
}
/**
*
*
* @param password {@link String}
* @return {@link UserEntity}
*/
public UserEntity validatedPassword(String password) {
UserEntity user = getCurrentUser();
boolean matches = passwordEncoder.matches(password, user.getPassword());
if (!matches) {
logger.error("用户ID: [{}] 用户名: [{}] 密码匹配失败", user.getId(), user.getUsername());
throw new PasswordValidatedFailException();
}
return user;
}
/**
* Executor
*/
private final Executor executor;
/**
* AccountConverter
*/
private final UserProfileConverter userProfileConverter;
/**
* PasswordEncoder
*/
private final PasswordEncoder passwordEncoder;
/**
* UserRepository
*/
private final UserRepository userRepository;
/**
* Repository
*/
private final UserDetailRepository userDetailsRepository;
/**
* SessionRegistry
*/
private final SessionRegistry sessionRegistry;
/**
* OtpContextHelp
*/
private final OtpContextHelp otpContextHelp;
/**
* SmsMsgEventPublish
*/
private final SmsMsgEventPublish smsMsgEventPublish;
/**
* StringRedisTemplate
*/
private final StringRedisTemplate stringRedisTemplate;
/**
* PasswordPolicyManager
*/
private final PasswordPolicyManager<UserEntity> passwordPolicyManager;
/**
* IdentityProviderRepository
*/
private final IdentityProviderRepository identityProviderRepository;
/**
* UserAuthnBindRepository
*/
private final UserIdpRepository userIdpRepository;
public UserProfileServiceImpl(AsyncConfigurer asyncConfigurer,
UserProfileConverter userProfileConverter,
PasswordEncoder passwordEncoder, UserRepository userRepository,
UserDetailRepository userDetailsRepository,
SessionRegistry sessionRegistry, OtpContextHelp otpContextHelp,
SmsMsgEventPublish smsMsgEventPublish,
StringRedisTemplate stringRedisTemplate,
PasswordPolicyManager<UserEntity> passwordPolicyManager,
IdentityProviderRepository identityProviderRepository,
UserIdpRepository userIdpRepository) {
this.executor = asyncConfigurer.getAsyncExecutor();
this.userProfileConverter = userProfileConverter;
this.passwordEncoder = passwordEncoder;
this.userRepository = userRepository;
this.userDetailsRepository = userDetailsRepository;
this.sessionRegistry = sessionRegistry;
this.otpContextHelp = otpContextHelp;
this.smsMsgEventPublish = smsMsgEventPublish;
this.stringRedisTemplate = stringRedisTemplate;
this.passwordPolicyManager = passwordPolicyManager;
this.identityProviderRepository = identityProviderRepository;
this.userIdpRepository = userIdpRepository;
}
}