feat: email 비밀번호 찾기 이메일 인증 추가
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 8.4 KiB |
Before Width: | Height: | Size: 8.4 KiB |
@ -37,7 +37,7 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-mail'
|
||||
implementation 'org.mindrot:jbcrypt:0.4'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-actuator'
|
||||
|
||||
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
|
||||
|
||||
|
||||
|
||||
|
@ -2,6 +2,7 @@ package com.edufocus.edufocus.global.config;
|
||||
|
||||
|
||||
import com.edufocus.edufocus.global.properties.ImagePathProperties;
|
||||
import com.edufocus.edufocus.global.properties.MailProperties;
|
||||
import com.edufocus.edufocus.global.properties.RabbitMQProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -9,7 +10,8 @@ import org.springframework.context.annotation.Configuration;
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({
|
||||
RabbitMQProperties.class,
|
||||
ImagePathProperties.class
|
||||
ImagePathProperties.class,
|
||||
MailProperties.class
|
||||
})
|
||||
public class PropertiesConfig {
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
package com.edufocus.edufocus.global.config;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
|
||||
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
@Value("${spring.data.redis.host}")
|
||||
private String host;
|
||||
|
||||
@Value("${spring.data.redis.port}")
|
||||
private int port;
|
||||
|
||||
@Bean
|
||||
public RedisConnectionFactory redisConnectionFactory() {
|
||||
return new LettuceConnectionFactory(host, port);
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package com.edufocus.edufocus.global.properties;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
@ConfigurationProperties(prefix = "spring.mail")
|
||||
public class MailProperties {
|
||||
private final String host;
|
||||
private final Integer port;
|
||||
private final String name;
|
||||
private final String password;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package com.edufocus.edufocus.mail.controller;
|
||||
|
||||
import com.edufocus.edufocus.mail.service.MailService;
|
||||
import com.edufocus.edufocus.user.model.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
@Slf4j
|
||||
@RestController
|
||||
@RequestMapping("/mail")
|
||||
@RequiredArgsConstructor
|
||||
public class MailController {
|
||||
|
||||
private final MailService mailService;
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
@PostMapping("/sendCode")
|
||||
public ResponseEntity<?> sendMail(@RequestParam String email) {
|
||||
if (!userService.isEmailExist(email)) {
|
||||
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
|
||||
}
|
||||
|
||||
mailService.sendMail(email);
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@GetMapping("/verify")
|
||||
public ResponseEntity<?> verifyCode(@RequestParam String code, @RequestParam String email) {
|
||||
if (!mailService.verifyCode(code, email)) {
|
||||
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
|
||||
}
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package com.edufocus.edufocus.mail.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public interface MailService {
|
||||
|
||||
void sendMail(String to);
|
||||
|
||||
String createRandomCode();
|
||||
|
||||
boolean verifyCode(String code, String email);
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
package com.edufocus.edufocus.mail.service;
|
||||
|
||||
import com.edufocus.edufocus.redis.util.RedisUtil;
|
||||
import com.edufocus.edufocus.user.model.entity.vo.User;
|
||||
import com.edufocus.edufocus.user.model.repository.UserRepository;
|
||||
import com.edufocus.edufocus.user.model.service.UserService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
|
||||
@Service
|
||||
@Slf4j
|
||||
@ToString
|
||||
@RequiredArgsConstructor
|
||||
public class MailServiceImpl implements MailService {
|
||||
|
||||
private final JavaMailSender mailSender;
|
||||
|
||||
private final UserRepository userRepository;
|
||||
|
||||
private final UserService userService;
|
||||
|
||||
private final RedisUtil redisUtil;
|
||||
|
||||
@Override
|
||||
public void sendMail(String email) {
|
||||
String code = createRandomCode();
|
||||
redisUtil.setDataExpire(code, email, 60 * 5L);
|
||||
|
||||
SimpleMailMessage mail = createEmail(email, "[EDUFOCUS] 비밀번호 찾기 안내", code);
|
||||
mailSender.send(mail);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verifyCode(String code, String email) {
|
||||
String registedEmail = redisUtil.getData(code);
|
||||
|
||||
return registedEmail != null && registedEmail.equals(email);
|
||||
}
|
||||
|
||||
private SimpleMailMessage createEmail(String to, String title, String code) {
|
||||
SimpleMailMessage message = new SimpleMailMessage();
|
||||
message.setTo(to);
|
||||
message.setSubject(title);
|
||||
message.setText("인증번호 6자리입니다 : " + code);
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String createRandomCode() {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
Random random = new Random();
|
||||
for (int i = 0; i < 3; i++) {
|
||||
sb.append((char) (random.nextInt(26) + 65));
|
||||
}
|
||||
for (int i = 0; i < 3; i++) {
|
||||
sb.append(random.nextInt(10));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package com.edufocus.edufocus.redis.util;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.data.redis.core.StringRedisTemplate;
|
||||
import org.springframework.data.redis.core.ValueOperations;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class RedisUtil {
|
||||
|
||||
private final StringRedisTemplate stringRedisTemplate;
|
||||
|
||||
public String getData(String key) {
|
||||
ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
|
||||
return valueOperations.get(key);
|
||||
}
|
||||
|
||||
public void setData(String key, String value) {
|
||||
ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
|
||||
valueOperations.set(key, value);
|
||||
}
|
||||
|
||||
public void setDataExpire(String key, String value, long duration) {
|
||||
ValueOperations<String, String> valueOperations = stringRedisTemplate.opsForValue();
|
||||
Duration expireDuration = Duration.ofSeconds(duration);
|
||||
valueOperations.set(key, value, expireDuration);
|
||||
}
|
||||
|
||||
public void deleteData(String key) {
|
||||
stringRedisTemplate.delete(key);
|
||||
}
|
||||
|
||||
}
|
@ -47,6 +47,6 @@ public class WebConfiguration implements WebMvcConfigurer {
|
||||
public void addInterceptors(InterceptorRegistry registry) {
|
||||
registry.addInterceptor(jwtInterceptor)
|
||||
.addPathPatterns("/**") // 모든 경로에 대해 인터셉터 적용
|
||||
.excludePathPatterns("/v3/api-docs/**", "/swagger-resources/**", "/webjars/**", "/swagger-ui/**", "/auth/**", "/board/**", "/user/**", "/lecture/**", "/qna/**", "/quiz/**", "/video/**", "/registration/**", "/report/**"); // 인증 없이 접근 가능한 경로 설정
|
||||
.excludePathPatterns("/v3/api-docs/**", "/swagger-resources/**", "/webjars/**", "/swagger-ui/**", "/auth/**", "/board/**", "/user/**", "/lecture/**", "/qna/**", "/quiz/**", "/video/**", "/registration/**", "/report/**", "/mail/**"); // 인증 없이 접근 가능한 경로 설정
|
||||
}
|
||||
}
|
@ -32,9 +32,9 @@ public class UserController {
|
||||
private final JWTUtil jwtUtil;
|
||||
|
||||
@PostMapping("/join")
|
||||
public ResponseEntity<String> join(@RequestBody RequestJoinDto requestJoinDto){
|
||||
public ResponseEntity<String> join(@RequestBody RequestJoinDto requestJoinDto) {
|
||||
|
||||
if(userService.isUserIdExist(requestJoinDto.getUserId()))
|
||||
if (userService.isUserIdExist(requestJoinDto.getUserId()))
|
||||
return new ResponseEntity<>("아이디가 중복 됐습니다.", HttpStatus.CONFLICT);
|
||||
|
||||
userService.join(requestJoinDto);
|
||||
@ -66,6 +66,14 @@ public class UserController {
|
||||
}
|
||||
}
|
||||
|
||||
// 비밀번호 찾기를 통한 변경
|
||||
@PutMapping("/updateforgottenpassword")
|
||||
public ResponseEntity<String> updatePassword(@RequestParam long userId,
|
||||
@RequestParam String newPassword) {
|
||||
userService.changeForgottenPassword(userId, newPassword);
|
||||
return new ResponseEntity<>(HttpStatus.OK);
|
||||
}
|
||||
|
||||
@Operation(summary = "로그인", description = "아이디와 비밀번호를 이용하여 로그인 처리.")
|
||||
@PostMapping("/login")
|
||||
public ResponseEntity<Map<String, Object>> login(
|
||||
@ -83,8 +91,8 @@ public class UserController {
|
||||
|
||||
userService.saveRefreshToken(loginUser.getId(), refreshToken);
|
||||
|
||||
resultMap.put("name",loginUser.getName());
|
||||
resultMap.put("role",loginUser.getRole());
|
||||
resultMap.put("name", loginUser.getName());
|
||||
resultMap.put("role", loginUser.getRole());
|
||||
resultMap.put("access-token", accessToken);
|
||||
|
||||
setCookies(response, refreshToken);
|
||||
@ -108,7 +116,7 @@ public class UserController {
|
||||
|
||||
@Operation(summary = "Access Token 재발급", description = "만료된 access token 을 재발급 받는다.")
|
||||
@PostMapping("/refresh")
|
||||
public ResponseEntity<?> refreshToken(HttpServletRequest request,HttpServletResponse response) {
|
||||
public ResponseEntity<?> refreshToken(HttpServletRequest request, HttpServletResponse response) {
|
||||
Cookie[] cookies = request.getCookies();
|
||||
String token = null;
|
||||
if (cookies != null) {
|
||||
@ -120,9 +128,9 @@ public class UserController {
|
||||
}
|
||||
}
|
||||
|
||||
try{
|
||||
try {
|
||||
jwtUtil.checkToken(token);
|
||||
}catch (Exception e){
|
||||
} catch (Exception e) {
|
||||
throw new InvalidTokenException();
|
||||
}
|
||||
|
||||
@ -140,7 +148,7 @@ public class UserController {
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
resultMap.put("access-token", accessToken);
|
||||
|
||||
userService.saveRefreshToken(userId,refreshToken);
|
||||
userService.saveRefreshToken(userId, refreshToken);
|
||||
|
||||
setCookies(response, refreshToken);
|
||||
|
||||
@ -175,7 +183,7 @@ public class UserController {
|
||||
}
|
||||
|
||||
|
||||
private void setCookies(HttpServletResponse response, String refreshToken){
|
||||
private void setCookies(HttpServletResponse response, String refreshToken) {
|
||||
Cookie refreshCookie = new Cookie("refresh-token", refreshToken);
|
||||
refreshCookie.setPath("/");
|
||||
refreshCookie.setHttpOnly(true);
|
||||
|
@ -31,4 +31,6 @@ public interface UserRepository extends JpaRepository<User,Long> {
|
||||
|
||||
Optional<User> findByUserId(String userId);
|
||||
|
||||
Optional<User> findByEmail(String email);
|
||||
|
||||
}
|
||||
|
@ -6,23 +6,27 @@ import com.edufocus.edufocus.user.model.entity.dto.RequestJoinDto;
|
||||
import com.edufocus.edufocus.user.model.entity.vo.User;
|
||||
|
||||
public interface UserService {
|
||||
void join(RequestJoinDto requestJoinDto);
|
||||
void join(RequestJoinDto requestJoinDto);
|
||||
|
||||
User login(User user);
|
||||
User login(User user);
|
||||
|
||||
void saveRefreshToken(Long id, String refreshToken);
|
||||
void saveRefreshToken(Long id, String refreshToken);
|
||||
|
||||
String getRefreshToken(Long id);
|
||||
String getRefreshToken(Long id);
|
||||
|
||||
void deleteRefreshToken(Long id);
|
||||
void deleteRefreshToken(Long id);
|
||||
|
||||
User userInfo(Long id);
|
||||
User userInfo(Long id);
|
||||
|
||||
String getUserName(Long id);
|
||||
String getUserName(Long id);
|
||||
|
||||
void changeUserInfo(InfoDto infoDto,Long id);
|
||||
void changeUserInfo(InfoDto infoDto, Long id);
|
||||
|
||||
void changePassword(PasswordDto passwordDto,Long id);
|
||||
void changePassword(PasswordDto passwordDto, Long id);
|
||||
|
||||
boolean isUserIdExist(String userId);
|
||||
boolean isUserIdExist(String userId);
|
||||
|
||||
boolean isEmailExist(String email);
|
||||
|
||||
void changeForgottenPassword(Long id, String newPassword);
|
||||
}
|
||||
|
@ -3,11 +3,11 @@ package com.edufocus.edufocus.user.model.service;
|
||||
|
||||
import com.edufocus.edufocus.user.model.entity.dto.InfoDto;
|
||||
import com.edufocus.edufocus.user.model.entity.dto.PasswordDto;
|
||||
import com.edufocus.edufocus.user.util.PasswordUtils;
|
||||
import com.edufocus.edufocus.user.model.entity.dto.RequestJoinDto;
|
||||
import com.edufocus.edufocus.user.model.entity.vo.User;
|
||||
import com.edufocus.edufocus.user.model.exception.UserException;
|
||||
import com.edufocus.edufocus.user.model.repository.UserRepository;
|
||||
import com.edufocus.edufocus.user.util.PasswordUtils;
|
||||
import jakarta.transaction.Transactional;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.stereotype.Service;
|
||||
@ -23,8 +23,7 @@ public class UserServiceImpl implements UserService {
|
||||
private final UserRepository userRepository;
|
||||
|
||||
|
||||
public void join(RequestJoinDto requestJoinDto)
|
||||
{
|
||||
public void join(RequestJoinDto requestJoinDto) {
|
||||
User user = User.builder()
|
||||
.userId(requestJoinDto.getUserId())
|
||||
.email(requestJoinDto.getEmail())
|
||||
@ -36,7 +35,7 @@ public class UserServiceImpl implements UserService {
|
||||
}
|
||||
|
||||
|
||||
public User login(User user){
|
||||
public User login(User user) {
|
||||
Optional<User> findUser = userRepository.findByUserId(user.getUserId());
|
||||
|
||||
if (findUser.isEmpty()) {
|
||||
@ -63,32 +62,29 @@ public class UserServiceImpl implements UserService {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@Override
|
||||
public String getUserName(Long id){
|
||||
public String getUserName(Long id) {
|
||||
return userRepository.findById(id).get().getName();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void changeUserInfo(InfoDto infoDto, Long id){
|
||||
public void changeUserInfo(InfoDto infoDto, Long id) {
|
||||
|
||||
User user = userRepository.findById(id).orElseThrow(IllegalArgumentException::new);
|
||||
|
||||
if (infoDto.getName() != null)
|
||||
user.setName(infoDto.getName());
|
||||
|
||||
if(infoDto.getEmail()!=null)
|
||||
if (infoDto.getEmail() != null)
|
||||
user.setEmail(infoDto.getEmail());
|
||||
|
||||
userRepository.save(user);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void changePassword(PasswordDto passwordDto, Long id){
|
||||
public void changePassword(PasswordDto passwordDto, Long id) {
|
||||
User user = userRepository.findById(id).orElse(null);
|
||||
|
||||
if (user == null) {
|
||||
@ -114,30 +110,52 @@ public class UserServiceImpl implements UserService {
|
||||
}
|
||||
|
||||
public String getTempPassword() {
|
||||
char[] charSet = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
|
||||
char[] charSet = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F',
|
||||
'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'};
|
||||
String str = "";
|
||||
|
||||
int idx = 0;
|
||||
for (int i=0; i<10; i++) {
|
||||
for (int i = 0; i < 10; i++) {
|
||||
idx = (int) (charSet.length * Math.random());
|
||||
str += charSet[idx];
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void saveRefreshToken(Long id, String refreshToken){
|
||||
public void saveRefreshToken(Long id, String refreshToken) {
|
||||
userRepository.saveRefreshToken(id, refreshToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getRefreshToken(Long id){
|
||||
public String getRefreshToken(Long id) {
|
||||
return userRepository.getRefreshToken(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteRefreshToken(Long id){
|
||||
public void deleteRefreshToken(Long id) {
|
||||
userRepository.deleteRefreshToken(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEmailExist(String email) {
|
||||
Optional<User> user = userRepository.findByEmail(email);
|
||||
|
||||
return user.isPresent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeForgottenPassword(Long id, String newPassword) {
|
||||
User user = userRepository.findById(id).orElse(null);
|
||||
|
||||
if (user == null) {
|
||||
throw new UserException("User not found");
|
||||
}
|
||||
|
||||
// Hash the new password before saving
|
||||
user.setPassword(PasswordUtils.hashPassword(newPassword));
|
||||
userRepository.save(user);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -30,3 +30,12 @@ spring.rabbitmq.port=${RABBITMQ_PORT}
|
||||
spring.rabbitmq.username=${RABBITMQ_USERNAME}
|
||||
spring.rabbitmq.password=${RABBITMQ_PASSWORD}
|
||||
image.path=${IMAGE_PATH}
|
||||
spring.mail.host=smtp.gmail.com
|
||||
spring.mail.port=${MAIL_PORT}
|
||||
spring.mail.username=${MAIL_NAME}
|
||||
spring.mail.password=${MAIL_PASSWORD}
|
||||
spring.mail.properties.mail.smtp.auth=true
|
||||
spring.mail.properties.mail.smtp.timeout=5000
|
||||
spring.mail.properties.mail.smtp.starttls.enable=true
|
||||
spring.data.redis.host=${REDIS_HOST}
|
||||
spring.data.redis.port=${REDIS_PORT}
|