단의 개발 블로그
서버 설정 본문
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
'Side Project > Modak' 카테고리의 다른 글
| Modak (0) | 2024.11.06 |
|---|