단의 개발 블로그

서버 설정 본문

Side Project/Modak

서버 설정

danso 2024. 11. 6. 14:50

FrontEnd(Nextjs)

프론트는 작업중이다....

BackEnd (SpringBoot)

서버구조

도메인 별로 패키지를 나눠서 그 안에서 controller, service, dto, entity를 나눌까 생각 했는데, 간단하게 만들 프로젝트고 실제 운영까지는 하지 않을 생각이기 때문에 기능별로만 나눴다. 여기선 패키지로 설명한다.

  • aop : 유저가 백엔드로 요청한 정보를 서버로그에 기록한다. 나중에 어떤 장애가 발생할 경우 어떤 유저가 어떤 데이터로 요청했는지 추적하기 위해서 사용했다. 또 만약 나중에 부하가 많이 생기는 비즈니스 로직단에서 문제가 발생하면 실행 시간도 추가할 예정이다.
  • config : 서버에 관한 전반적인 자바 빈으로 설정한 파일이 모아져 있는 곳이다. 시큐리티, 스웨거 등을 여기서 설정한다.
  • controller : 사용자가 요청한 데이터를 받는 곳이다. 각 요청에 맞게 알맞은 비즈니스 메소드를 호출해서 처리하고, 해당 데이터를 다시 사용자에게 전달한다.
  • domain : 프로젝트 개발하는 대상 영역, JPA 엔티티를 여기다 생성한다.
  • dto : 여기저기서 주고 받는 데이터 객체가 있는 곳이다.
  • filter : 스프링 영역 전 앞단에서 정제하는 역할을 하는 곳이다. 여기서 인증/인가 jwt 등 작업을 한다.
  • handler : 메소드 전후처리 하는 곳이다.
  • repository : JPA 인터페이스가 존재하는 곳이다.
  • service : 실제 비즈니스 로직이 처리되고 DB에 내용을 반영하는 곳이다. (=JPA 구현체)
  • util : 암호화, 파일다운로드 등 여러곳에서 공통으로 사용하는 메소드가 존재하는 곳이다.

 

의존성

일단 앞단에서 생각한 기능을 구현하기 위해 필요한 의존성들을 추가했다. (일단 이거만으로도 웬만한건 구현은 될테니까..)

    implementation 'org.springframework.boot:spring-boot-starter'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'org.springframework.boot:spring-boot-starter-aop'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-validation'
    implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

    implementation 'com.auth0:java-jwt:4.2.1'
    implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'
    implementation 'io.jsonwebtoken:jjwt:0.9.1'
    implementation 'org.projectlombok:lombok'
    implementation 'com.googlecode.json-simple:json-simple:1.1'
    implementation 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5'

    runtimeOnly 'org.mariadb.jdbc:mariadb-java-client:2.7.4'

    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.security:spring-security-test'
    testRuntimeOnly 'org.junit.platform:junit-platform-launcher'

    annotationProcessor 'org.projectlombok:lombok'

 

작업내용

스웨거

@OpenAPIDefinition(
        info = @Info(title = "Modak API",
                description = "미완",
                version = "v1"))
@RequiredArgsConstructor
@Configuration
public class SwaggerConfig {

    @Bean
    public GroupedOpenApi SampleOpenApi() {
        String[] paths = {"/**"};

        return GroupedOpenApi.builder()
                .group("Modak v1")
                .pathsToMatch(paths)
                .build();
    }
}

시큐리티

@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
public class SecurityConfig {

  private final LoginService loginService;
  private final JwtService jwtService;
  private final UserRepository userRepository;
  private final ObjectMapper objectMapper;
  private final CustomOAuth2UserService customOAuth2UserService;

  @Bean
  public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .csrf(AbstractHttpConfigurer::disable)
        .formLogin(AbstractHttpConfigurer::disable)
        .httpBasic(AbstractHttpConfigurer::disable)
        .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
        .authorizeHttpRequests((authorizeRequests) ->
            authorizeRequests
                .requestMatchers("/", "/home","/user/join", "/WEB-INF/**", "/js/**", "/css/**", "/image/**", "/favicon.ico").permitAll()
                .requestMatchers("/api/v1/user/**").hasRole(Role.USER.name())
                .anyRequest().authenticated())
        .oauth2Login(oauth -> {
          oauth.successHandler(loginSuccessHandler());
          oauth.failureHandler( loginFailureHandler());
          oauth.userInfoEndpoint(userInfoEndpoint -> userInfoEndpoint.userService(customOAuth2UserService));
        })
        .formLogin(login -> login.loginPage("/auth/loginForm")
            .loginProcessingUrl("/auth/loginProc")
            .defaultSuccessUrl("/")
        );
    http.addFilterAfter(customJsonUsernamePasswordAuthenticationFilter(), LogoutFilter.class);
    http.addFilterBefore(jwtAuthenticationProcessingFilter(), CustomJsonUsernamePasswordAuthenticationFilter.class);
    return http.build();
  }
  @Bean
  public BCryptPasswordEncoder encode(){
    return new BCryptPasswordEncoder();
  }

  @Bean
  public AuthenticationManager authenticationManager() {
    DaoAuthenticationProvider provider = new DaoAuthenticationProvider();
    provider.setPasswordEncoder(encode());
    provider.setUserDetailsService(loginService);
    return new ProviderManager(provider);
  }

  @Bean
  public LoginSuccessHandler loginSuccessHandler() {
    return new LoginSuccessHandler(jwtService, userRepository);
  }

  @Bean
  public LoginFailureHandler loginFailureHandler() {
    return new LoginFailureHandler();
  }

  @Bean
  public CustomJsonUsernamePasswordAuthenticationFilter customJsonUsernamePasswordAuthenticationFilter() {
    CustomJsonUsernamePasswordAuthenticationFilter customJsonUsernamePasswordLoginFilter
        = new CustomJsonUsernamePasswordAuthenticationFilter(objectMapper);
    customJsonUsernamePasswordLoginFilter.setAuthenticationManager(authenticationManager());
    customJsonUsernamePasswordLoginFilter.setAuthenticationSuccessHandler(loginSuccessHandler());
    customJsonUsernamePasswordLoginFilter.setAuthenticationFailureHandler(loginFailureHandler());
    return customJsonUsernamePasswordLoginFilter;
  }

  @Bean
  public JwtAuthenticationProcessingFilter jwtAuthenticationProcessingFilter() {
    JwtAuthenticationProcessingFilter jwtAuthenticationFilter = new JwtAuthenticationProcessingFilter(jwtService, userRepository, encode());
    return jwtAuthenticationFilter;
  }
}

AOP

@Aspect
@Component
public class LogAspect {
  Logger log = LoggerFactory.getLogger(this.getClass().getName());

  @Pointcut("execution(* com.modak.backend.contoller.*Controller.*(..))")
  public void controller() {}

  @Around("controller()")
  public Object writeLogControllerRequest(ProceedingJoinPoint joinPoint) throws Throwable {
    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();

    String controllerName = joinPoint.getSignature().getDeclaringType().getName();
    String methodName = joinPoint.getSignature().getName();
    Map<String, Object> params = new HashMap<>();
    try {
      String decodedURI = URLDecoder.decode(request.getRequestURI(), "UTF-8");
      params.put("controller", controllerName);
      params.put("method", methodName);
      params.put("params", getParams(request));
      params.put("log_time", System.currentTimeMillis());
      params.put("request_uri", decodedURI);
      params.put("http_method", request.getMethod());
    } catch (Exception e) {
      log.error("LoggerAspect error", e.getMessage());
    }
    log.info("[{}] [{}] [{}.{}] [{}]",
        params.get("http_method"),
        params.get("request_uri"),
        params.get("controller"),params.get("method"),
        params.get("params"));
    return joinPoint.proceed();
  }

  private static JSONObject getParams(HttpServletRequest request) {
    JSONObject jsonObject = new JSONObject();
    Enumeration<String> params = request.getParameterNames();
    while (params.hasMoreElements()) {
      String param = params.nextElement();
      String replaceParam = param.replaceAll("\\.", "-");
      jsonObject.put(replaceParam, request.getParameter(param));
    }
    return jsonObject;
  }
}

Rest 요청할 때 예외 발생할 경우 공통으로 처리할 Global Exception

@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

  @ExceptionHandler(Exception.class)
  protected ResponseEntity<ErrorResponse> handleException(Exception ex, WebRequest request) {
    log.error(ex.getMessage(), ex);
    ErrorResponse response = ErrorResponse.builder(ex, HttpStatus.INTERNAL_SERVER_ERROR, ex.getMessage()).build();
    return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR);
  }
}

프론트 통신 응답 공통 모듈

public record ApiResponse<T> (
    boolean success,
    @Nullable int code,
    @Nullable T data
) {

  public static <T> ApiResponse<T> ok(@Nullable int code, @Nullable final T data) {
    return new ApiResponse<>(true, code, data);
  }


}

yaml 설정은 비공개......... 무튼 하나의 yaml로 사용하면 너무 길어질 거 같아서 각 기능 별 필요한 yaml로 나눠서 사용할 예정이다.

 

 

Server  (Infra)

일단 생각 나는거만 먼저 설정했다..

  • Ubuntu
  • Nginx
  • Mariadb
  • Redis
  • FD
  • NodeJS
  • Jenkins

 

Git: https://github.com/dasolit/side-modak

'Side Project > Modak' 카테고리의 다른 글

Modak  (0) 2024.11.06