기존 스프링 Security에서 same-origin의 경우 별도 CORS설정을 안해줘도 문제가 없었지만

origin이 Https가 되는 경우엔 cors 설정을 해줘야 한다. 

 

이렇게 Origin 헤더가 https인 경우 CORS 설정이 필요하다. 

Origin: https://test.io

 

또한 allow method 디폴트가 GET,HEAD, POST 이므로

PUT과 같은 method를 사용하는 경우

  public WebMvcConfigurer corsConfigurer() {
    return new WebMvcConfigurer() {
      @Override
      public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedMethods("*")
            .allowedOrigins("https://test.io")
            .allowedHeaders("*")
            .allowCredentials(true);
      }
    };
  }

registry.allowedMethods("*") 설정이 필요하다. 

 

'개발 > 기타' 카테고리의 다른 글

Docker service 실행 안될 때  (0) 2022.04.05
URI와 URL의 개념, 차이  (2) 2020.05.23
SAML / OAuth2.0 / OpenIDConnect  (1) 2020.05.12
[PHP] Call to undefined function dl()  (0) 2020.02.10
카프카(Kafka)  (0) 2020.01.30

스프링 빈 라이프사이클

라이프사이클

스프링의 빈은 스프링이 빈 생성, 주입, 소멸까지를 책임진다. 빈 생성부터 소멸까지의 라이프사이클을 다뤄보고자 한다.

라이프사이클 콜백

컨테이너가 빈의 라이프사이클을 관리하는데, 라이프사이클의 특정 시점에 커스텀 로직을 넣을 수 있도록 한 것이 라이프사이클 콜백이다.

라이프사이클 살펴보기

1. XML이나 어노테이션을 탐색하여 빈 정의를 스캔

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

SpringApplication.run()에서 어플리케이션컨텍스트를 만들고 빈 정의를 탐색하기 시작한다.

2. 빈 클래스의 인스턴스를 생성 - 생성자 호출

정의된 빈 인스턴스를 생성한다. 이때 생성자를 호출한다.

3. 빈 안에 주입되야할 프로퍼티들을 셋팅한다.

@Component
public class HelloBean implements ApplicationContextAware, InitializingBean {
    @Autowired
    WorldBean worldBean;
}

빈 안에 프로퍼티를 주입해야 하는데 프로퍼티에 해당하는 빈이 아직 생성되지 않은 경우 그 빈부터 생성해준다.
빈 간에 순환참조가 있으면 빈 생성과정에 에러가 발생하게 된다.

3. ~Aware 관련 콜백 호출

예를 들면 다음과 같은 것들이 있다.

ApplicationContextAware.setApplicationContext - 어플리케이션컨텍스트를 받을 수 콜백,
BeanClassLoaderAware.setBeanClassLoader - 클래스로더를 받을 수 콜백

이걸 써서 컨텍스트나 클래스로더를 따로 저장해둬야하는 상황이 어떤게 있을까....

이런 콜백들을 호출하려면 각 인터페이스를 implement해야한다.

package sample;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanClassLoaderAware;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

@Component
public class MyBean implements ApplicationContextAware, BeanClassLoaderAware {

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        // do something
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        // do something
    }
}

4. @PostContruct, InitializingBean.afterPropertiesSet(), init-method

셋은 프로퍼티 및 종속성 주입이 완료된 후 호출되는 점에서 동일하다.

@PostContruct는 JSR-250에서 정의한 어노테이션으로 스프링이 아닌 프레임워크에서도 사용할 수 있는 콜백이고, InitializingBean.afterPropertiesSet(), init-method는 스프링에서 제공하는 콜백이다.

InitializingBean.afterPropertiesSet()의 경우 InitializingBean 인터페이스를 implement하면 된다.

@Component
public class HelloBean implements InitializingBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        logger.info("afterPropertiesSet() called");
    }
}

init-method의 경우 @Bean(initmethod="..")으로 등록한 메서드가 실행되게 된다.

실행 순서는 @PostContruct -> InitializingBean.afterPropertiesSet() -> init-method 이다.

세 가지 콜백을 각각 언제 사용해야 하는지를 명확히 구분할 수는 없다.

참고로 BeanPostProcessor.postProcessBeforeInitialization() 안에서 @PostContruct 가 호출된다.

5. BeanPostProcessor.postPRocessAfterInitialization()

기본 스프링부트 설정을 사용하는 경우 BeanPostProcessor를 따로 설정하지 않는 한 이 단계에서 하는 일은 없다.

이렇게 되면 빈을 사용할 수 있는 상태가 된다.
빈이 제거 될 때 설정할 수 있는 콜백도 생성과 비슷하다.

'개발 > Spring' 카테고리의 다른 글

[Java, Spring] Filter와 Interceptor의 차이  (0) 2020.05.22
[SpringMVC] CORS 처리  (2) 2020.03.31

자바 언어에 타입 파라미터라는 개념을 도입한 것이 제네릭스(Generics)이다.
인터페이스나 메서드에서 처리할 타입을 사용하는 쪽에서 지정할 수 있다.

가장 쉬운 예로 List 인터페이스는 제네릭스 기반으로 작성되었다.

List  
booelan add(E e);  
E get(int index);

List<Integer> list; 혹은 List<String> list; 등 사용하는 쪽에서 지정하는 것이다.

제네릭스가 없다면 여러 타입을 담으려면 결국 모든 클래스의 조상인 Object로 지정해야 했을 것이다.

List  
boolean add(Object object);  
Object get(int index);

이렇게 하면 발생하는 문제는 다음과 같다.

  1. 형변환
    매번 사용할 때마다 형변환을 해줘야 한다.

    Integer 타입을 예로 들면,

    Integer value = 4;
    add((Object) value);
    (Integer) ret = get(1);
  2. 타입 체크가 안됨
    Integer 리스트를 만들었는데 모르고 Long을 넣어도 알 수가 없다.

    Long value = 4L;
    add((Object) value);

이러한 이유로 제네릭스를 이용하는 것이 좋다.

참고자료

토비의 스프링 3.1, 이일민

'URI가 URL을 포함한다' 정도만 알고 있었는데, 제대로 정리해보고자 한다.

URI, URL, URN의 개념

모두 리소스 식별자이다.
리소스를 구분하고 싶은 것이다.

 

https://danielmiessler.com/study/difference-between-uri-url/

 

예를 들어 어떤 두 사람이 있다고 치자.
두 사람을 구분하고 싶다.
어떻게 구분할까?

  1. 이름을 붙인다. --> URN
    첫번째 사람 - 홍길동,
    두번째 사람 - 박길동

  2. 그 리소스를 얻을 수 있는 주소(위치)를 표현한다. --> URL
    첫번째 사람 - 서울시 아무구 아무동 123-1
    두번째 사람 - 서울시 아무구 아무동 456-1

이러한 방법으로 리소스를 명확히 식별할 수 있으면(구분할 수 있으면) 그건 URI다.
따라서 URN(Uniform Resource Name) 과 URL(Uniform Resource Locator) 모두 URI(Uniform Resource Identifier)다.

여기서 하나씩 짚고 넘어가보자.

URN이란?

리소스에 고유한 이름을 부여해서 구분하겠다는 방식이다.
여기서 중요한건 고유해야한다는 것이다.

예를 들어 urn:isbn:0-486-27557-4 를 검색하면 로미오와 줄리엣이 나온다.
ISBN은 국제표준도서번호이고, 국제표준으로 도서에 고유한 이름 을 부여한 것이다.
이건 서로 약속하는 것이다. 중복이 없어야 한다.

그런데 여기서 URL과 차이가 있다.
URN은 리소스를 식별만 할뿐 리소스를 얻을 수 있는 위치는 포함하지 않는다
리소스 위치에 상관 없이 고유하게 식별하려는 목적이다.

URL이란?

리소스를 식별하는 방법 중에, 리소스의 위치로 식별하는 방법이다.

ex) https://tools.ietf.org/html/rfc1738 --> 이 URL로 특정 RFC문서(리소스)를 얻을 수 있다.
ex) https://somewhere.com/user?userId=123 --> 이 URL로 특정 유저 정보(리소스)를 얻을 수 있다.

리소스를 위치 로 식별하겠다는 것이다.

URN과 비교를 해보자.

https://book.com/romio-juliet
https://book.net/romio-juliet

둘 다 동일한 로미오와 줄리엣 리소스를 가리킨다고 했을 때 URL은 각각을 서로 다른 리소스로 식별한다.

중간 정리

리소스 식별자 --> URI = URN + URI + α
이름으로 식별 --> URN
위치로 식별 --> URL

혼란

관련해서 구글링을 해보면 URL의 정의에 대한 여러 의견이 있어 혼란스러운 부분이 있었고, 이를 나름대로 정리해보고자 한다.

어디까지 URL인가

아래와 같이 정리된 글이 있었다.

https://somewhere/search?q=uri 아래와같은 주소가 있다고하자. query string인 q의 값에 따라 여러가지 결과값을 가져올 수 있다. 위 주소에서 URL은 https://somewhere/search까지이고, 내가 원하는 정보를 얻기위해서는 q=uri라는 식별자가 필요하므로, https://somewhere/search?q=uri 이 주소는 URI이지만 URL은 아니다.

아니다. 쿼리스트링까지 있어야 리소스를 명확히 식별할 수 있으므로 https://somewhere/search?q=uri이 URL이고 URI다. 쿼리스트링도 URL에 포함된다.

또 다른 글이 있었다.

http://somewhere.com/132 에서 http://somewhere.com까지만 URL이다.

아니다. 정확히 132에 해당하는 리소스를 식별해야하므로 http://somewhere.com/132 까지가 URL이다.
물론 http://somewhere.com에 해당하는 리소스가 있으면 http://somewhere.com 자체도 URL이지만 132에 대한 URL은 http://somewhere.com/132인 것이다.

이는 URL의 범위가 어디까지 인가에 대한 혼란인 것 같다.
RFC1737에 따르면 PATH, 쿼리스트링 모두 URL에 포함된다.

  httpaddress     http://hostport[/path][?search]   

이런 혼란이 생긴 이유중에 하나로 HttpServletRequest의 잘못도 있다고 생각한다.

    @GetMapping("/user")
    public void test(HttpServletRequest request, @RequestParam("id") int id) {
        String uri = request.getRequestURI();
        String url = request.getRequestURL().toString();
        logger.info("uri - {}, url - {}", uri, url);
    }

로그를 찍어보면 아래와 같이 나온다.
uri - /user, url - http://localhost:8080/user

이걸보고, '아 url에 쿼리스트링은 포함되지 않는구나' 에 더불어 'uri는 path 부분을 말하는구나' 라고 착각할 수 있을 것 같다.

HttpServletRequest의 문서를 읽어보면

getRequestURI
public java.lang.String getRequestURI()
Returns the part of this request's URL from the protocol name up to the query string in the first line of the HTTP request. The web container does not decode this String. For example:
First line of HTTP request Returned Value
POST /some/path.html HTTP/1.1 /some/path.html
GET http://foo.bar/a.html HTTP/1.0 /a.html
HEAD /xyz?a=b HTTP/1.1 /xyz
To reconstruct an URL with a scheme and host, use HttpUtils.getRequestURL(javax.servlet.http.HttpServletRequest).

Returns:
a String containing the part of the URL from the protocol name up to the query string
See Also:
HttpUtils.getRequestURL(javax.servlet.http.HttpServletRequest)

getRequestURI()는 URL(URL은 URI이므로)의 일부분(path)만 리턴한다고 되어있고, 전체 URL을 얻고 싶으면 다른 방법을 쓰라고 되어있다.
getRequestURI()의 리턴값 /user 은 URL의 일부일 뿐, URL은 아니다. method명이 혼란을 주는 듯 하다.

getRequestURL
public java.lang.StringBuffer getRequestURL()
Reconstructs the URL the client used to make the request. The returned URL contains a protocol, server name, port number, and server path, but it does not include query string parameters.
Because this method returns a StringBuffer, not a string, you can modify the URL easily, for example, to append query parameters.

This method is useful for creating redirect messages and for reporting errors.

Returns:
a StringBuffer object containing the reconstructed URL

여기서는 또 URL에서 쿼리스트링을 빼고 리턴하겠다고 한다.
때문에 쿼리스트링은 URL이 아닌 것 처럼 혼란을 주는 것 같다.
getRequestURL()의 리턴값 http://localhost:8080/user은 URL의 일부일 뿐, URL은 아니다.

결론

  • URI는 리소스 식별자이다.
  • URL은 리소스를 어디서 얻을 수 있는 지 위치를 말해준다. URL는 식별방법의 일부이다.
  • 또 다른 식별방법으론 고유한 이름을 부여하는 URN이 있다.

Appendix

RFC says

A URI can be further classified as a locator, a name, or both. The term “Uniform Resource Locator” (URL) refers to the subset of URIs that, in addition to identifying a resource, provide a means of locating the resource by describing its primary access mechanism (e.g., its network “location”).
rfc 3986, section 1.1.3

URI는 위치, 이름 등이 될 수 있다.

The URI itself only provides identification; access to the resource is neither guaranteed nor implied by the presence of a URI.
rfc 3986, section 1.2.2

URI는 식별만 할 뿐, 리소스를 어디서 얻을 수 있는지를 반드시 제공해야하는 것은 아니다.

Each URI begins with a scheme name, as defined in Section 3.1, that refers to a specification for assigning identifiers within that scheme.
rfc 3986, section 1.1.1

참고자료

https://danielmiessler.com/study/difference-between-uri-url/
https://tools.ietf.org/html/rfc1738

'개발 > 기타' 카테고리의 다른 글

Docker service 실행 안될 때  (0) 2022.04.05
[SpringSecurity] Https same origin 에 대한 CORS 설정  (0) 2020.06.09
SAML / OAuth2.0 / OpenIDConnect  (1) 2020.05.12
[PHP] Call to undefined function dl()  (0) 2020.02.10
카프카(Kafka)  (0) 2020.01.30

Filter와 Interceptor, 둘 다 전후처리 용도로 사용될 수 있다는 점에선 동일한 것 같다.
구체적으로 어떤 차이가 있는 지 정리해보았다.

Filter

Filter는 Java의 Servlet 스펙에서 정의된 개념이다.
Java Servlet의 스펙이므로 스프링 프레임워크가 없이도 사용 가능하다.
SpringMVC를 사용하는 경우 DispatcherServlet 전후로 호출된다.

Interceptor

Interceptor는 스프링의 스펙으로, Filter와 목적은 동일하나 좀 더 세분화된 컨트롤을 제공한다.

When to use Interceptor

세분화된 컨트롤을 하고자 하는 경우

Filter는 전, 후 처리만 가능하지만 Interceptor는 preHandle, postHandler, afterCompletion로 세분화된다.
그 외 WebContentInterceptor, LocaleChangeInterceptor 등의 상황에 맞게 활용할 수 있는 다양한 인터페이스를 제공한다.

ModelAndView를 컨트롤 해야하는 경우

Filter는 DispatcherServlet의 영역이 아니기 때문에 ModelAndView에 접근이 불가하다.
Interceptor는 DispatcherServlet 사이클 안에서 사용되므로 ModelAndView를 접근할 수 있다.

public interface HandlerInterceptor {
    // ... 생략
    default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
            @Nullable ModelAndView modelAndView) throws Exception {
    }

When to use Filter

HttpServletRequest, HttpServletResponse를 유연하게 컨트롤하고자 하는 경우

Filter는 다음 Filter로 체이닝을 할때 새로운 HttpServletRequest, HttpServletResponse를 넘길 수 있다.
Interceptor는 이미 DispatcherServlet으로 넘어온 상태이기 때문에 HttpServletRequest, HttpServletResponse를 다른 인스턴스로 교체할 수 있는 방법이 없다.

예를 들어 다음 상황에서 Filter가 좀 더 유연하다.

  • ServletRequest의 Body를 여러번 읽어야 하는 경우 (ex: 로깅)
    ServletRequest의 Body는 한 번 밖에 읽을 수 없다. 여러번 읽을 수 있게 원본 Request에서 읽은 내용을 byte[] 버퍼에 저장해둔 새로운 ServeletRequest를 정의하여 넘길 수 있다.

이처럼 재정의한 인스턴스로 넘길 수 있기 때문에 유연하게 내가 원하는 기능을 추가할 수 있을 것이다.

참고로 SpringSecurity는 Filter 기반으로 동작한다. (링크)

Appendix

다음은 Filter에서 HttpServletRequest, HttpServletResponse를 수정하는 예시다.

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
      throws IOException, ServletException {
        HttpServletRequest currentRequest = (HttpServletRequest) servletRequest;
        ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(currentRequest);
        chain.doFilter(wrappedRequest, servletResponse);
    }

'개발 > Spring' 카테고리의 다른 글

[Spring] 스프링 빈(Bean) 라이프사이클  (0) 2020.05.23
[SpringMVC] CORS 처리  (2) 2020.03.31

고정 비율 그리드

20% 너비로 설정한 그리드

HTML

<div class="parent">
    <div class="child"></div>
    <div class="child"></div>
    <div class="child"></div>
    <div class="child"></div>
    <div class="child"></div>
    <div class="child"></div>
    <div class="child"></div>
    <div class="child"></div>
</div>

CSS

  .parent {  
    display:flex;  
    flex-wrap: wrap;
    border:1px solid black;
  }

  .child {  
    flex-basis: 20%;
    overflow:hidden;
    background-color:skyblue;
    box-shadow: 0 0 0 1px #000 inset;
  }

 

https://jsfiddle.net/ksh491zf/

'개발 > 프론트' 카테고리의 다른 글

[자바스크립트] 호이스팅(Hoisting)  (0) 2020.01.30

Insert 후 ID를 리턴하고 싶은 경우 LAST_INSERT_ID()를 사용하면 된다.

MyBatis의 경우 selectKey 문을 이용하여 사용 가능하다.

<insert id="addUser" parameterType="User">
  INSERT INTO TB_USER VALUES ( // 생략)
  <selectKey keyProperty="id" resultType="Integer">
    SELECT LAST_INSERT_ID()
  </selectKey>
</insert>        

참고로 리턴값에 담기는게 아니라 파라미터로 넘긴 User 객체에 id가 담긴다.

동시다발로 요청을 하는 경우 이 값이 계속 업데이트되면 문제가 되지 않을까 생각했는데, 그렇지 않다.
MySQL 공식 문서에 따르면 다음과 같다.

For LAST_INSERT_ID(), the most recently generated ID is maintained in the server on a per-connection basis. It is not changed by another client. It is not even changed if you update another AUTO_INCREMENT column with a nonmagic value (that is, a value that is not NULL and not 0). Using LAST_INSERT_ID() and AUTO_INCREMENT columns simultaneously from multiple clients is perfectly valid. Each client will receive the last inserted ID for the last statement that client executed.

커넥션 별로 _LAST_INSERT_ID_가 관리되므로 여러개의 요청이 동시에 발생해도 문제가 없다.

'개발 > 데이터베이스' 카테고리의 다른 글

ACID (Atomicity, Consistency, Isolation, Durability)  (0) 2020.02.03

자주 사용하지만 번번히 헷갈리는 Spring의 주요 어노테이션들을 정리해보고자 한다.

@SpringBootApplication

@SpringBootApplication은 다음 4개의 어노테이션을 포함한다.

@Configuration

어플리케이션 컨텍스트의 빈이 정의된 클래스임을 가리킨다.

@Configuration
public class MyConfig {
  @Bean
  public MyBean getMyBean() {
    ... 
  }
}

@EnableAutoConfiguration

@Configuration이랑 헷갈릴 수 있으나 다른 개념이다.

클래스패스와 내가 정의한 빈에 기반해서 필요할 것 같은 나머지 빈들을 알아서(지능적으로) 추측해서 생성해주는 역할이라고 한다.
예를들어 tomcat-embedded.jar이 클래스패스에 있는 경우 TomcatServletWebServerFactory 빈을 추가로 등록해준다.
스프링부트의 핵심 기능이라고 볼 수 있다.

예를 들어 Thymeleaf 관련해서 AutoCongifuration이 어떻게 동작하는 지 보면

@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {
 // ...
}

TemplateMode.class, SpringTemplateEngine.class 요 두가지 클래스가 클래스패스에 있는 경우 ThymeleafAutoConfiguration의 빈들을 생성하는 방식이다.

@EnableWebMvc

웹 어플리케이션에 필요한 DispatcherServlet 같은 것들을 셋팅한다.
spring-webmvc가 클래스패스에 있는 경우 자동으로 추가된다.

@ComponentScan

Bean을 탐색하는 경로를 지정하는 어노테이션이다. @EnableAutoConfiguration와 함께 사용된다.
지정된 패키지의 서브패키지들을 탐색한다. 때문에 루트 패키지에 붙이는 것이 일반적이다.

+ Recent posts