Skip to content

Commit

Permalink
Merge pull request #10 from OneAndZeroAreEnough/feat/#1-googleEmailLogin
Browse files Browse the repository at this point in the history
feat: 이메일 인증로그인 완료
  • Loading branch information
chamcham0707 authored Jun 4, 2024
2 parents cd2db31 + 13041cc commit eac155d
Show file tree
Hide file tree
Showing 9 changed files with 210 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.sparta.oneandzerobest.auth.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import java.util.Properties;

@Configuration
public class MailConfig {
@Value("${spring.mail.host}")
private String mailHost;

@Value("${spring.mail.port}")
private int mailPort;

@Value("${spring.mail.username}")
private String mailUsername;

@Value("${spring.mail.password}")
private String mailPassword;

@Bean
public JavaMailSender javaMailSender() {
JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
mailSender.setHost(mailHost);
mailSender.setPort(mailPort);
mailSender.setUsername(mailUsername);
mailSender.setPassword(mailPassword);

Properties props = mailSender.getJavaMailProperties();
props.put("mail.transport.protocol", "smtp");
props.put("mail.smtp.auth", "true");
props.put("mail.smtp.starttls.enable", "true");
props.put("mail.debug", "true");

return mailSender;

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.sparta.oneandzerobest.auth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;


@Configuration
public class RedisConfig {

@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, String> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new StringRedisSerializer());
return template;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,20 @@ public ResponseEntity<TokenResponseDto> refresh(@RequestBody RefreshTokenRequest
TokenResponseDto tokenResponseDto = new TokenResponseDto(newAccessToken, refreshToken);
return ResponseEntity.ok(tokenResponseDto);
}

/**
* 이메일 인증
* @param username 사용자 이름
* @param verificationCode 인증 코드
* @return 인증 성공 또는 실패 메시지
*/
@PostMapping("/verify-email")
public ResponseEntity<String> verifyEmail(@RequestParam String username, @RequestParam String verificationCode) {
boolean isVerified = userService.verifyEmail(username, verificationCode);
if (isVerified) {
return ResponseEntity.ok("이메일 인증 성공");
} else {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("이메일 인증 실패");
}
}
}
5 changes: 5 additions & 0 deletions src/main/java/com/sparta/oneandzerobest/auth/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -65,6 +66,10 @@ public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}

public void setEmail(String email) {
this.email = email;
}

@Override
public boolean isEnabled() {
return "정상".equals(this.statusCode); // 계정이 활성화된 상태인지 확인
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.sparta.oneandzerobest.auth.service;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;

@Service
public class EmailService {
private final JavaMailSender javaMailSender;

@Value("${spring.mail.username}")
private String from;
public EmailService(JavaMailSender javaMailSender) {
this.javaMailSender = javaMailSender;
}
public void sendEmail(String to, String subject, String text) {
SimpleMailMessage message = new SimpleMailMessage();
message.setFrom(from);
message.setTo(to);
message.setSubject(subject);
message.setText(text);
javaMailSender.send(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import com.sparta.oneandzerobest.auth.entity.User;
import com.sparta.oneandzerobest.auth.repository.UserRepository;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,9 @@ public interface UserService {
void withdraw(String username, String password);
// 리프레시 토큰
TokenResponseDto refresh(String refreshToken);

// 이메일 인증
boolean verifyEmail(String username, String verificationCode);
// 이메일 업데이트 - 이메일이 잘못된 회원가입
void updateEmail(SignupRequest signupRequest);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,34 @@
import com.sparta.oneandzerobest.exception.InfoNotCorrectedException;
import com.sparta.oneandzerobest.exception.InvalidPasswordException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;

import java.util.Optional;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class UserServiceImpl implements UserService {

private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final EmailService emailService;
private final RedisTemplate<String, String> redisTemplate;
private final JwtUtil jwtUtil;
private final Random random = new Random();

public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder, JwtUtil jwtUtil) {
@Value("${app.email.verification.expiry}")
private long verificationExpiry;
public UserServiceImpl(UserRepository userRepository, PasswordEncoder passwordEncoder, EmailService emailService, RedisTemplate<String, String> redisTemplate, JwtUtil jwtUtil) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.emailService = emailService;
this.redisTemplate = redisTemplate;
this.jwtUtil = jwtUtil;
}

Expand All @@ -42,7 +56,16 @@ public void signup(SignupRequest signupRequest) {
throw new IllegalArgumentException("비밀번호는 최소 10자 이상이며 알파벳 대소문자, 숫자, 특수문자를 포함해야 합니다.");
}

if (userRepository.findByUsername(authId).isPresent()) {
// if (userRepository.findByUsername(authId).isPresent()) {
Optional<User> existingUser = userRepository.findByUsername(authId);
if (existingUser.isPresent()) {
User user = existingUser.get();
if ("인증 전".equals(user.getStatusCode())) {
// 인증 전 상태일 때는 이메일을 업데이트하고 새로운 인증 이메일을 보냄
updateEmail(signupRequest);
return;

}
throw new InfoNotCorrectedException("중복된 사용자 ID가 존재합니다.");
}

Expand All @@ -54,8 +77,27 @@ public void signup(SignupRequest signupRequest) {
String encodedPassword = passwordEncoder.encode(password);
User user = new User(authId, encodedPassword, signupRequest.getUsername(), email, "정상");
userRepository.save(user);
sendVerificationEmail(user);
}
/**
* 인증 코드 생성: 6자리 랜덤 숫자를 생성
* @return 인증 코드
*/
private String generateVerificationCode() {
return String.valueOf(100000 + random.nextInt(900000));
}

/**
* 인증 이메일 발송: 이메일로 인증 코드를 발송
* @param user 회원 정보
*/
private void sendVerificationEmail(User user) {
String verificationCode = generateVerificationCode();
// Redis에 인증 코드를 저장하고 3분 유지
redisTemplate.opsForValue().set(user.getUsername(), verificationCode, verificationExpiry, TimeUnit.MINUTES);
String text = String.format("귀하의 인증 코드는 %s 입니다.", verificationCode);
emailService.sendEmail(user.getEmail(), "이메일 인증", text);
}
/**
* 로그인: ACCESS TOKEN, REFRESH TOKEN 생성
* 비밀번호: 암호화
Expand All @@ -76,6 +118,9 @@ public LoginResponse login(LoginRequest loginRequest) {
throw new InfoNotCorrectedException("탈퇴한 사용자입니다.");
}

if ("인증 전".equals(user.getStatusCode())) {
throw new InfoNotCorrectedException("이메일 인증이 필요합니다.");
}
String accessToken = jwtUtil.createAccessToken(user.getUsername());
String refreshToken = jwtUtil.createRefreshToken(user.getUsername());

Expand Down Expand Up @@ -142,4 +187,34 @@ public TokenResponseDto refresh(String refreshToken) {

return new TokenResponseDto(newAccessToken, newRefreshToken);
}

/**
* 이메일 인증: 입력한 인증 코드를 검증
* @param username 사용자 이름
* @param verificationCode 입력한 인증 코드
* @return 인증 성공 여부
*/
@Override
public boolean verifyEmail(String username, String verificationCode) {
String storedCode = redisTemplate.opsForValue().get(username);
if (storedCode != null && storedCode.equals(verificationCode)) {
Optional<User> userOptional = userRepository.findByUsername(username);
if (userOptional.isPresent()) {
User user = userOptional.get();
user.setStatusCode("정상"); // 인증이 성공하면 정상
userRepository.save(user);
return true;
}
}
return false;
}

@Override
public void updateEmail(SignupRequest signupRequest) {
User user = userRepository.findByUsername(signupRequest.getUsername())
.orElseThrow(() -> new IllegalArgumentException("사용자를 찾을 수 없습니다."));
user.setEmail(signupRequest.getEmail());
userRepository.save(user);
sendVerificationEmail(user);
}
}
20 changes: 19 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,26 @@ spring:
password: 123456789
jpa:
hibernate:
ddl-auto: update
ddl-auto: create
show-sql: true
mail:
host: smtp.gmail.com
port: 587
username: {이부분에}
password: {보내드린거 복붙하고 올릴때는 다시 지워주세요!}
properties:
mail:
smtp:
auth: true
starttls:
enable: true

app:
email:
verification:
subject: 이메일 인증
text: 귀하의 인증 코드는 %s 입니다.
expiry: 3

jwt:
secret:
Expand Down

0 comments on commit eac155d

Please sign in to comment.