Home Proxy
Post
Cancel

Proxy

프록시란?

프록시(Proxy)란 ‘대리’라는 의미로, 네트워크 기술에서는 프로토콜에 있어서 대리 응답 등에서 친숙한 개념이다.

보안 분야에서는 주로 보안상의 이유로 직접 통신할 수 없는 두 점 사이에서 통신을 할 경우

그 상이에 있어서 중계기로서 대리로 통신을 수행하는 기능을 가리켜 ‘프록시’,

그 중계 기능을 하는 것을 프록시 서버라고 부른다.




프록시 서버

서버와 클라이언트 사이에서 클라이언트가 자신을 통해

다른 네트워크 서비스에 간접적으로 접속할 수 있게 해주는 컴퓨터 시스템이나 응용 프로그램을 말한다.




프록시 서버에서의 캐싱

캐시 안에 정보를 담아두고, 캐시 안에 있는 정보를 요구하는 요청에 대해 다시 저 멀리 있는 원격 서버에 요청하지 않고

캐시 안에 있는 데이터를 활용하는 것을 말한다.


이를 통해 캐시 안에 있는 정보를 요구하는 요청에 대해서는 원격 서버에 접속하여 데이터를 가져올 필요가 없게 됨으로써

전송 시간을 절약을 할 수 있다.


또, 불필요하게 외부와 연결하지 않기 때문에 트래픽을 줄일 수 있다(→ 네트워크 병목 현상 방지 효과)는 장점이 있다.



프록시 서버란 서버 앞단에 둬서 캐싱, 로깅, 데이터 분석을 서버보다 먼저 하는 서버를 말한다.

이를 통해 포트 번호를 바꿧 사용자가 실제 서버의 포트에 접근하지 못하게 할 수 있으며

공격자의 DDOS 공격을 차단하거나 CDN을 프록시 서버로 달아서 캐싱 처리를 용이하게 할 수 있다.




프록시 서버 종류

서버의 위치에 따라 분류하면 크게 두 가지로 나눌 수 있다.



Forward 프록시

image

프록시 서버를 ‘클라이언트 호스트들과 접근하고자 하는 원격 리소스의 사이’에 위치시킨다.
(인터넷보다 프록시 서버를 먼저 호출)

일반적으로 프록시라고 하면 Forward Proxy 라고 한다.


클라이언트가 서버에게 요청할때 직접 서버에 접근하지 않고 Forward 프록시 서버에게 요청하면

Forward 프록시 서버가 해당 서버에게 접근하여 요청을 전달하고 결과를 클라이언트에게 전달해주는 방식이다.


Forward 프록시는 캐시 기능을 사용하기 때문에 캐시 서버로 활용하여 성능을 향상시킬 수 있다.

자주 사용되는 자원을 캐시에 저장해놓기 때문에 해당 자원 요청이 온다면 서버에게 갈 필요 없이 프록시 서버 자체에서 처리가 가능하다.

클라이언트가 서버를 직접 접근하지 못하기 때문에 접근 가능한 사이트를 제한할 수 있으므로 보안을 향상시킬 수 있다.

즉, 클라이언트는 서버를 알지만, 서버는 프록시를 통해 요청이 오기 때문에 클라이언트를 알지 못한다.

서버가 응답받은 IP는 Forward 프록시 서버의 IP이기 때문에 클라이언트가 누군지 알 수 없다.



Reverse 프록시

image

클라이언트가 서버를 호출할 때 Reverse 프록시를 호출하게 되고
프록시 서버가 서버를 요청하여 받은 응답을 클라이언트에게 전달하는 방식이다.

내부 인트라넷에 있는 서버를 호출하기 위해서 인터넷 망에 있는 클라이언트가 Reverse 프록시 서버에 요청하여 응답을 받는 방식이다.

Reverse 프록시는 서버가 누구인지 감추는 역할을 해준다.

마찬가지로 클라이언트는 내부 서버를 접근하지 못하기 때문에 보안과 성능을 향상시킬 수 있다.

Forward 프록시 서버와는 반대로 내부 서버는 클라이언트를 알지만,
클라이언트는 프록시를 통해 내부 서버를 접근하기 때문에 내부 서버를 알지 못한다.


예를 들어 보안을 위해 Reverse 프록시 서버는 HTTPS 프로토콜로 접근 가능하도록 하여 보안을 강화한다.

하지만 내부 서버는 자신에게 오는 요청은 Reverse 프록시 서버의 HTTPS 프로토콜로 한번 걸러지기 때문에 HTTP 프로토콜로 처리하면 된다.

또한, 여러개의 내부 서버를 둘 수 있기 때문에 로드 밸런싱이나 서버 확장을 통해 트래픽을 분산시킬 수 있다.




프록시 서버 장점

1. 보안

프록시 서버를 사용하면 클라이언트나 서버 모두 IP를 숨길 수 있는 방법이 생긴다.

실제 서버 또는 클라이언트의 IP를 숨기고 프록시 서버의 IP만 공개함으로써 해킹을 대비할 수 있다.


2. 성능

프록시 서버를 사용하여 캐싱 기능과 트래픽 분산으로 성능 향상을 가져올 수 있다.

캐싱 기능은 자주 사용되는 동일한 요청을 캐싱하여 재활용하는 방식이다.

실제 서버로 다시 호출하지 않고 프록시 서버가 대신 응답을 주어 서버의 자원 사용을 줄여주게 된다.


3. 트래픽 분산

일부 프록시 서버는 로드 밸런싱도 제공하여 여러 대의 분산된 서버가 있다면 서버의 트랙픽을 분산시켜 준다.

그리고 앤드 포인트(URL)마다 호출하는 서버를 설정할 수 있어 역할에 따라 서버의 트래픽을 분산할 수도 있다.




프록시 객체

어떠한 대상의 기본적인 동작의 작업을 가로챌 수 있는 객체를 뜻한다.




JPA 에서의 프록시

실제 Entity 객체 대신에 사용되는 객체로서 실제 Entity 클래스와 상속 관계 및 위임 관계에 있다.

프록시 객체는 실제 Entity 클래스를 상속 받아서 만들어지므로 실제 Entity 겉모습이 같다.

지연 로딩을 사용하면 실제 Entity 객체 대신 가짜 객체가 필요한데 이것이 프록시 객체이다.

프록시 객체는 실제 객체에 대한 참조(target)를 보관한다.

프록시 객체의 메소드를 호출하면 프록시 객체는 실제 객체의 메소드를 호출한다. (이때 실제 db에서 조회한다.)


entityManager.getReference(): 데이터베이스 조회를 미루는 가짜(프록시) Entity 객체 조회



프록시 객체의 초기화

image

초기에 target은 null이고, 영속성 컨텍스트에 초기화를 요청한다.

이후 영속성 컨텍스트는 DB를 조회해서 실제 Entity 객체를 생성하여 target에 연결시켜준다.

target으로 실제 객체와 연결되면, 실제 객체의 메서드를 호출한다.



특징

프록시 객체는 처음 사용할때 한번만 초기화한다.

프록시 객체를 초기화할 때, 프록시 객체가 실제 Entity로 바뀌는것이 아니다.

초기화되면 프록시 객체를 통해서 실제 Entity에 접근이 가능한 것이다.

프록시 객체는 원본 Entity를 상속받기 때문에, 타입체크시 주의해야한다. ( == 대신 instance of 사용)

프록시 객체와 원본 객체는 == 비교시에 타입이 각각 프록시타입과 실제 타입으로 다르다.


실제 Entity 조회 후 프록시 객체를 조회하는 경우

→ 영속성 컨텍스트에 찾는 Entity가 이미 있으면 entityManager.getReference()를 호출해도 실제 Entity가 반환된다.

이미 영속성 컨텍스트에 올라가있기 때문에 굳이 프록시를 가져올 이유가없다.

성능 최적화 입장에서도 영속성 컨텍스트에 올라간 실제 Entity를 가져오면 된다.


프록시 객체를 조회 후 실제 Entity를 조회하는 경우

반대의 상황도 같다 (프록시가 먼저 초기화됐으면, entityManager.find()를 호출해도 프록시 객체가 반환된다)



영속성 컨텍스트의 도움을 받을 수 없는 준영속 상태일 때, 프록시를 초기화하면 문제 발생

아래 코드와 같이 em.detach()em.close(), em.clear()로 영속성 컨텍스트에서 detach시키거나 닫아서

준영속 상태에서 getUsername을 호출하면 LazyInitializationException 예외가 발생한다.




Eager & Lazy

Entity를 조회하고 항상 연관된 Entity가 사용되는 것이 아니다.

JPA는 Entity가 실제로 사용되기 전까지 데이터베이스 조회를 지연할 수 있도록 제공하는데 이를 지연 로딩이라 한다.

실제 사용하는 시점에 데이터베이스에서 필요한 데이터를 가져오는 것이다.



Fetch 기본 전략

  • @ManyToOne, @OneToOne : 즉시 로딩 (EAGER LODING)
  • @OneToMany, @ManyToMany : 지연 로딩 (LAZY LOADING)




즉시 로딩(EAGER LODING)

Entity를 조회할 때 자신과 연관되는 Entity를 join을 통해 함께 조회하는 방식을 말한다.

Entity를 조회할 때 연관된 Entity를 함께 조회한다.

즉시 로딩을 사용하고 싶다면 연관 관계 매핑의 fetch 속성을 FetchType.EAGER로 지정하면 된다.


즉시 로딩하여 가져오기 때문에 프록시를 쓸 일이 없다. 이미 초기화가 다 끝나있는 상태이다.



Comment Entity를 조회하는 find() method를 호출할 때 join이 일어나며 그 때 Registry도 조회하는 것을 볼 수 있다.

즉시 로딩에서 Hibernate는 SELECT 쿼리를 2번 실행하는 것보다 성능 최적화를 위해 join을 수행한다.

image

🐣 application.properties에 아래 코드를 추가하면 위와 같은 형식으로 볼 수 있다.

1
2
3
4
5
# SQL output
spring.jpa.properties.hibernate.format_sql=true

# show sql console
spring.jpa.show-sql=true



EAGER LODING에서 outer join을 사용한 이유는 외래키가 null이 허용되기 때문이다.

@JoinColumn 어노테이션에서 nullable 속성을 추가할 수 있는데, 기본 값으로 true를 갖는다.

따라서 @JoinColumn(name="registry_id", nullable=false) 로 수정하면 즉시 로딩 할 때 outer join이 아닌 inner join을 수행한다.

outer join보다 inner join이 성능이 더 좋다는 것을 감안하여 외래키의 null 허용 여부를 결정해야한다.



즉시 로딩 주의점

  • @ManyToOne, @OneToOne
    • optional = false : 내부 조인
    • optional = true : 외부 조인
  • @OneToMany, @ManyToMany
    • optional = false : 외부 조인
    • optional = true : 외부 조인




지연 로딩(LAZY LOADING)

지연 로딩이란 자신과 연관된 Entity를 실제로 사용할 때 연관된 Entity를 조회( SELECCT )하는 것을 말한다.

연관된 Entity를 실제로 사용할 때 조회한다.

연관 관계 매핑의 fetch 속성을 FetchType.LAZY로 지정하면 사용할 수 있다.

@ManyToOne(fetch = FetchType.LAZY)



지연로딩을 하기 위해서는 프록시라는 것이 필요하다.

실제 데이터가 필요한 순간에서야 데이터베이스를 조회해 프록시 객체를 초기화한다.
Entity를 사용하는 시점에 초기화가 된다.(DB조회)

만약 영속성 컨텍스트에 객체가 이미 존재한다면 프록시 객체가 아닌 실제 객체를 사용한다.




오류

지연 로딩으로 설정하면 에러가 나타나는 경우가 있다.

No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer

필요가 없으면 조회를 안해서 비어있는 객체를 serializer 하려고 해서 발생되는 문제이다.


여기서부터는 내가 이해한 방향이다.

@RestController의 경우 @ResponseBody로 객체를 반환해 줄 때 JSON 형태로 변환한다.

따라서 JSON 형태로 리턴해 주는 시점에서 에러가 발생한 것이다.


Comment를 JSON으로 변환하는 과정에서 Registry를 serialize(직렬화) 해주려는 순간

fetchType이 Lazy라서 실제 Registry 객체가 아닌

프록시로 감싸져 있는 hibernateLazynitializer를 serialize하려 하기 때문에 문제가 발생한 것이다.

→ default 값인 EAGER였다면 실제 매핑되어있는 Registry에 대한 조회가 이루어지고 실제 Registry 객체를 serialize 한다는 말이다.




오류 해결하기

이를 해결하는 방법은 대표적으로 3가지가 있다.

  1. application 파일에 spring.jackson.serialization.fail-on-empty-beans=false 설정해주기

  2. 오류가 나는 Entity의 LAZY 설정을 EAGER로 바꿔주기

  3. 오류가 나는 컬럼에 @JsonIgnore를 설정해주기



1.

application 파일에 spring.jackson.serialization.fail-on-empty-beans=false 설정하는 경우에는 오류만 안나오도록 하는 것이다.

근본적인 해결방법이 아닌 표면적인 방법이므로 근본적인 해결을 위해서는 JSON 형식으로 변환할지 말지에 대해 정해야한다.

image

registry에 hibernateLazyInitializer 데이터가 추가된 걸 확인할 수 있다.



2.

LAZY 설정을 EAGER로 바꿔주는 경우에는 오류가 N+1문제가 발생할 수 있다.

image

단순히 Comment 정보만 조회하더라도 Registry db까지 모두 조회하게 된다.



3.

오류가 나는 컬럼에 @JsonIgnore를 설정해주는 경우 해당 필드를 json으로 변경시 제외된다.

image

registry에 관한 db가 제외되었다.



참고로 각 Entity에 @JsonIgnoreProperties({"hibernateLazyInitializer", "handler"}) 를 설정해주는 방법도 있다.

image



@JsonIgnore과 @JsonIgnoreProperties의 차이

  • @JsonIgnore 어노테이션은 클래스의 속성(필드, 멤버변수) 수준에서 사용
  • @JsonIgnoreProperties 어노테이션은 클래스 수준(클래스 선언 바로 위에)에 사용





reference
프록시란?
[JPA] 프록시, 즉시 로딩과 지연로딩
🙈[Spring JPA] 프록시( proxy )와 지연로딩🐵
[JPA] 프록시란? 지연로딩 vs 즉시 로딩란?
Forward Proxy와 Reverse Proxy 차이점
포워드 프록시(Forward Proxy) vs 리버스 프록시(Reverse Proxy)

[JPA] FetchType.Lazy로 인한 JSON 오류
[JPA]No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer
[JPA]No serializer found for class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor and no properties discovered to create BeanSerializer
@JsonIgnore, @JsonIgnoreProperties, @JsonIgnoreType차이점
[JPA] 프록시 Proxy & LAZY,EAGER 로딩, 영속성 전이 CASCADE

This post is licensed under CC BY 4.0 by the author.