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

Release v.1.2.2 #719

Merged
merged 1 commit into from
Oct 6, 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
5 changes: 3 additions & 2 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,14 @@ repositories {
}

dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'
implementation 'org.springframework.boot:spring-boot-starter-log4j2'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'

modules {
module("org.springframework.boot:spring-boot-starter-logging") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ public ReissuedTokenDto reissueAuthToken(
validateTokenInfo(accessTokenPayload, refreshTokenPayload);

final String newAccessToken = tokenProcessor.generateAccessToken(accessTokenPayload.memberId());
final String newRefreshToken = tokenProcessor.generateRefreshToken(accessTokenPayload.memberId());
final String newRefreshToken = tokenProcessor.generateRefreshToken(refreshTokenPayload.memberId());
redisTemplate.opsForValue().set(newRefreshToken, accessTokenPayload.memberId(), Duration.ofDays(14L));
return new ReissuedTokenDto(newAccessToken, newRefreshToken);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,25 @@
package com.votogether.global.config;

import com.votogether.global.jwt.JwtAuthenticationFilter;
import com.votogether.global.jwt.JwtAuthorizationArgumentResolver;
import com.votogether.global.jwt.TokenProcessor;
import com.votogether.global.log.context.MemberIdHolder;
import com.votogether.global.log.presentation.RequestLogInterceptor;
import com.votogether.global.log.presentation.RequestResponseCacheFilter;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@RequiredArgsConstructor
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

Expand All @@ -16,25 +28,61 @@ public class WebMvcConfig implements WebMvcConfigurer {
private static final String DEV_SERVER = "https://dev.votogether.com";
private static final String PROD_SERVER = "https://votogether.com";

private final MemberIdHolder memberIdHolder;
private final TokenProcessor tokenProcessor;
private final RequestLogInterceptor requestLogInterceptor;
private final JwtAuthorizationArgumentResolver jwtAuthorizationArgumentResolver;

public WebMvcConfig(final JwtAuthorizationArgumentResolver jwtAuthorizationArgumentResolver) {
this.jwtAuthorizationArgumentResolver = jwtAuthorizationArgumentResolver;
@Bean
public FilterRegistrationBean<CorsFilter> corsFilterRegistrationBean() {
final CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin(LOCALHOST_FRONTEND);
config.addAllowedOrigin(HTTPS_LOCALHOST_FRONTEND);
config.addAllowedOrigin(DEV_SERVER);
config.addAllowedOrigin(PROD_SERVER);
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.addExposedHeader(HttpHeaders.LOCATION);
config.addExposedHeader(HttpHeaders.SET_COOKIE);

final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);

final FilterRegistrationBean<CorsFilter> filterBean = new FilterRegistrationBean<>(new CorsFilter(source));
filterBean.setOrder(0);

return filterBean;
}

@Bean
public FilterRegistrationBean<RequestResponseCacheFilter> requestResponseCacheFilter() {
final FilterRegistrationBean<RequestResponseCacheFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new RequestResponseCacheFilter());
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(1);
return filterRegistrationBean;
}

@Bean
public FilterRegistrationBean<JwtAuthenticationFilter> jwtAuthenticationFilter() {
final FilterRegistrationBean<JwtAuthenticationFilter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(new JwtAuthenticationFilter(memberIdHolder, tokenProcessor));
filterRegistrationBean.addUrlPatterns("/*");
filterRegistrationBean.setOrder(2);
return filterRegistrationBean;
}

@Override
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(jwtAuthorizationArgumentResolver);
public void addInterceptors(final InterceptorRegistry registry) {
registry.addInterceptor(requestLogInterceptor)
.addPathPatterns("/**")
.order(1);
}

@Override
public void addCorsMappings(final CorsRegistry registry) {
registry.addMapping("/**")
.allowedHeaders("*")
.allowedOrigins(HTTPS_LOCALHOST_FRONTEND, LOCALHOST_FRONTEND, DEV_SERVER, PROD_SERVER)
.allowedMethods("*")
.allowCredentials(true)
.exposedHeaders(HttpHeaders.LOCATION, HttpHeaders.SET_COOKIE);
public void addArgumentResolvers(final List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(jwtAuthorizationArgumentResolver);
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.votogether.global.jwt;

import com.votogether.global.log.context.MemberIdHolder;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
Expand All @@ -12,11 +13,9 @@
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@RequiredArgsConstructor
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

private static final List<String> ALLOWED_URIS = List.of(
Expand All @@ -43,6 +42,7 @@ public class JwtAuthenticationFilter extends OncePerRequestFilter {
)
);

private final MemberIdHolder memberIdHolder;
private final TokenProcessor tokenProcessor;

@Override
Expand All @@ -54,6 +54,8 @@ protected void doFilterInternal(
final String token = request.getHeader(HttpHeaders.AUTHORIZATION);
final String tokenWithoutType = tokenProcessor.resolveToken(token);
tokenProcessor.validateToken(tokenWithoutType);
final TokenPayload tokenPayload = tokenProcessor.parseToken(tokenWithoutType);
memberIdHolder.setId(tokenPayload.memberId());
filterChain.doFilter(request, response);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.votogether.global.log.aop;

import com.votogether.global.log.context.QueryCount;
import java.sql.PreparedStatement;
import lombok.RequiredArgsConstructor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;

@RequiredArgsConstructor
public class ConnectionMethodInterceptor implements MethodInterceptor {

private final QueryCount queryCount;

@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
final Object result = invocation.proceed();
if (result instanceof PreparedStatement ps) {
final ProxyFactory proxyFactory = new ProxyFactory(ps);
proxyFactory.addAdvice(new PreparedStatementMethodInterceptor(queryCount));
return proxyFactory.getProxy();
}
return result;
}

}
70 changes: 70 additions & 0 deletions backend/src/main/java/com/votogether/global/log/aop/LogAop.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.votogether.global.log.aop;

import java.util.Objects;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;

@Slf4j
@RequiredArgsConstructor
@Aspect
@Component
public class LogAop {

private static final String PROXY = "Proxy";

private final Logger logger;

@Pointcut("@within(org.springframework.web.bind.annotation.RestController)")
public void restControllerAnnotatedClass() {
}

@Pointcut("@within(org.springframework.stereotype.Service)")
public void serviceAnnotatedClass() {
}

@Pointcut("execution(* com.votogether.domain..*Repository+.*(..))")
public void repositoryClass() {
}

@Around("restControllerAnnotatedClass() || serviceAnnotatedClass() || repositoryClass()")
public Object doLog(final ProceedingJoinPoint joinPoint) throws Throwable {
if (!isRequestScope()) {
return joinPoint.proceed();
}
final String className = getClassSimpleName(joinPoint);
final String methodName = getMethodName(joinPoint);
logger.methodCall(className, methodName);
try {
final Object result = joinPoint.proceed();
logger.methodReturn(className, methodName);
return result;
} catch (Throwable e) {
logger.throwException(className, methodName, e);
throw e;
}
}

private boolean isRequestScope() {
return Objects.nonNull(RequestContextHolder.getRequestAttributes());
}

private String getClassSimpleName(final ProceedingJoinPoint joinPoint) {
final Class<?> clazz = joinPoint.getTarget().getClass();
String className = clazz.getSimpleName();
if (className.contains(PROXY)) {
className = clazz.getInterfaces()[0].getSimpleName();
}
return className;
}

private String getMethodName(final ProceedingJoinPoint joinPoint) {
return joinPoint.getSignature().getName();
}

}
56 changes: 56 additions & 0 deletions backend/src/main/java/com/votogether/global/log/aop/Logger.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.votogether.global.log.aop;

import com.votogether.global.log.context.LogContext;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

@Slf4j
@RequiredArgsConstructor
@Component
public class Logger {

private static final String DOT = ".";
private static final String PARENTHESES = "()";
private static final String CALL_PREFIX = "--->";
private static final String RETURN_PREFIX = "<---";
private static final String EX_PREFIX = "<X--";

private final LogContext logContext;

public void methodCall(final String className, final String methodName) {
logContext.increaseMethodCall();
log.info("[{}] {}",
logContext.getLogId(),
formattedClassAndMethod(logContext.depthPrefix(CALL_PREFIX), className, methodName)
);
}

public void methodReturn(final String className, final String methodName) {
final long currentTimeMillis = System.currentTimeMillis();
log.info("[{}] {} - [execution time = {}ms] [total time = {}ms]",
logContext.getLogId(),
formattedClassAndMethod(logContext.depthPrefix(RETURN_PREFIX), className, methodName),
logContext.executionTime(currentTimeMillis),
logContext.totalTakenTime(currentTimeMillis)
);
logContext.decreaseMethodCall();
}

public void throwException(final String className, final String methodName, final Throwable exception) {
final long currentTimeMillis = System.currentTimeMillis();
log.warn("[{}] {} - [execution time = {}ms] [total time = {}ms] [throws {}]",
logContext.getLogId(),
formattedClassAndMethod(logContext.depthPrefix(EX_PREFIX), className, methodName),
logContext.executionTime(currentTimeMillis),
logContext.totalTakenTime(currentTimeMillis),
exception.getClass().getSimpleName()
);
logContext.decreaseMethodCall();
}

private String formattedClassAndMethod(final String prefix, final String className, final String methodName) {
return prefix + className + DOT + methodName + PARENTHESES;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.votogether.global.log.aop;

import com.votogether.global.log.context.QueryCount;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.web.context.request.RequestContextHolder;

@RequiredArgsConstructor
public class PreparedStatementMethodInterceptor implements MethodInterceptor {

private static final Set<String> QUERY_METHODS = new HashSet<>(List.of("execute", "executeQuery", "executeUpdate"));

private final QueryCount queryCount;

@Override
public Object invoke(final MethodInvocation invocation) throws Throwable {
if (isExecuteQuery(invocation.getMethod()) && isRequestScope()) {
queryCount.increase();
}
return invocation.proceed();
}

private boolean isExecuteQuery(final Method method) {
final String methodName = method.getName();
return QUERY_METHODS.contains(methodName);
}

private boolean isRequestScope() {
return Objects.nonNull(RequestContextHolder.getRequestAttributes());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.votogether.global.log.aop;

import com.votogether.global.log.context.QueryCount;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class QueryCountAop {

private final QueryCount queryCount;

public QueryCountAop(final QueryCount queryCount) {
this.queryCount = queryCount;
}

@Around("execution(* javax.sql.DataSource.getConnection())")
public Object getConnection(final ProceedingJoinPoint joinPoint) throws Throwable {
final Object connection = joinPoint.proceed();
final ProxyFactory proxyFactory = new ProxyFactory(connection);
proxyFactory.addAdvice(new ConnectionMethodInterceptor(queryCount));
return proxyFactory.getProxy();
}

}
Loading
Loading