Skip to content

Commit

Permalink
Merge pull request #10 from Link-MIND/test
Browse files Browse the repository at this point in the history
[Merge] ํ˜„์žฌ๊นŒ์ง€ ๋ณ€๊ฒฝ์‚ฌํ•ญ develop ๋ธŒ๋žœ์น˜ ๋ฐ˜์˜
  • Loading branch information
sss4920 authored Dec 31, 2023
2 parents d3977d1 + d75b693 commit cf556e3
Show file tree
Hide file tree
Showing 26 changed files with 648 additions and 7 deletions.
Binary file added .DS_Store
Binary file not shown.
15 changes: 12 additions & 3 deletions linkmind/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,21 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'mysql:mysql-connector-java:8.0.32'
compileOnly 'org.projectlombok:lombok'
//implementation 'org.springframework.boot:spring-boot-starter-security'
//testImplementation 'org.springframework.security:spring-security-test'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
runtimeOnly 'com.mysql:mysql-connector-j'
implementation 'org.springframework.boot:spring-boot-starter-validation'

//JWT
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.2'
implementation group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.2'


//FCM
implementation group: 'com.google.firebase', name: 'firebase-admin', version: '6.8.1'
implementation 'com.squareup.okhttp3:okhttp:4.10.0' // Firebase ์„œ๋ฒ„๋กœ ํ‘ธ์‹œ ๋ฉ”์‹œ์ง€ ์ „์†ก ์‹œ ํ•„์š”

implementation 'org.springframework.boot:spring-boot-starter-actuator'
}

Expand Down
11 changes: 11 additions & 0 deletions linkmind/src/main/java/com/app/toaster/config/UserId.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.app.toaster.config;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface UserId {
}
45 changes: 45 additions & 0 deletions linkmind/src/main/java/com/app/toaster/config/UserIdResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.app.toaster.config;

import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;

import com.app.toaster.config.jwt.JwtService;

import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Component
public class UserIdResolver implements HandlerMethodArgumentResolver {

private final JwtService jwtService;

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(UserId.class) && Long.class.equals(parameter.getParameterType());
}

@Override
public Object resolveArgument(@NotNull MethodParameter parameter, ModelAndViewContainer modelAndViewContainer, @NotNull NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
final HttpServletRequest request = (HttpServletRequest) webRequest.getNativeRequest();
final String token = request.getHeader("accessToken");

// ํ† ํฐ ๊ฒ€์ฆ
if (!jwtService.verifyToken(token)) {
throw new RuntimeException(String.format("USER_ID๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. (%s - %s)", parameter.getClass(), parameter.getMethod()));
}

// ์œ ์ € ์•„์ด๋”” ๋ฐ˜ํ™˜
final String tokenContents = jwtService.getJwtContents(token);
try {
return Long.parseLong(tokenContents);
} catch (NumberFormatException e) {
throw new RuntimeException(String.format("USER_ID๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ–ˆ์Šต๋‹ˆ๋‹ค. (%s - %s)", parameter.getClass(), parameter.getMethod()));
}
}
}
88 changes: 88 additions & 0 deletions linkmind/src/main/java/com/app/toaster/config/jwt/JwtService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.app.toaster.config.jwt;


import static io.jsonwebtoken.Jwts.*;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Date;

import javax.crypto.SecretKey;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import com.app.toaster.exception.Error;
import com.app.toaster.exception.model.NotFoundException;
import com.app.toaster.exception.model.UnauthorizedException;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Header;
import io.jsonwebtoken.security.Keys;
import jakarta.annotation.PostConstruct;

@Service
public class JwtService {

@Value("${jwt.secret}")
private String jwtSecret;

@PostConstruct
protected void init() {
jwtSecret = Base64.getEncoder()
.encodeToString(jwtSecret.getBytes(StandardCharsets.UTF_8));
}

// JWT ํ† ํฐ ๋ฐœ๊ธ‰
public String issuedToken(String userId, Long tokenExpirationTime) {
final Date now = new Date();

// ํด๋ ˆ์ž„ ์ƒ์„ฑ
final Claims claims = claims()
.setSubject("token")
.setIssuedAt(now)
.setExpiration(new Date(now.getTime() + tokenExpirationTime));

//private claim ๋“ฑ๋ก
claims.put("userId", userId);

return builder()
.setHeaderParam(Header.TYPE , Header.JWT_TYPE)
.setClaims(claims)
.signWith(getSigningKey())
.compact();
}

private SecretKey getSigningKey() {
final byte[] keyBytes = jwtSecret.getBytes(StandardCharsets.UTF_8);
return Keys.hmacShaKeyFor(keyBytes);
}

// JWT ํ† ํฐ ๊ฒ€์ฆ
public boolean verifyToken(String token) {
try {
final Claims claims = getBody(token);
return true;
} catch (RuntimeException e) {
if (e instanceof ExpiredJwtException) {
throw new UnauthorizedException(Error.TOKEN_TIME_EXPIRED_EXCEPTION, Error.TOKEN_TIME_EXPIRED_EXCEPTION.getMessage());
}
throw new NotFoundException(Error.NOT_FOUND_USER_EXCEPTION, Error.NOT_FOUND_USER_EXCEPTION.getMessage());
}
}

private Claims getBody(final String token) {
return parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
.getBody();
}

// JWT ํ† ํฐ ๋‚ด์šฉ ํ™•์ธ
public String getJwtContents(String token) {
final Claims claims = getBody(token);
return (String) claims.get("userId");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.app.toaster.controller;

import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import com.app.toaster.common.dto.ApiResponse;
import com.app.toaster.config.UserId;
import com.app.toaster.controller.request.auth.SignInRequestDto;
import com.app.toaster.controller.response.auth.SignInResponseDto;
import com.app.toaster.controller.response.auth.TokenResponseDto;
import com.app.toaster.exception.Success;
import com.app.toaster.service.auth.AuthService;

import lombok.RequiredArgsConstructor;

@RestController
@RequiredArgsConstructor
@RequestMapping("/auth")
public class AuthController {
private final AuthService authService;

@PostMapping
@ResponseStatus(HttpStatus.OK)
public ApiResponse<SignInResponseDto> signIn(
@RequestHeader("Authorization") String socialAccessToken,
@RequestBody SignInRequestDto requestDto
) {
return ApiResponse.success(Success.LOGIN_SUCCESS, authService.signIn(socialAccessToken, requestDto));
}

@PostMapping("/token")
@ResponseStatus(HttpStatus.OK)
public ApiResponse<TokenResponseDto> reissueToken(@RequestHeader String refreshToken) {
return ApiResponse.success(Success.RE_ISSUE_TOKEN_SUCCESS, authService.issueToken(refreshToken));
}

@PostMapping("/sign-out")
@ResponseStatus(HttpStatus.OK)
public ApiResponse signOut(@UserId Long userId) {
authService.signOut(userId);
return ApiResponse.success(Success.SIGNOUT_SUCCESS);
}

@DeleteMapping("/withdraw")
@ResponseStatus(HttpStatus.OK)
public ApiResponse withdraw(@UserId Long userId){
authService.withdraw(userId);
return ApiResponse.success(Success.DELETE_USER_SUCCESS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package com.app.toaster.controller.request.auth;

public record SignInRequestDto(String socialType, String fcmToken) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.app.toaster.controller.response.auth;

public record SignInResponseDto(Long userId, String accessToken, String refreshToken, String fcmToken, Boolean isRegistered,Boolean FcmIsAllowed) {
public static SignInResponseDto of(Long userId, String accessToken, String refreshToken, String fcmToken,
Boolean isRegistered, Boolean fcmIsAllowed){
return new SignInResponseDto(userId,accessToken, refreshToken,fcmToken,isRegistered,fcmIsAllowed);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.app.toaster.controller.response.auth;

public record TokenResponseDto(String accessToken, String refreshToken) {
public static TokenResponseDto of(String accessToken, String refreshToken){
return new TokenResponseDto(accessToken,refreshToken);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
Expand Down
2 changes: 1 addition & 1 deletion linkmind/src/main/java/com/app/toaster/domain/Toast.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public class Toast {
private Long Id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "toast_id")
@JoinColumn(name = "user_id")
private User user;

private String title;
Expand Down
16 changes: 14 additions & 2 deletions linkmind/src/main/java/com/app/toaster/exception/Error.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,24 @@ public enum Error {
* 404 NOT FOUND
*/
DUMMY_NOT_FOUND(HttpStatus.NOT_FOUND, "๋”๋ฏธ์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋œ ๋“ค์–ด๊ฐ„ ๊ฒƒ ๊ฐ™์•„์š”"),

RESERVATION_NOT_FOUND(HttpStatus.NOT_FOUND, "์˜ˆ์•ฝ์ •๋ณด๋ฅผ ์ฐพ์ง€ ๋ชปํ–ˆ์–ด์š”"),
NOT_FOUND_USER_EXCEPTION(HttpStatus.NOT_FOUND, "์ฐพ์„ ์ˆ˜ ์—†๋Š” ์œ ์ €์ž…๋‹ˆ๋‹ค."),

/**
* 401 UNAUTHORIZED EXCEPTION
*/
TOKEN_TIME_EXPIRED_EXCEPTION(HttpStatus.UNAUTHORIZED, "๋งŒ๋ฃŒ๋œ ํ† ํฐ์ž…๋‹ˆ๋‹ค."),
INVALID_APPLE_PUBLIC_KEY(HttpStatus.UNAUTHORIZED, "์œ ํšจํ•˜์ง€์•Š์€ ์• ํ”Œ ํผ๋ธ”๋ฆญ ํ‚ค ์ž…๋‹ˆ๋‹ค."),
EXPIRED_APPLE_IDENTITY_TOKEN(HttpStatus.UNAUTHORIZED, "๋งŒ๋ฃŒ๋œ ์•„์ด๋ดํ‹ฐํ‹ฐ ํ† ํฐ์ž…๋‹ˆ๋‹ค."),
INVALID_APPLE_IDENTITY_TOKEN(HttpStatus.UNAUTHORIZED, "์ž˜๋ชป๋œ ์•„์ด๋ดํ‹ฐํ‹ฐ ํ† ํฐ์ž…๋‹ˆ๋‹ค."),

UNPROCESSABLE_ENTITY_DELETE_EXCEPTION(HttpStatus.UNPROCESSABLE_ENTITY, "์„œ๋ฒ„์—์„œ ์š”์ฒญ์„ ์ดํ•ดํ•ด ์‚ญ์ œํ•˜๋ ค๋Š” ๋„์ค‘ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค."),
/**
* 500 INTERNAL_SERVER_ERROR
*/
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "์•Œ ์ˆ˜ ์—†๋Š” ์„œ๋ฒ„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค")
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "์•Œ ์ˆ˜ ์—†๋Š” ์„œ๋ฒ„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค"),
INVALID_ENCRYPT_COMMUNICATION(HttpStatus.INTERNAL_SERVER_ERROR, "ios ํ†ต์‹  ์ฆ๋ช… ๊ณผ์ • ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."),
CREATE_PUBLIC_KEY_EXCEPTION(HttpStatus.INTERNAL_SERVER_ERROR, "publickey ์ƒ์„ฑ ๊ณผ์ • ์ค‘ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."),
;

private final HttpStatus httpStatus;
Expand Down
6 changes: 6 additions & 0 deletions linkmind/src/main/java/com/app/toaster/exception/Success.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ public enum Success {
GET_MAIN_SUCCESS(HttpStatus.OK, "๋ฉ”์ธ ํŽ˜์ด์ง€ ์กฐํšŒ ์„ฑ๊ณต"),
GET_TICKET_SUCCESS(HttpStatus.OK, "ํ‹ฐ์ผ“ ์„ ํƒ ํŽ˜์ด์ง€ ์กฐํšŒ ์„ฑ๊ณต"),

LOGIN_SUCCESS(HttpStatus.OK, "๋กœ๊ทธ์ธ ์„ฑ๊ณต"),
RE_ISSUE_TOKEN_SUCCESS(HttpStatus.OK, "ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ์„ฑ๊ณต"),
SIGNOUT_SUCCESS(HttpStatus.OK, "๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต"),
DELETE_USER_SUCCESS(HttpStatus.OK, "์œ ์ € ์‚ญ์ œ ์„ฑ๊ณต"),


/**
* 204 NO_CONTENT
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.app.toaster.exception.model;

import com.app.toaster.exception.Error;

public class UnauthorizedException extends CustomException{
public UnauthorizedException(Error error, String message) {
super(error, message);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.app.toaster.exception.model;

import com.app.toaster.exception.Error;

public class UnprocessableEntityException extends CustomException{
public UnprocessableEntityException(Error error, String message) {
super(error, message);
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.app.toaster.infrastructure;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;

import com.app.toaster.domain.SocialType;
import com.app.toaster.domain.User;

public interface UserRepository extends JpaRepository<User, Long> {
Boolean existsBySocialIdAndSocialType(String socialId, SocialType socialType);

Optional<User> findByUserId(Long userId);

Optional<User> findBySocialIdAndSocialType(String socialId, SocialType socialType);

Boolean existsByNickname(String s);

Optional<User> findByRefreshToken(String refreshToken);

Long deleteByUserId(Long userId);
}
Loading

0 comments on commit cf556e3

Please sign in to comment.