'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

세가지 인증/인가 표준에 대해 공부하면서 나름대로 이해한 내용을 정리해보았다.
퍼즐 조각 맞추듯 이해한 내용이기 때문에 정확하지 않을 수 있다.

SSO(Single Sign On)

SSO가 없는 상황
SSO가 도입된 상황

  • 한번의 로그인으로 여러 어플리케이션 이용한다. (로그인 피로도 감소)
  • 인증을 못하면 전체 어플리케이션을 이용할 수 없다.
  • 비인가 유저가 일단 권한을 얻으면 모든 어플리케이션을 이용할 수 있게 된다.

한번만 로그인하면 된다?

정확히는 브라우저당 로그인을 한번만 하면 된다.

예를 들어 a.com, b.com이 같은 idp(myidp.com)를 바라보고 SSO가 되어있다고 할때,

  1. 인증안한 상태로 a.com 접속
  2. myidp.com으로 리다이렉트
  3. myidp.com 세션 쿠키가 없으므로 사용자에게 로그인 요청
  4. 인증 성공하면 myidp.com에 세션쿠키를 생성
  5. a.com의 콜백 url로 리다이렉트, a.com에 세션쿠키 생성
  6. 최초 요청 페이지로 다시 리다이렉트

이 상태에서 b.com 접속한다고 해보자. 다시 로그인해야할까? 아니다. 왜 그런걸까?

  1. b.com에는 세션 쿠키가 없는 상태로 접속
  2. myidp.com으로 리다이렉트
  3. myidp.com 세션쿠키가 있으므로 (위의 3에서) 사용자 로그인 필요없이 인증성공 내려줌
  4. b.com의 콜백 url로 리다이렉트, b.com에 세션쿠키 생성
  5. 최초 요청 페이지로 다시 리다이렉트

때문에 브라우저에 myidp.com의 세션쿠키만 남아있다면 다른 사이트에서 별도의 인증이 필요 없는 것이다.

SAML2.0 / OAUTH2.0 / OIDC(OpenIDConnect)

출처: https://spin.atomicobject.com/2016/05/30/openid-oauth-saml/

OAuth는 인증보단 인가 목적의 표준이므로 SAML과 OIDC를 비교해보자.
SSO과 같이 통합인증(federated authentication)을 위해 SAML이 있었다.
그런데 단점이 있다. SAML은 클라이언트가 웹사이트인 경우에는 적합하지만 모바일 어플리케이션 등에는 적합하지 않다. (그 이유는 아래에서)
이런 단점들을 OAuth 프로토콜을 이용해 보완하게 OIDC이다.

SAML(Security Assertion Markup Language)과 OIDC

SAML 인증 플로우

SAML은 SP가 웹 사이트여야만 한다. OIDP는 웹이나 모바일앱 등이 가능하다.

SAML은 백채널을 (거의) 안쓴다. 브라우저를 통해서만 SP와 IDP가 인증정보를 주고 받는다.
서버간 통신없이 클라이언트를 통해서 인증 데이터를 주고 받기 때문에, POST 바인딩을 통해 인증정보 + 리다이렉션을 처리한다. ( POST Binding) 헤더를 사용하는게 아니라 HTML내에 스크립트를 심어서 리다이렉트를 시키기 때문에 클라이언트가 웹사이트여야 한다. 모바일웹에서 SAML 방식을 사용한다고 하면 별도로 브라우저를 띄워서 구현해야 할 텐데 그럼 사용자 경험이 떨어질 것이다.

IDP의 응답으로 내려온 HTML에서 스크립트로 리다이렉트 처리하는 예시이다.
form으로 POST 요청을 만들어 IDP에서 얻은 인증 정보를 SP에게 전달한다.
스크립트가 실행되기 위해선 브라우저라는 환경이 필요하다.

<form method=’post’ action=’https://localhost:9443/samlsso'>
  <input type=’hidden’ name=’SAMLRequest’ value=’PD94bWwgdmVyc2lvbj0iMS4wln…….’>
  <input type=’hidden’ name=’RelayState’ value=”trtreey34234fdfes’’>
  <button type=’submit’>POST</button>
</form>

때문에 이런 단점을 보완한 서버간 통신을하는 OAuth 프로토콜 기반 OIDC가 나오게 된 듯하다.

SAML의 XML Assertion이 OIDC의 ID token에 대응되는 개념이다.

아래가 사용자 정보를 담은 SAML의 XML Assertion이다.

```
<samlp:ArtifactResponse
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
ID="identifier_5"
InResponseTo="identifier_4"
Version="2.0"
IssueInstant="2004-12-05T09:22:05Z">
<!-- an ArtifactResponse message SHOULD be signed -->
<ds:Signature
  xmlns:ds="http://www.w3.org/2000/09/xmldsig#">...</ds:Signature>
<samlp:Status>
  <samlp:StatusCode
    Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
</samlp:Status>
<samlp:Response
  xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
  xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
  ID="identifier_6"
  InResponseTo="identifier_3"
  Version="2.0"
  IssueInstant="2004-12-05T09:22:05Z"
  Destination="https://sp.example.com/SAML2/SSO/Artifact">
  <saml:Issuer>https://idp.example.org/SAML2</saml:Issuer>
  <ds:Signature
    xmlns:ds="http://www.w3.org/2000/09/xmldsig#">...</ds:Signature>
  <samlp:Status>
    <samlp:StatusCode
      Value="urn:oasis:names:tc:SAML:2.0:status:Success"/>
  </samlp:Status>
  <saml:Assertion
    xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
    ID="identifier_7"
    Version="2.0"
    IssueInstant="2004-12-05T09:22:05Z">
    <saml:Issuer>https://idp.example.org/SAML2</saml:Issuer>
    <!-- a Subject element is required -->
    <saml:Subject>
      <saml:NameID
        Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress">
        user@mail.example.org
      </saml:NameID>
      <saml:SubjectConfirmation
        Method="urn:oasis:names:tc:SAML:2.0:cm:bearer">
        <saml:SubjectConfirmationData
          InResponseTo="identifier_3"
          Recipient="https://sp.example.com/SAML2/SSO/Artifact"
          NotOnOrAfter="2004-12-05T09:27:05Z"/>
      </saml:SubjectConfirmation>
    </saml:Subject>
    <saml:Conditions
      NotBefore="2004-12-05T09:17:05Z"
      NotOnOrAfter="2004-12-05T09:27:05Z">
      <saml:AudienceRestriction>
        <saml:Audience>https://sp.example.com/SAML2</saml:Audience>
      </saml:AudienceRestriction>
    </saml:Conditions>
    <saml:AuthnStatement
      AuthnInstant="2004-12-05T09:22:00Z"
      SessionIndex="identifier_7">
      <saml:AuthnContext>
        <saml:AuthnContextClassRef>
          urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport
       </saml:AuthnContextClassRef>
      </saml:AuthnContext>
    </saml:AuthnStatement>
  </saml:Assertion>
</samlp:Response>
</samlp:ArtifactResponse>
```

다음은 OIDC의 id_token이다.
{ "iss": "https://accounts.google.com", "azp": "1234987819200.apps.googleusercontent.com", "aud": "1234987819200.apps.googleusercontent.com", "sub": "10769150350006150715113082367", "at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q", "hd": "example.com", "email": "jsmith@example.com", "email_verified": "true", "iat": 1353601026, "exp": 1353604926, "nonce": "0394852-3190485-2490358" }

OIDC와 OAuth2.0의 차이

OIDC는 OAuth2.0 기반으로 돌아가기 때문에 비슷하나 목적이 다르다.

  • OAuth2.0은 3rd Party의 API 사용인가가 목적이고, OIDC는 통합인증(federated authentication), SSO가 목적이다.
    (OAuth를 인증 용도로도 사용할 수는 있다)

  • OAuth를 인증 용도로 사용할 수 있는데 왜 OIDC가 필요한걸까?

    • OAuth는 다양한 상황의 요구사항을 충족할 수 있또록 제네릭하게 구현되었다. 제네릭하다는 것은 그 만큼 클라이언트에서 할일이 많아진다. 반면 OIDC는 인증 초점이다. 상대적으로 클라이언트가 간편하게 사용할 수 있다. 또한 OAuth에서 제공하지 않는 부가적인 스펙(OP Discovery 등)도 있다. 때문에 인증만이 목적이라면 OIDC를 사용하는게 좋을 것 같다.
  • OIDC에는 id_token 개념이 추가된다. 사용자에 대한 부가적인 정가 id_token에 담긴다. 사용자 정보를 얻기 위해 별도 API콜을 하지 않아도 된다. (요청량이 많은 서비스에선 유의미한 효과일 것이다)

  • Oauth 인가 플로우

       +--------+                               +---------------+
       |        |--(A)- Authorization Request ->|   Resource    |
       |        |                               |     Owner     |
       |        |<-(B)-- Authorization Grant ---|               |
       |        |                               +---------------+
       |        |
       |        |                               +---------------+
       |        |--(C)-- Authorization Grant -->| Authorization |
       | Client |                               |     Server    |
       |        |<-(D)----- Access Token -------|               |
       |        |                               +---------------+
       |        |
       |        |                               +---------------+
       |        |--(E)----- Access Token ------>|    Resource   |
       |        |                               |     Server    |
       |        |<-(F)--- Protected Resource ---|               |
       +--------+                               +---------------+
  • OIDC 인증 플로우

OIDC가 OAuth2.0 기반이므로 플로우는 유사하다.
앞서 말했듯이 OIDC는 access_token외에 id token이라는 것을 추가로 받게 된다.

-   `ID token`  
    JWT 형태로, 누가 인증을 했는지, 그 사람에 대한 정보, 발행자(issuer)가 누구인지등의 정보가 담겨있다.

    ```
     {
       "iss": "https://accounts.google.com",
       "azp": "1234987819200.apps.googleusercontent.com",
       "aud": "1234987819200.apps.googleusercontent.com",
       "sub": "10769150350006150715113082367",
       "at_hash": "HK6E_P6Dh8Y93mRNtsDB1Q",
       "hd": "example.com",
       "email": "jsmith@example.com",
       "email_verified": "true",
       "iat": 1353601026,
       "exp": 1353604926,
       "nonce": "0394852-3190485-2490358"
     }
    ```

때문에 추가적인 API 콜없이 토큰을 열어서 유저정보를 획득할 수 있다.

정리

OIDC는 통합 인증을 위한 표준으로 SAML을

  • XML Assertion을 ID token (JWT)으로
  • 통신 플로우를 OAuth2.0 방식으로
    재작성했다고 볼 수 있다.
sudo yum install http://opensource.wandisco.com/centos/6/git/x86_64/wandisco-git-release--1.noarch.rpm
sudo yum install git

CORS(Cross Origin Resource Sharing)

다른 오리진(Cross origin)으로의 요청은 보안상의 이유로 제한된다.
요청을 정상적으로 보내려면(CORS를 하려면) 별도 추가 작업이 필요하다.

CORS 과정

from.com에서 POST to.com/myApi라는 리소스를 요청한다고 하자.

Preflight 요청

본 요청을 보내기전에 서버(b.com)가 요청하는 리소스에 CORS를 허용하는 지를 확인하는 Preflight 요청을 보낸다.

  OPTIONS /myApi
  Access-Control-Request-Method: POST 
  Access-Control-Request-Headers: origin, x-requested-with
  Origin: https://from.com
  • OPTIONS라는 특별한 메서드를 사용한다.
  • Access-Control-Request-Method, Access-Control-Request-Headers, Origin 헤더가 있어야 한다.
  • 이 요청을 'Preflight'라 한다.
  • Preflight는 브라우저에 의해 자동으로 요청된다.(개발자는 관여하지 않는다.)

Preflight 응답 확인

CORS를 허용하는 경우 응답에 아래 헤더가 있어야 한다.

Access-Control-Allow-Origin: https://from.com
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type (설정값에 따라 다름)
Access-Control-Max-Age: 86400 (설정값에 따라 다름)

a.com에서 해당 API를 POST로 호출해도 좋다는 응답이다. 이후 본요청을 전송하게 된다.

만약 허용하지 않는 서버라면 응답에 위 헤더들이 없을 것이고 브라우저는 다음과 같은 에러를 뱉을 것이다.

No 'Access-Control-Allow-Origin' header is present on the requested resource.

참고로 Preflight가 실패하면 본래의 요청 조차 보내지 않는다.

CORS 처리하기

보통 백엔드 서버만 작업하면 된다.
그러나 세션정보(쿠키)등을 같이 넘기려면 JS 작업이 필요할 수 있다.

SpringBoot에서 CORS 처리하기

옵션 1, @CrossOrigin 어노테이션 사용한다.

@CrossOrigin(origins = "https://from.com")
@PostMapping("/myApi")
public void myApi() {}

세션 정보(쿠키)를 같이 넘기려면 withCredentials를 활성화해야 한다. (js에서도 withCredentials 옵션을 줘야한다.)

@CrossOrigin(origins = "https://from.com, withCredentials = "true" )
@PostMapping("/myApi")
public void myApi() {}

withCredentials=true로 하려면 allow origin을 "*"으로 설정하면 안된다. {"a.com", "b.com"} 등으로 특정해야 한다.

옵션 2. 전역 설정

@Configuration
public WebConfig {
    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/myApi").allowedOrigins("https://from.com");
                //.allowCredentials(true) // 필요한 경우
            }
        };
    }
 }

JS에서 CORS 처리하기

특별히 할건 없다.
세션정보(쿠키)를 넘기려면 withCredentials 옵션을 줘야한다.

기본

var xhr = new XMLHttpRequest();
xhr.open('POST', 'https://to.com/myApi', true);
xhr.withCredentials = true;
xhr.send(null);

axios

const myMethod = () => {
  axios.post(`https://to.com/myApi`, {
    ...
  },
  { withCredentials: true }
  ).then(response => {
    ...
  })
}

jQuery

$.ajax({
   url: 'https://to.com/myApi',
   xhrFields: {
      withCredentials: true
   }
   ...
});

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

[Spring] 스프링 빈(Bean) 라이프사이클  (0) 2020.05.23
[Java, Spring] Filter와 Interceptor의 차이  (0) 2020.05.22

개인적으로 공부하며 정리한 내용으로 정확하지 않을 수 있습니다.


Java에는 변수 타입에는 기본형(Primitive Type)과 참조형(Reference Type)이 있다.
기본형은 short, int, long, float, double, byte, char, boolean로 총 8개가 지원된다.

기본형과 참조형의 차이

기본형 변수는 값을 그대로 저장한다.
반면 참조형 변수는 객체의 레퍼런스를 저장한다.

int a = 3; // 기본형
int b[] = new int[]{1,2,3} // 참조형

기본형은 사이즈가 고정되어있기 때문에 해당 변수의 메모리 공간에 그대로 값을 할당하면 된다.
그런데 참조형에는 객체가 할당되어야 하는데, 그 사이즈가 유동적이다. 그래서 바로 값을 할당할 수 없고 힙(Heap) 영역에서 별도의 메모리공간을 차지하여 그곳에 할당한뒤 변수에는 해당 힙 영역의 주소값을 가리키게 한다.

왜 기본형과 참조형 두 개념이 있는 걸까

기본형도 참조형처럼 레퍼런스를 참조하게 되면 개발자는 기본형과 참조형의 차이를 두지 않고 개발할 수 있을거 같은데 왜 두 개념을 구분했을까 하는 생각이 들었다.

우선 기본형을 참조형처럼 레퍼런스로 참조하는 방식으로 사용하면 기본형으로 사용했을 때에 비해 참조 비용이 증가하게 된다. 변수가 몇개 안되면 성능 차이가 안느껴질 수 있지만 int[]의 사이즈가 매우 크다고 생각 했을 때 모든 요소를 참조할 때 마다 힙 영역의 메모리 주소를 또 다시 참조해야 한다고 하면 비용 차이가 커질 수 있다.

때문에 기본형으로 처리할 수 있는 것은 기본형으로 쓰는 것이 나을 것이다.

래퍼클래스(Wrapper Class)

Java는 객체지향 언어이다. 모든 것을 객체로 다룬다! 문제는 기본형은 객체가 아니라는 거다.
List에 int를 넣고 싶다고 해서 List<int> a;와 같이 선언할 수 없다.
저게 왜 안돼? 라고 생각을 할 수 있는데, 모든 것을 객체로 다루는 Java에서 기본형까지 저런식으로 사용할 수 있게 하려면 그 구현이 매우 복잡했을 것이여서 그렇지 않을 까 한다.

이럴 때 사용하는데 래퍼클래스이다. 래퍼 클래스는 기본형을 객체로 한번 감싼 클래스이다. 때문에 기본형을 객체로 다룰 수 있게 해준다.

public class Integer {
    ...
    private final int value;
    ...
}
// Integer라는 래퍼클래스를 사용하면 List에 정수형값을 담을 수 있다. 
List<Integer> a;

// 기본형은 null값을 가질 수 없지만 래퍼 클래스 변수는 null값을 가질 수 있다.
Integer b = null;

그럼 기본형 대신 항상 래퍼클래스를 쓰면 되지 않을까?

역시나 비용문제가 크다. 객체 생성 비용, 참조 비용등... 기본형에 비해 비싸다. 필요할 때만 써야한다. 

그럼 기본형의 개념을 숨기고 모든 것을 객체로만 처리하게 할 수는 없을까?

컴파일러 내부적으로 기본형의 개념을 가지되, 개발자는 기본형을 모르고 모든 것을 객체로 다룰 수 있게 할 수도 있지 않은가? 라는 생각이 들었다.
예를 들면 Groovy의 경우 int a = 3으로 선언해도 a를 래퍼 클래스(Wrapper Class)로 오토박싱(Autoboxing)하여 모든 것을 객체로 다룬다고 한다.

Java는 왜 이렇게 하지 않는 걸까? 라고 생각해서 찾아보니... 비교적 최근 언어인 Groovy등은 기존 언어의 단점등을 보완해서 새로운 언어로 정의한 것이라 가능하겠지만, Java는 기존에 사용하던 오래된 언어 속하고 이런 것들을 고치기엔 너무나 많은 곳에서 사용되고 있다. 하위호환을 생각했을 때 기본형을 없애기는 힘들 것 같다.

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

[Java] Generics  (0) 2020.05.23
[Java] private vs protected  (0) 2020.02.17
[Java] 불변(Immutable) 속성  (0) 2020.02.04
[Java] 자바 네티이브 인터페이스(JNI)  (0) 2020.02.01
[JVM] 런타임 데이터 영역(Run-time Data Area)  (0) 2020.02.01

+ Recent posts