Skip to content

Commit

Permalink
Merge pull request #51 from ghdcksgml1/dev
Browse files Browse the repository at this point in the history
Dev
  • Loading branch information
ghdcksgml1 authored Sep 14, 2023
2 parents 29e9f82 + 85d16ad commit ce59cb8
Show file tree
Hide file tree
Showing 32 changed files with 753 additions and 322 deletions.
5 changes: 4 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,7 @@ projectGroup=com.heachi
kotlinVersion=1.6.21
springBootVersion=3.1.2
springDependencyManagementVersion=1.1.2
springCloudDependenciesVersion=2022.0.3
springCloudDependenciesVersion=2022.0.3

DOCKER_USERNAME=
DOCKER_PASSWORD=
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication(scanBasePackages = "com.heachi")
@EnableJpaAuditing // JPA Auditing 기능 활성화 - BaseEntity
public class HeachiAuthApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package com.heachi.auth.api.controller.auth;

import com.heachi.admin.common.exception.ExceptionMessage;
import com.heachi.admin.common.exception.auth.AuthException;
import com.heachi.admin.common.exception.oauth.OAuthException;
import com.heachi.admin.common.response.JsonResult;
import com.heachi.auth.api.controller.auth.request.AuthRegisterRequest;
import com.heachi.auth.api.controller.auth.response.UserSimpleInfoResponse;
import com.heachi.auth.api.service.auth.AuthService;
import com.heachi.auth.api.service.auth.request.AuthServiceRegisterRequest;
import com.heachi.auth.api.service.auth.response.AuthServiceLoginResponse;
import com.heachi.auth.api.service.oauth.OAuthService;
import com.heachi.mysql.define.user.User;
import com.heachi.mysql.define.user.constant.UserPlatformType;
import com.heachi.mysql.define.user.constant.UserRole;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
Expand All @@ -17,12 +23,13 @@

import java.security.Principal;

import static com.heachi.mysql.define.user.constant.UserRole.*;

@Slf4j
@RequiredArgsConstructor
@RequestMapping("/auth")
@RestController
public class AuthController {

private final AuthService authService;
private final OAuthService oAuthService;

Expand All @@ -41,28 +48,25 @@ public JsonResult<String> login(
@RequestParam("code") String code,
@RequestParam("state") String state,
HttpServletRequest request) {
String requestState = request.getSession().getId();

// state 값 유효성 검증
if (!state.equals(requestState)) {
throw new OAuthException(ExceptionMessage.OAUTH_INVALID_STATE);
}
// state 값 검증 (redis)

AuthServiceLoginResponse loginResponse = authService.login(platformType, code, request.getSession().getId());

return JsonResult.successOf(loginResponse);
}

@PostMapping("/{platformType}/register")
@PostMapping("/register")
public JsonResult<?> register(
@PathVariable("platformType") UserPlatformType platformType,
@RequestBody AuthRegisterRequest request) {
return JsonResult.successOf();
@Valid @RequestBody AuthRegisterRequest request) {

AuthServiceLoginResponse registerResponse = authService.register(AuthServiceRegisterRequest.of(request));

return JsonResult.successOf(registerResponse);
}

@GetMapping("/info")
public JsonResult<?> userInfo(@AuthenticationPrincipal UserDetails user) {
public JsonResult<UserSimpleInfoResponse> userInfo(@AuthenticationPrincipal User user) {

return JsonResult.successOf(user);
return JsonResult.successOf(UserSimpleInfoResponse.of(user));
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,33 @@
package com.heachi.auth.api.controller.auth.request;

import com.heachi.mysql.define.user.constant.UserRole;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthRegisterRequest {
}
@NotEmpty
@Email
private String email;

@Enumerated(EnumType.STRING)
@NotNull
private UserRole role;

// 숫자 값이므로 null이 아니어야 하니까 @NotEmpty 대신 @NotNull 사용 -> 빈 문자열("") 허용
@NotNull
@Pattern(regexp = "^\\d{11}$",
message = "전화번호는 11자리의 숫자로 입력해야 합니다.")
private String phoneNumber;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.heachi.auth.api.controller.auth.response;

import com.heachi.mysql.define.user.User;
import com.heachi.mysql.define.user.constant.UserRole;
import lombok.Builder;
import lombok.Getter;

@Getter
public class UserSimpleInfoResponse {
private UserRole role;
private String name;
private String email;
private String profileImageUrl;

@Builder
private UserSimpleInfoResponse(UserRole role, String name, String email, String profileImageUrl) {
this.role = role;
this.name = name;
this.email = email;
this.profileImageUrl = profileImageUrl;
}

public static UserSimpleInfoResponse of(User user) {
return UserSimpleInfoResponse.builder()
.role(user.getRole())
.name(user.getName())
.email(user.getEmail())
.profileImageUrl(user.getProfileImageUrl())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.heachi.auth.api.service.auth;

import com.heachi.admin.common.exception.ExceptionMessage;
import com.heachi.admin.common.exception.auth.AuthException;
import com.heachi.admin.common.exception.oauth.OAuthException;
import com.heachi.auth.api.service.auth.request.AuthServiceRegisterRequest;
import com.heachi.auth.api.service.auth.response.AuthServiceLoginResponse;
Expand All @@ -11,14 +12,16 @@
import com.heachi.mysql.define.user.constant.UserPlatformType;
import com.heachi.mysql.define.user.constant.UserRole;
import com.heachi.mysql.define.user.repository.UserRepository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jakarta.persistence.TypedQuery;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.HashMap;
import java.util.Objects;
import java.util.Optional;


@Slf4j
Expand All @@ -30,6 +33,9 @@ public class AuthService {
private final UserRepository userRepository;
private final OAuthService oAuthService;
private final JwtService jwtService;
private final BCryptPasswordEncoder passwordEncoder;
@PersistenceContext
private EntityManager entityManager;

private static final String ROLE_CLAIM = "role";
private static final String NAME_CLAIM = "name";
Expand Down Expand Up @@ -58,31 +64,50 @@ public AuthServiceLoginResponse login(UserPlatformType platformType, String code
// 기존 회원의 경우 name, profileImageUrl 변하면 update
findUser.updateProfile(loginResponse.getName(), loginResponse.getProfileImageUrl());

// 인증이 완료되지 않은 사용자(UNAUTH) 처리
if (findUser.getRole() == UserRole.UNAUTH) {
return AuthServiceLoginResponse.builder()
.token(null)
.role(UserRole.UNAUTH)
.build();
// JWT 토큰 발급
final String token = createJwtToken(findUser);

return AuthServiceLoginResponse.builder()
.token(token)
.role(findUser.getRole())
.build();
}

@Transactional
public AuthServiceLoginResponse register(AuthServiceRegisterRequest request) {
User findUser = userRepository.findByEmail(request.getEmail()).orElseThrow(() -> {
// UNAUTH인 토큰을 받고 회원 탈퇴 후 그 토큰으로 회원가입 요청시 예외 처리
throw new AuthException(ExceptionMessage.AUTH_INVALID_REGISTER);
});

// UNAUTH 토큰으로 회원가입을 요청했지만 이미 update되어 UNAUTH가 아닌 사용자 예외 처리
if (findUser.getRole() != UserRole.UNAUTH) {
throw new AuthException(ExceptionMessage.AUTH_DUPLICATE_UNAUTH_REGISTER);
}

// JWT 토큰 생성을 위한 claims 생성
HashMap<String, String> claims = new HashMap<>();
claims.put(ROLE_CLAIM, findUser.getRole().name());
claims.put(NAME_CLAIM, findUser.getName());
claims.put(PROFILE_IMAGE_CLAIM, findUser.getProfileImageUrl());
// 회원가입 정보 DB 반영
findUser.updateRegister(request.getRole(), request.getPhoneNumber());

// JWT 토큰 생성 (claims, UserDetails)
final String token = jwtService.generateToken(claims, findUser);
// JWT 토큰 재발급
final String token = createJwtToken(findUser);

// 로그인 반환 객체 생성
return AuthServiceLoginResponse.builder()
.token(token)
.role(findUser.getRole())
.build();
}

public AuthServiceLoginResponse register(UserPlatformType platformType, AuthServiceRegisterRequest request) {
return null;
private String createJwtToken(User user) {
// JWT 토큰 생성을 위한 claims 생성
HashMap<String, String> claims = new HashMap<>();
claims.put(ROLE_CLAIM, user.getRole().name());
claims.put(NAME_CLAIM, user.getName());
claims.put(PROFILE_IMAGE_CLAIM, user.getProfileImageUrl());

// JWT 토큰 생성 (claims, UserDetails)
final String token = jwtService.generateToken(claims, user);

// 로그인 반환 객체 생성
return token;
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
package com.heachi.auth.api.service.auth.request;


import com.heachi.auth.api.controller.auth.request.AuthRegisterRequest;
import com.heachi.mysql.define.user.constant.UserRole;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class AuthServiceRegisterRequest {
private String email;
private UserRole role;
private String phoneNumber;

public static AuthServiceRegisterRequest of(AuthRegisterRequest request) {
return AuthServiceRegisterRequest.builder()
.email(request.getEmail())
.role(request.getRole())
.phoneNumber(request.getPhoneNumber())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.heachi.auth.api.service.jwt;

import com.heachi.admin.common.exception.ExceptionMessage;
import com.heachi.admin.common.exception.jwt.JwtException;
import com.heachi.mysql.define.user.constant.UserRole;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
Expand Down Expand Up @@ -51,16 +54,23 @@ public String generateToken(Map<String, String> extraClaims, UserDetails userDet
.compact();
}

public boolean isTokenValid(String token, UserDetails userDetails) {
public boolean isTokenValid(String token, String username) {
Claims claims = extractAllClaims(token);

if (!claims.containsKey("role")) return false;
if (!claims.containsKey("name")) return false;
if (!claims.containsKey("profileImageUrl")) return false;
try {
if (!claims.containsKey("role")) {
UserRole.valueOf(claims.get("role", String.class));
return false;
}
if (!claims.containsKey("name")) return false;
if (!claims.containsKey("profileImageUrl")) return false;
} catch (RuntimeException e) { // covered for NullPointException, IllegalArgumentException
throw new JwtException(ExceptionMessage.JWT_ILLEGAL_ARGUMENT);
}

String username = claims.getSubject();
String claimsSubject = claims.getSubject();

return (username.equals(userDetails.getUsername())) && !isTokenExpired(token);
return (claimsSubject.equals(username)) && !isTokenExpired(token);
}

private boolean isTokenExpired(String token) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,31 @@
package com.heachi.auth.config.advice;

import com.heachi.admin.common.response.JsonResult;
import org.springframework.http.HttpStatus;
import org.springframework.context.support.DefaultMessageSourceResolvable;
import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import java.util.stream.Collectors;

@RestControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(BindException.class)
public JsonResult bindException(BindException e) {
return JsonResult.failOf(
e.getBindingResult()
.getFieldErrors()
.stream()
.map(fieldError -> fieldError.getField() + ": " + fieldError.getDefaultMessage())
.collect(Collectors.joining(", "))
);
}

@ExceptionHandler(Exception.class)
public JsonResult<Exception> exception(Exception e) {
return JsonResult.failOf(e.getMessage());
}


}
Loading

0 comments on commit ce59cb8

Please sign in to comment.