Skip to content

Commit

Permalink
Merge pull request #41 from everymeals/feature/user-profile-create
Browse files Browse the repository at this point in the history
  • Loading branch information
Qbeom0925 authored Sep 30, 2023
2 parents b43a4b9 + aa7deee commit 81ac123
Show file tree
Hide file tree
Showing 16 changed files with 404 additions and 6 deletions.
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

//STMP
implementation 'org.springframework.boot:spring-boot-starter-mail'
}

tasks.named('test') {
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/everymeal/server/global/config/WebMvcConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package everymeal.server.global.config;


import everymeal.server.global.util.authresolver.UserJwtResolver;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@RequiredArgsConstructor
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

private final UserJwtResolver adminResolver;

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(adminResolver);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ public enum ExceptionList {
INVALID_REQUEST("R0001", HttpStatus.BAD_REQUEST, "Request의 Data Type이 올바르지 않습니다."),

USER_NOT_FOUND("U0001", HttpStatus.NOT_FOUND, "등록된 유저가 아닙니다."),
USER_AUTH_TIME_OUT("U0002", HttpStatus.FORBIDDEN, "인증 시간이 만료되었습니다."),
USER_AUTH_FAIL("U0003", HttpStatus.FORBIDDEN, "인증에 실패하였습니다."),

TOKEN_NOT_VALID("T0001", HttpStatus.NOT_ACCEPTABLE, "해당 토큰은 유효하지 않습니다."),
TOKEN_EXPIRATION("T0002", HttpStatus.FORBIDDEN, "토큰이 만료되었습니다."),
Expand Down
25 changes: 23 additions & 2 deletions src/main/java/everymeal/server/global/util/JwtUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,21 @@ public class JwtUtil {
@Value("${jwt.validity.refresh-seconds}")
private Long refreshTokenExpirationMs;

public String generateEmailToken(Long userId, String email, String sendAuthPassword) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + (accessTokenExpirationMs * 2));
Map<String, Object> claims = new HashMap<>();
claims.put("CLAIM_KEY_IDX", userId);
claims.put("CLAIM_KEY_EMAIL", email);
claims.put("CLAIM_KEY_SEND_AUTH_PASSWORD", sendAuthPassword);
return Jwts.builder()
.setClaims(claims)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(Keys.hmacShaKeyFor(accessSecretKey.getBytes()), SignatureAlgorithm.HS512)
.compact();
}

public String generateAccessToken(Long idx) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + accessTokenExpirationMs);
Expand Down Expand Up @@ -67,13 +82,19 @@ public AuthenticatedUser getAuthenticateUserFromAccessToken(String token) {
if (claims != null) {
return AuthenticatedUser.builder()
.idx(Long.parseLong(claims.get("CLAIM_KEY_IDX").toString()))
.deviceId(claims.get("CLAIM_KEY_DEVICEID").toString())
.nickName(claims.get("CLAIM_KEY_NICKNAME").toString())
.build();
}
return null;
}

public String getEmailTokenFromAuthCode(String token) {
Claims claims = getClaimsFromToken(tokenSubBearer(token), accessSecretKey);
if (claims != null) {
return claims.get("CLAIM_KEY_SEND_AUTH_PASSWORD").toString();
}
return null;
}

private Claims getClaimsFromToken(String token, String secretKey) {
try {
Key key = Keys.hmacShaKeyFor(secretKey.getBytes());
Expand Down
27 changes: 27 additions & 0 deletions src/main/java/everymeal/server/global/util/MailUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package everymeal.server.global.util;


import jakarta.mail.MessagingException;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.MimeMessage.RecipientType;
import lombok.RequiredArgsConstructor;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Component;

@RequiredArgsConstructor
@Component
public class MailUtil {
private final JavaMailSender javaMailSender;

public void sendMail(String email, String title, String text) {
try {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
mimeMessage.setSubject(title);
mimeMessage.setText(text);
mimeMessage.setRecipients(RecipientType.TO, email);
javaMailSender.send(mimeMessage);
} catch (MessagingException e) {
e.printStackTrace();
}
}
}
36 changes: 34 additions & 2 deletions src/main/java/everymeal/server/user/controller/UserController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,13 @@
import everymeal.server.global.util.authresolver.Auth;
import everymeal.server.global.util.authresolver.AuthUser;
import everymeal.server.global.util.authresolver.entity.AuthenticatedUser;
import everymeal.server.user.controller.dto.request.UserEmailAuthReq;
import everymeal.server.user.controller.dto.request.UserEmailAuthVerifyReq;
import everymeal.server.user.controller.dto.response.UserEmailAuthRes;
import everymeal.server.user.controller.dto.response.UserLoginRes;
import everymeal.server.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand Down Expand Up @@ -66,9 +70,37 @@ public ResponseEntity<ApplicationResponse<UserLoginRes>> login(

@Auth(require = true)
@GetMapping("/auth")
@Operation(summary = "유저 인증 여부")
@Operation(
summary = "유저 인증 여부",
description = "유저가 인증되었는지 여부를 반환합니다. <br> 인증되었다면 true, 아니라면 false를 반환합니다.")
@SecurityRequirement(name = "bearerAuth")
public ApplicationResponse<Boolean> isAuth(@AuthUser AuthenticatedUser authenticatedUser) {
public ApplicationResponse<Boolean> isAuth(
@AuthUser @Parameter(hidden = true) AuthenticatedUser authenticatedUser) {
return ApplicationResponse.ok(userService.isAuth(authenticatedUser));
}

@Auth(require = true)
@SecurityRequirement(name = "bearerAuth")
@PostMapping("/email/auth")
@Operation(
summary = "이메일 인증",
description =
"이메일 인증을 진행합니다. <br> Response에 5분 동안 유효한 JWT 토큰이 담기는데 해당 토큰에는 발송 값이 들어있습니다.")
public ApplicationResponse<UserEmailAuthRes> emailAuth(
@RequestBody UserEmailAuthReq request,
@AuthUser @Parameter(hidden = true) AuthenticatedUser authenticatedUser) {
return ApplicationResponse.ok(userService.emailAuth(request, authenticatedUser));
}

@Auth(require = true)
@SecurityRequirement(name = "bearerAuth")
@PostMapping("/email/auth/verify")
@Operation(
summary = "이메일 인증 확인",
description = "이메일 인증을 확인합니다. <br> Request에는 이메일 인증 시 발송된 값이 담겨야 합니다.")
public ApplicationResponse<Boolean> verifyEmailAuth(
@RequestBody UserEmailAuthVerifyReq request,
@AuthUser @Parameter(hidden = true) AuthenticatedUser authenticatedUser) {
return ApplicationResponse.ok(userService.verifyEmailAuth(request, authenticatedUser));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package everymeal.server.user.controller.dto.request;


import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Getter
public class UserEmailAuthReq {

@Email(message = "이메일 형식이 아닙니다.")
@NotBlank(message = "이메일을 입력해주세요.")
@Schema(description = "이메일", example = "[email protected]")
private String email;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package everymeal.server.user.controller.dto.request;


import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Setter
public class UserEmailAuthVerifyReq {

@Schema(
description = "이메일 인증 API에서 반환된 토큰 값",
defaultValue = "eyBdvsjfgsdkgb",
example = "eyBdvsjfgsdkgb")
private String emailAuthToken;

@Schema(description = "이메일 유저가 입력한 인증 값", defaultValue = "123456", example = "123456")
private String emailAuthValue;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package everymeal.server.user.controller.dto.request;


import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
public class UserProfileCreateReq {

private String nickName;
private String profileImage;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package everymeal.server.user.controller.dto.response;


import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.Setter;

@AllArgsConstructor
@Getter
@Setter
@Builder
public class UserEmailAuthRes {

private String emailAuthToken;
}
4 changes: 4 additions & 0 deletions src/main/java/everymeal/server/user/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,8 @@ public User(String deviceId, String nickName, String email, University universit
this.email = email;
this.university = university;
}

public void setEmail(String email) {
this.email = email;
}
}
7 changes: 7 additions & 0 deletions src/main/java/everymeal/server/user/service/UserService.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@


import everymeal.server.global.util.authresolver.entity.AuthenticatedUser;
import everymeal.server.user.controller.dto.request.UserEmailAuthReq;
import everymeal.server.user.controller.dto.request.UserEmailAuthVerifyReq;
import everymeal.server.user.controller.dto.response.UserEmailAuthRes;
import everymeal.server.user.controller.dto.response.UserLoginRes;

public interface UserService {
Expand All @@ -11,4 +14,8 @@ public interface UserService {
UserLoginRes login(String userDeviceId);

Boolean isAuth(AuthenticatedUser authenticatedUser);

UserEmailAuthRes emailAuth(UserEmailAuthReq request, AuthenticatedUser authenticatedUser);

Boolean verifyEmailAuth(UserEmailAuthVerifyReq request, AuthenticatedUser authenticatedUser);
}
50 changes: 49 additions & 1 deletion src/main/java/everymeal/server/user/service/UserServiceImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,32 @@
import everymeal.server.global.exception.ApplicationException;
import everymeal.server.global.exception.ExceptionList;
import everymeal.server.global.util.JwtUtil;
import everymeal.server.global.util.MailUtil;
import everymeal.server.global.util.authresolver.entity.AuthenticatedUser;
import everymeal.server.user.controller.dto.request.UserEmailAuthReq;
import everymeal.server.user.controller.dto.request.UserEmailAuthVerifyReq;
import everymeal.server.user.controller.dto.response.UserEmailAuthRes;
import everymeal.server.user.controller.dto.response.UserLoginRes;
import everymeal.server.user.entity.User;
import everymeal.server.user.repository.UserRepository;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Random;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class UserServiceImpl implements UserService {

private final UserRepository userRepository;
private final JwtUtil jwtUtil;
private final MailUtil mailUtil;

@Override
@Transactional
public Boolean signUp(String userDeviceId) {
User user = User.builder().deviceId(userDeviceId).build();
userRepository.save(user);
Expand All @@ -42,6 +53,43 @@ public Boolean isAuth(AuthenticatedUser authenticatedUser) {
userRepository
.findByDeviceId(authenticatedUser.getDeviceId())
.orElseThrow(() -> new ApplicationException(ExceptionList.USER_NOT_FOUND));
return !user.getEmail().isBlank();
return user.getEmail() != null;
}

@Override
public UserEmailAuthRes emailAuth(
UserEmailAuthReq request, AuthenticatedUser authenticatedUser) {
try {
Random random = SecureRandom.getInstanceStrong();
int authCode = random.nextInt(900000) + 100000;
mailUtil.sendMail(request.getEmail(), "[에브리밀] 대학교 이메일 인증", "인증번호 : " + authCode);
String mailJwt =
jwtUtil.generateEmailToken(
authenticatedUser.getIdx(),
request.getEmail(),
Integer.toString(authCode));
return UserEmailAuthRes.builder().emailAuthToken(mailJwt).build();
} catch (NoSuchAlgorithmException e) {
return null;
}
}

@Override
@Transactional
public Boolean verifyEmailAuth(
UserEmailAuthVerifyReq request, AuthenticatedUser authenticatedUser) {
String emailTokenFromAuthCode =
jwtUtil.getEmailTokenFromAuthCode(request.getEmailAuthToken());
if (request.getEmailAuthValue().equals(emailTokenFromAuthCode)) {
User user =
userRepository
.findById(authenticatedUser.getIdx())
.orElseThrow(
() -> new ApplicationException(ExceptionList.USER_NOT_FOUND));
user.setEmail(emailTokenFromAuthCode);
return true;
} else {
throw new ApplicationException(ExceptionList.USER_AUTH_FAIL);
}
}
}
2 changes: 1 addition & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ spring:
include: secret
jpa:
hibernate:
ddl-auto: update
ddl-auto: none
properties:
hibernate:
show_sql: true
Expand Down
Loading

0 comments on commit 81ac123

Please sign in to comment.