스프링 부트로 프로젝트를 하면서 API를 만들어 포스트맨 확인 후 넘겨드렸는데 CORS 오류가 발생했다는 얘기를 전해들었다. 해당 오류는 SOP 정책을 위반했을 때 발생하는 오류이며 SOP 정책은 다음과 같다.

 

SOP(Same Origin Policy) 정책

같은 호스트, 같은 포트, 같은 프로토콜에서만 요청을 주고받을 수 있는 보안 정책

스프링 부트에서 아무런 설정을 적용하지 않으면 위 SOP 정책을 그대로 따르게 된다고 한다.

 

그럼 SOP 정책을 어떻게 변경할 수 있을까?

해답은 CORS(Cross-Origin Resource Sharing)을 이용하면 된다고 한다.

CORS는 같은 호스트, 같은 포트, 같은 프로토콜이 아니더라도 요청을 주고 받을 수 있도록 해준다.

 

CORS를 적용하는 법

1. CORS 적용이 필요한 컨트롤러 위에 @CrossOrigin("*") 애노테이션을 추가한다.

2. WebMvcConfigurer 인터페이스를 상속받아서 설정한다.(스프링 시큐리티 사용 안할 시)

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOrigins("http://localhost:3000");
    }

3. 스프링 시큐리티 설정 관련 config 파일에 아래 내용 추가 (스프링 시큐리티 사용할 시)

@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
    httpSecurity.cors().configurationSource(request -> {
        var cors = new CorsConfiguration();
        //허용할 주소
        cors.setAllowedOrigins(List.of("http://localhost:3000")); 
        //허용할 http 메소드
        cors.setAllowedMethods(List.of("GET","POST", "PUT", "DELETE", "OPTIONS"));
        //허용할 header
        cors.setAllowedHeaders(List.of("*"));
        return cors;
    });

 

필자는 스프링 시큐리티를 사용하고 있어서 3번으로 해결했다.

 

참고 사이트

https://velog.io/@minchae75/Spring-boot-CORS-%EC%A0%81%EC%9A%A9%ED%95%98%EA%B8%B0

 

 

 

Jwt 파싱할 때 발생한 오류다.

구글링해서 알아보니 parsing한 secert key 값이 원래 로컬에 저장된 secret key값과 달라서 발생하는 오류라고 한다.

 

먼저 오류 발생했던 초기 코드는 아래와 같다.

Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody().getSubject();

 

이후 구글링을 통해 다수의 사람들이 해결하던 .getBytes()를 추가하는 방식으로 수정해봤으나 오류가 해결되지 않았다.

.getBytes()를 통해 바이트 형식으로 키 값을 다뤄 key 값이 토큰에 들어가거나 파싱되는 순간에 변경이 되는 걸 방지한다는 디버깅 방식이었다.

Jwts.parser().setSigningKey(secretKey.getBytes()).parseClaimsJws(token).getBody().getSubject();

 

위 방식으로도 해결이 안된 이후로 몇 시간 동안 이 코드만 디버깅을 시도했었다.

구글링해도 자료가 많지 않아 난항을 겪고 있던 와중에 한 중국인이 올린 코드를 보게 됐고 그 분 방식처럼 아예 parsing하는 함수를 따로 분리하면서 문법도 바꿔 작성하니 오류가 해결됐다...

 

참고한 글은 아래 참고 사이트에 링크 남겼고 내가 작성한 오류 해결 코드는 아래와 같다.

 /**
     * Access 토큰 파싱
     * @param jwt
     * @return
     */
    public Claims parseJwt(String jwt) {
        Claims claims = Jwts.parser()
                .setSigningKey(secretKey)
                .parseClaimsJws(jwt)
                .getBody();

        return claims;
    }
    
    Claims claims = parseJwt(token);
    String s=claims.getSubject();

 

 

 

참고 사이트 

https://github.com/jwtk/jjwt/issues/177

EC2서버에서 스프링 부트 프로젝트를 돌리는데 계속 빌드를 하다 멈추는 일이 발생했다.

디스크 메모리 용량을 확인해보니 아직 한참 남아서 cpu 사용량을 확인해보니 RAM이 딸려서 발생하는 문제인 것 같았다.

알아보니 t2.micro의 램이 1GB밖에 안돼서 현재 서버에서 스프링 부트 빌드만 해도 터지는 것 같았다.

이전 스프링 부트 프로젝트를 할 때는 램이 모잘라서 터지진 않았는데

이번 스프링 부트 프로젝트는 사용하는 기술들이 많아 몸집이 상당히 커져서 계속 터지는 것 같다.

 

다행히 리눅스에서는 SWAP 메모리를 지정할 수 있어서 RAM이 부족할 때 HDD의 일정 공간을 마치 RAM처럼 사용할 수 있었다. 속도가 좀 느리긴 하지만 그래도 돌아가는게 어디인가

(실무에서는 좋은 해결 방법이 아닌 것 같은데 애초에 실무에서 쓰이는 서버라면 RAM이 1GB이지 않을 것 같다ㅎㅎ)

 

램이 2GB이하일 땐 보통 RAM 용량의 2배를 SWAP 메모리로 잡는데 ec2는 1GB로 SWAP 메모리는 2GB로 잡아서 설정할 예정이다. 이때 주의해야되는게 SWAP 메모리의 공간이 최소 32MB는 돼야한다고 한다.

 

free 명령어를 통해 메모리(램)과 SWAP 메모리를 확인해 볼 수 있다.

현재 이렇게 SWAP 메모리 할당이 안돼있는 상태이다.

이제 SWAP 메모리를 할당해보자.

sudo dd if=/dev/zero of=/swapfile bs=128M count=16   //2GB정도 공간 할당
sudo chmod 600 /swapfile    //swap 파일 권한 업데이트
sudo mkswap /swapfile    //linux swap 영역 설정
sudo swapon /swapfile    //swap space에 swap file 추가
sudo swapon -s    //위 명령어들 성공 확인

다음으로 /etc/fstab 파일을 편집하여 부팅 시 swap 파일을 활성화 해줘야 한다.

$ sudo vi /etc/fstab

제일 마지막에 아래 내용을 추가해준다.

/swapfile swap swap defaults 0 0

 

다시 free 명령어로 확인해보자.

 

참고사이트

https://aws.amazon.com/ko/premiumsupport/knowledge-center/ec2-memory-swap-file/
https://sundries-in-myidea.tistory.com/102

https://incomeplus.tistory.com/284?category=868671

ec2 서버에 프로젝트 코드를 정상적으로 옮기는 것 까지 성공했다면

이젠 해당 코드들을 빌드하고 무중단 서비스를 실행하고 중단해보자.

 

스프링 부트 프로젝트가 있는 이전 폴더로 이동합니다.

 

맨 처음 빌드시 permission denied가 뜨니 먼저 권한 설정부터 해줍니다.

chmod -R 777 프로젝트있는폴더명/

이후 프로젝트 폴더로 이동해줍니다.

 

스프링 부트 프로젝트 빌드 

./gradlew clean build

스프링 부트 무중단 서비스 시작

각자 맞는 실행파일 이름은 build/libs 폴더에 들어가서 확인할 수 있다.

nohup java -jar build/libs/{각자맞는이름}-0.0.1-SNAPSHOT.jar &

스프링 부트 무중단 서비스 종료

ps -ef | grep {각자맞는이름}-0.0.1-SNAPSHOT.jar
=>종료시킬 스프링 부트 pid 확인
sudo kill -9 확인한pid
=>무중단 스프링 부트 중지

Amazon RDS(Relational Database Service)란?

분산 관계형 데이터베이스 서버 인스턴스로서 관계형 데이터베이스의 설치, 관리, 업데이트를 알아서 해준다.

따라서, RDS를 통해 관계형 데이터베이스를 손쉽게 사용, 관리할 수 있고 스토리지 확장 같은 기능들을 활용할 수 있다.

프리티어 계정이라면 aws에서 직접 운용하는 Aurora는 사용할 수 없고 Microsoft SQL, Oracle, MySQL, Aurora, Maria DB 중에서 하나를 택해서 임대 받으면 된다.


1. RDS 데이터베이스 생성받기

검색창에서 RDS 검색 후 데이터베이스 클릭

필자는 이미 임대 받은 RDS가 있어서 저렇게 뜨지만 원래는 목록이 비어있다.

데이터베이스 생성 클릭

원하는 데이터베이스 종류 선택

Aurora는 프리티어 무료 계정으로 사용할 수 없다.

버전을 확인할 수도 있고 변경을 원할 시 변경도 가능하다.

무료로 사용할 예정이므로 프리 티어를 선택한다.

데이터베이스 이름, 마스터 이름, 비밀번호를 설정한다.

이후 워크벤치, 데이터그립등을 활용하여 데이터베이스에 접근할 때 필요한 정보이므로 신중하게 설정해야한다.

과금을 막기 위해 범용 SSD 스토리지 유형 선택, 자동 조정 활성화 체크를 해제한다.

외부에서도 데이터베이스에 접근할 수 있도록 퍼블릭 엑세스를 예로 바꿔준다.

aws의 모든 인스턴스들은 내부/외부 상호작용을 위해 vpc 설정을하는데 필자는 기본 vpc를 선택했다.

외부에서 DB에 접근할 때 인증 옵션을 선택해야한다.

현업이 아니고 사이드 프로젝트용으로 사용할 것이기 때문에 간단하게 암호 인증으로 선택했다.

이후 데이터 베이스 생성 버튼 클릭하면 데이터베이스 인스턴스가 생성되며 RDS 데이터베이스 탭에서 확인가능하다.

 

2. RDS 파라미터 그룹 변경

Time Zone, Character Set, Max Connection 파라미터 값을 변경해줘야한다.

RDS에 들어가서 파라미터 탭->파라미터 그룹 생성 클릭

그룹 이름과 설명을 적어준 뒤 생성 버튼 클릭

 

아래 사진들의 왼쪽 이름을 검색 후 오른쪽과 같이 설정 변경

time_zone

charcter_set

한글과 이모지 지원

max_connection

 

 

최종 변경 사항 저장 클릭

다시 RDS 데이터베이스 탭으로 이동하여 해당 데이터베이스를 클릭한 후 수정 클릭

추가 구성에서 방금 생성한 파라미터 그룹으로 설정 후 초기 설정이므로 즉시 적용 클릭

예시로 만들던 파라미터 그룹과 이름이 다른건 이전 파라미터 그룹을 설정한 것으로 신경안쓰셔도 됩니다.

 

3. 보안그룹 설정

RDS 데이터베이스 탭에서 원하는 인스턴스의 이름 클릭

연결&보안에서 vpc 보안 그룹 밑 이름 클릭

 

인바운드 규칙 탭에서 인바운드 규칙 편집 클릭

외부에서 접속하려는 ip 추가(로컬에서 접근시 네이버에서 내 ip 주소치면 ip주소 확인 가능)

이때  ip주소 뒤에 /32를 넣어줘야된다.

ec2 보안 그룹 ID를 복사해서 넣어줘도 된다. 

다 추가했으면 규칙 저장

 

이제 초기 설정은 다 끝났다. 

 

이후 원하는 프로그램을 써서 데이터베이스에 접근하면 된다.

필자는 주로 데이터그립을 사용하며 데이터그립을 활용해 연결하는 방법은 아래 게시물을 참고하면 된다.

https://cofls6581.tistory.com/60

 

Datagrip으로 MySQL 외부에서 접속하기

Datagrip이란? 외부 접속위한 DB 개발과 관리를 용이하게 해주는 GUI 개발 툴 Datagrip 다운 www.jetbrains.com/ko-kr/datagrip/download/#section=windows 학생 인증 후 무료 라이센스 pack 다운로드 new project..

cofls6581.tistory.com

 

참고 사이트

https://overcome-the-limits.tistory.com/294

https://bcp0109.tistory.com/357?category=1073811

https://8iggy.tistory.com/71

https://brunch.co.kr/@topasvga/696

스프링 부트에서 Redis 서버를 연결해서 작동하는 로직이 있는데 코드를 작성한 후 관련 api를 실행시켜보니 ERR Client sent AUTH, but no password is set라는 에러로그가 출력됐다.

Redis 서버에 비밀번호 설정을 안해줬거나 설정해줬는데 yml과 같은 Redis 서버 연결관련 설정 파일에 비밀번호를 제대로 적지 않아줘서 생기는 오류이다.

분명 초반에 깔 때 비밀번호를 설정해주고 확인까지 했었는데 비밀번호가 날라가있었다. 
+원인을 알았다. 집에서 사용하는 IP가 고정적인 IP가 아니여서 가끔씩 IP가 바뀌는데 IP가 바뀌면 redis 설정을 다시 해줘야하는 것 같다.

 

참고용 내 redis 서버 yml 설정 파일 코드는 아래와 같다.

spring:
  redis:
    host: localhost
    port: 6379
    password: '설정한비밀번호'
    lettuce:
      pool:
        max-active: 10
        max-idle: 10
        min-idle: 2

 

비밀번호를 설정해주지 않았거나 기억이 안난다면 아래를 따라오면 된다.

 

비밀번호 확인

 

redis-cli 실행

config get requirepass

 

"requirepass"

""

뜨면 비밀번호 설정이 안된 상태로 설정해줘야 한다.

 

비밀번호 설정법

 

redis-cli 실행

config set requirepass 비밀번호

이후 아래 코드를 치면

auth 설정한비밀번호 

config get requirepass

"requirepass"

"설정한비밀번호"

정상적으로 비밀번호가 변경됐음을 확인할 수 있다.

 

 

confing폴더에 securityConfig 생성

securityConfig에 아래와 같은 코드로 passwordEncoder를 Bean으로 추가

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class SecurityConfig {

    @Bean
    public PasswordEncoder passwordEncoder(){
        return new BCryptPasswordEncoder();
    }
}

참고사이트

https://stackoverflow.com/questions/60848619/consider-defining-a-bean-of-type-org-springframework-security-crypto-bcrypt-bcr?rq=1

스프링 부트 실행 시 cannot access MimeMessage와 같은 오류가 떴다.

구글링 후 build.gradle dependices에 다음과 같은 설정 추가

implementation 'org.springframework.boot:spring-boot-starter-mail'

 

참고 사이트 

https://youtrack.jetbrains.com/issue/IDEA-276659

스프링 부트 2.6 이상에서 Querydsl 5.0을 사용하려고 할 때 이전의  build.gradle에서의 Querydsl 설정법을 사용하면 발생하는 오류이다.

 

아래 코드를 build.gradle에 추가해주면 정상적으로 스프링 부트가 실행된다.

 

buildscript {
   ext {
      queryDslVersion = "5.0.0"
   }
}

plugins {
   //querydsl 추가
   id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"
}

dependencies {
   //querydsl 추가
   implementation "com.querydsl:querydsl-jpa:${queryDslVersion}"
   implementation "com.querydsl:querydsl-apt:${queryDslVersion}"
}

def querydslDir = "$buildDir/generated/querydsl"

querydsl {
   jpa = true
   querydslSourcesDir = querydslDir
}
sourceSets {
   main.java.srcDir querydslDir
}
compileQuerydsl{
   options.annotationProcessorPath = configurations.querydsl
}
configurations {
   compileOnly {
      extendsFrom annotationProcessor
   }
   querydsl.extendsFrom compileClasspath
}

Querydsl 사용하는 이유

data jpa의 @Query로는 다양한 조회 기능 사용에 한계가 존재

->해결 하기 위해 정적 타입을 지원하는 가장 유명한 조회 프레임워크 Querydsl 사용

동적 쿼리 작성이 편리

1. build.gradle 수정

 

plugin 블럭에 추가

id "com.ewerk.gradle.plugins.querydsl" version "1.0.10"

dependencies 블럭에 추가

 implementation 'com.querydsl:querydsl-jpa'

querydsl 설정 부분 블럭으로 추가

//querydsl설정

def querydslDir = "$buildDir/generated/querydsl"

querydsl {
    jpa = true
    querydslSourcesDir = querydslDir
}

sourceSets {
    main.java.srcDir querydslDir
}

configurations {
    querydsl.extendsFrom compileClasspath
}

compileQuerydsl {
    options.annotationProcessorPath = configurations.querydsl
}

2. configuration 추가

이 설정을 통해 jpaQueryFactory를 프로젝트 어느 곳에서나 주입받아 사용 가능

@Configuration
public class QuerydslConfiguration {
    @PersistenceContext
    private EntityManager entityManager;

    @Bean
    public JPAQueryFactory jpaQueryFactory(){
        return new JPAQueryFactory(entityManager);
    }

}

 

참고 사이트

https://hello-gg.tistory.com/62

https://jojoldu.tistory.com/372

 

 

 

원래 validate_password_policy 변수의 값 등을 변경하는 방식으로 비밀번호 정책을 바꿀 수 있었다.

my.cnf 파일에 들어가서 해당 변수를 바꾸려고 하거나

mySQL을 실행한 후 mysql> SET GLOBAL validate_password_policy=LOW;을 실행해 바꾸려고해도 

Unknown system variable 'validate_password_policy'라는 오류가 떴다.

 

1. SHOW VARIABLES LIKE 'validate_password%'; 로 시스템 변수명 확인

mySQL 버전이 업그레이드 되면서 새로운 방법으로 비밀번호 정책을 변경해야하는 게 아닌가 생각이 들던 중 

SHOW VARIABLES LIKE 'validate_password%';

위 코드를 통해 비밀번호 유효성 검사 시스템 변수들을 확인할 수 있다는 사실을 알았다. 

 

해당 명령어를 통해 비밀번호 정책 관련 시스템 변수들을 확인해보니 validate_password_policy에서 validate_password.policy로 변수명이 바껴있었고 validate_password_length 변수도 validate_password.length로 변수명이 바껴있었다. 이밖에도 관련 다른 변수명들을 해당 명령어를 통해 확인할 수 있다.

 

2. 바뀐 변수명에 맞춰 비밀번호 정책 바꿔주기

SET GLOBAL validate_password.policy=LOW;

 

SET GLOBAL validate_password.length=4;

 

3. 확인해보기

SHOW VARIABLES LIKE 'validate_password%'; 

정상적으로 변경됐음을 확인할 수 있다.

 

참고 사이트

https://scbyun.com/1137

AOP (Aspect-Orientes Programming)란?

관점 지향 프로그래밍

AOP는 OOP(객체지향프로그래밍)와 다른 게 아니라 OOP를 보완해주는 것

OOP에서 핵심 비즈니스 로직과 독립적으로 분리하기 힘든 부가 기능들을 AOP의 Aspect을 활용해 모듈화해준다.

핵심 비즈니스 로직을 담고 있지는 않지만 어플리케이션에 부가됨으로써 의미를 갖는 특별한 모듈!

 

OOP: 비지니스 로직의 모듈화

AOP: 인프라 혹은 부가 기능의 모듈화

  ex) 트랜잭션, 로깅, 등

 

Aspect을 이용해 모듈화 왜 필요? 그냥 쓰면 안되나?

흩어진 관심사를 횡단 기능에 관심을 두어 하나로 합칠 수 있다.

이 과정에서 중복을 줄여 코드의 효율성도 증가하고 유지보수도 더 쉬움

ex)

매 클래스마다 이용자가 사용하는 부가 기능(ex, 실행시간 측정 등)이 있다고 생각해보자.

관련된 코드들을 매 클래스마다 다 넣으면 핵심 로직과 관련된 코드도 아닌데 작성도 힘들고 수정이 필요할 시 유지보수도 힘들다.

그렇다고 없앨 수는 없다.

 

AOP 용어

Aspect

부가 기능 모듈

advice와 point cut을 내재

Target

aspect에 적용되는 대상

Advice

어떤 기능을 부가적으로 넣을지

Join Point

advice 적용될 위치, 끼어들 수 있는 지점, 메서드 진입 지점, 생성자 호출 지점 등 다양한 시점에 적용 가능

Point Cut

어디에 적용해야 하는지에 대한 정보 ex 메서드 정보 등

Proxy

타겟을 감싸서 타겟의 요청을 대신 받아주는 랩핑 오브젝트

Weaving

지정된 객체에 aspect를 적용해서 새로운 proxy 객체를 생성하는 과정

 

AOP 구현체 종류

1. AspectJ

2. Spring AOP

 

AOP 적용 방법

1. 컴파일시 -> AspectJ

자바 파일을 클래스 파일로 만들 때 바이트 코드를 조작하여 AOP 적용

2. 클래스 로드시 -> AspectJ

컴파일은 원래 클래스 그대로, 클래스를 로딩하는 시점에 AOP 적용

3. 런타임시(프록시 패턴 사용) -> Spring AOP

Spring AOP 대상인 클래스의 빈이 만들어질 때 Spring AOP가 프록시 기능이 추가된 클래스를 자동으로 만든다.

이후 원본이 아닌 프록시 기능이 추가된 클래스를 빈으로 등록

Spring AOP란?

스프링은 프록시 패턴을 이용해 AOP를 구현

스프링 빈에만 AOP 적용 가능

모든 AOP 기능을 지원하진 않고 스프링 IoC와 연동하여 중복코드, 프록시 클래스 작성 번거로움과 같은 흔한 문제들에 대한 해결책을 지원하는 게 목적

 

Spring AOP 의존성 추가

 

maven

<dependency>

  <groupId>org.springframework.boot</groupId>

  <artifactId>spring-boot-starter-aop</artifactId>

</dependency>

gradle

implementation 'org.springframework.boot:spring-boot-starter-aop'

 

참고 사이트

https://www.youtube.com/watch?v=Hm0w_9ngDpM 

https://jojoldu.tistory.com/71

https://devlog-wjdrbs96.tistory.com/398

https://atoz-develop.tistory.com/entry/Spring-%EC%8A%A4%ED%94%84%EB%A7%81-AOP-%EA%B0%9C%EB%85%90-%EC%9D%B4%ED%95%B4-%EB%B0%8F-%EC%A0%81%EC%9A%A9-%EB%B0%A9%EB%B2%95

https://engkimbs.tistory.com/746

https://tecoble.techcourse.co.kr/post/2021-06-25-aop-transaction/

 

 

 

 

 

 

 

+ Recent posts