Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat/#20 토큰 검증을 위한 ArgumentResolver 생성 #22

Merged
merged 9 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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 {

}
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