Skip to content

Commit

Permalink
Merge pull request #22 from coffee-meet/feat/#20
Browse files Browse the repository at this point in the history
Feat/#20 토큰 검증을 위한 ArgumentResolver 생성
  • Loading branch information
yumyeonghan authored Oct 23, 2023
2 parents 9b5846e + f141049 commit d9e7f6e
Show file tree
Hide file tree
Showing 9 changed files with 133 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package coffeemeet.server.auth;

import coffeemeet.server.auth.domain.RefreshToken;
import org.springframework.data.repository.CrudRepository;

public interface RefreshTokenRepository extends CrudRepository<RefreshToken, Long> {

}
23 changes: 23 additions & 0 deletions src/main/java/coffeemeet/server/auth/domain/RefreshToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package coffeemeet.server.auth.domain;

import lombok.Builder;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;

@Getter
@RedisHash(value = "refresh", timeToLive = 1209600)
public class RefreshToken {

@Id
private Long userId;

private String value;

@Builder
private RefreshToken(Long userId, String value) {
this.userId = userId;
this.value = value;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
import static org.springframework.http.HttpStatus.CREATED;

import coffeemeet.server.certification.service.CertificationService;
import coffeemeet.server.common.annotation.Login;
import coffeemeet.server.common.util.FileUtils;
import coffeemeet.server.user.dto.AuthInfo;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
Expand All @@ -21,17 +22,16 @@ public class CertificationController {

private final CertificationService certificationService;

@PostMapping("/users/{userId}/business-card")
@PostMapping("/users/business-card")
@ResponseStatus(CREATED)
public void uploadBusinessCard(
@PathVariable("userId")
@NotNull(message = "유저 ID는 null일 수 없습니다.")
long userId,
@Login
AuthInfo authInfo,
@RequestPart("businessCard")
@NotNull(message = "명함 이미지는 null일 수 없습니다.")
MultipartFile businessCard
) {
certificationService.uploadBusinessCard(userId,
certificationService.uploadBusinessCard(authInfo.userId(),
FileUtils.convertMultipartFileToFile(businessCard));
}

Expand Down
57 changes: 57 additions & 0 deletions src/main/java/coffeemeet/server/common/UserArgumentResolver.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package coffeemeet.server.common;

import coffeemeet.server.auth.RefreshTokenRepository;
import coffeemeet.server.auth.domain.RefreshToken;
import coffeemeet.server.auth.utils.JwtTokenProvider;
import coffeemeet.server.common.annotation.Login;
import coffeemeet.server.user.dto.AuthInfo;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
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;

@Component
@RequiredArgsConstructor
public class UserArgumentResolver implements HandlerMethodArgumentResolver {

public static final String USER_AUTHENTICATION_FAILED_MESSAGE = "사용자(%s)의 갱신 토큰이 존재하지 않습니다.";
private static final String HEADER_AUTHENTICATION_FAILED_MESSAGE = "(%s)는 잘못된 권한 헤더입니다.";

private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenRepository refreshTokenRepository;


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

@Override
public AuthInfo resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
HttpServletRequest httpServletRequest = (HttpServletRequest) webRequest.getNativeRequest();
String authHeader = httpServletRequest.getHeader("Authorization");

if (authHeader != null && authHeader.startsWith("Bearer ")) {
String token = authHeader.substring(7);
Long userId = jwtTokenProvider.extractUserId(token);
RefreshToken refreshToken = getRefreshToken(userId);
return new AuthInfo(userId, refreshToken.getValue());
}
throw new IllegalArgumentException(
String.format(HEADER_AUTHENTICATION_FAILED_MESSAGE, authHeader)
);
}

private RefreshToken getRefreshToken(Long userId) {
return refreshTokenRepository.findById(userId)
.orElseThrow(() -> new IllegalArgumentException(String.format(
USER_AUTHENTICATION_FAILED_MESSAGE, userId)));
}

}
12 changes: 12 additions & 0 deletions src/main/java/coffeemeet/server/common/annotation/Login.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package coffeemeet.server.common.annotation;

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 Login {

}
10 changes: 10 additions & 0 deletions src/main/java/coffeemeet/server/common/config/AuthWebConfig.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
package coffeemeet.server.common.config;

import coffeemeet.server.auth.RefreshTokenRepository;
import coffeemeet.server.auth.utils.JwtTokenProvider;
import coffeemeet.server.auth.utils.converter.OAuthProviderConverter;
import coffeemeet.server.common.UserArgumentResolver;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@RequiredArgsConstructor
public class AuthWebConfig implements WebMvcConfigurer {

private final JwtTokenProvider jwtTokenProvider;
private final RefreshTokenRepository refreshTokenRepository;

@Override
public void addFormatters(FormatterRegistry registry) {
registry.addConverter(new OAuthProviderConverter());
}

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new UserArgumentResolver(jwtTokenProvider, refreshTokenRepository));
}

}
16 changes: 8 additions & 8 deletions src/main/java/coffeemeet/server/common/util/FileUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,6 @@ public final class FileUtils {
private static final String MULTIPART_FILE_TRANSFER_ERROR = "MULTIPART FILE을 FILE로 변환 중 오류가 발생했습니다.";
private static final String FILE_DELETE_ERROR = "FILE 삭제 중 오류가 발생했습니다.";

public static class FileIOException extends RuntimeException {

public FileIOException(String message, Throwable e) {
super(message, e);
}

}

public static File convertMultipartFileToFile(MultipartFile multipartFile) {
try {
File file = File.createTempFile("temp", multipartFile.getOriginalFilename());
Expand All @@ -39,4 +31,12 @@ public static void delete(File file) {
}
}

public static class FileIOException extends RuntimeException {

public FileIOException(String message, Throwable e) {
super(message, e);
}

}

}
5 changes: 5 additions & 0 deletions src/main/java/coffeemeet/server/user/dto/AuthInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package coffeemeet.server.user.dto;

public record AuthInfo(Long userId, String refreshToken) {

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.documentationConfiguration;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;

import coffeemeet.server.auth.RefreshTokenRepository;
import coffeemeet.server.auth.utils.JwtTokenProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.BeforeEach;
Expand All @@ -27,6 +28,9 @@ public abstract class ControllerTestConfig {
@MockBean
protected JwtTokenProvider jwtTokenProvider;

@MockBean
protected RefreshTokenRepository refreshTokenRepository;

@BeforeEach
void setUp(WebApplicationContext ctx, RestDocumentationContextProvider restDocumentation) {
mockMvc = MockMvcBuilders.webAppContextSetup(ctx)
Expand Down

0 comments on commit d9e7f6e

Please sign in to comment.