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

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

SourceMap

해당 404 에러의 원인을 알기 전에 다음의 개념을 알고 있어야 한다.

js나 css를 배포할 때는 공백 및 줄바꿈을 제거하고 난독화등을 하여 min.js로 배포를 하게 된다.
그런데 스크립트 에러가 생겼을 때 min.js로 디버깅하긴 어렵다.
그럴 때 사용할 수 있는 것이 SourceMapping이다.

bootstrap.min.js 파일을 보면 하단에 다음과 같은 코드가 있다.

    ...
    /# sourceMappingURL=bootstrap.min.js.map

sourceMapping은 Javascript및 Css의 min 파일과 원본파일을 연결해주어 디버깅 시 원본파일로 디버깅할 수 있도록 해준다.

404에러

로컬로 bootstrap.min.js를 복사하면서 js.map 파일은 복사해두지 않아서 발생했다.
크롬에서는 sourceMapping이 disabled 였는지 오류가 안났지만
mac에서 safari로 했을 때는 enabled였는지 404 오류가 나면서 페이지가 동작하지 않았다.

js.map을 복사해오면 되지만 외부 리소스 전체를 다 받아오기엔 수가 많아서 그냥 disabled하고 싶었다.
그런데 별다른 방법이 없는 것 같다.

    ...
    /# source MappingURL=bootstrap.min.js.map

일단은 이렇게 띄어쓰기로 무력화 해주어서 처리했다.

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


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

/usr/lib/systemd/system

OS 벤더사가 관리하는 영역으로 배포 저장소(Distribution repository)에서 다운로드되는 패키지로 제공되는 파일들이 위치한다.

/etc/systemd/system

시스템 관리자가 관리하는 영역으로 시스템 특정(System-specific)한 유닛들이 위치한다.

벤더사에서 제공하는 유닛들중 자신의 시스템에 맞게 수정하고 싶은 내용이 있을 경우 /etc/ssystemd/system에 유닛을 추가하면 해당 유닛을 오버라이드하게 된다. (/etc/ssystemd/system에 없는 유닛들은 벤더사에서 제공하는 기본 유닛을 사용한다.)

참고 레퍼런스

https://unix.stackexchange.com/questions/206315/whats-the-difference-between-usr-lib-systemd-system-and-etc-systemd-system

private 대신 protect로 설정하는 경우 상속으로 인해 메서드가 재작성 될 수 있음을 인지하고 그에 따른 장단점을를 염두에 두고 설계되어야 한다. 오버라이딩으로 인해 예기치 못한 오류가 발생할 수 있다. 

 

상속 받는 입장에서 protected로 지정하면 상속이 되는줄도 모르고 상속을 받는다. 오버라이딩하는 줄도 모르고 오버라이딩 될 수 있으므로 명시적으로(abstract를 사용하는 등) 추상적인 인터페이스를 제공하는 것이 좋다. 

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

[Java] Generics  (0) 2020.05.23
[Java] Primitive Type, Wrapper Class  (0) 2020.03.07
[Java] 불변(Immutable) 속성  (0) 2020.02.04
[Java] 자바 네티이브 인터페이스(JNI)  (0) 2020.02.01
[JVM] 런타임 데이터 영역(Run-time Data Area)  (0) 2020.02.01

/bin

기본 바이너리, 모든 유저가 사용 가능
ex) cat, ls, cp

/sbin

기본 바이너리인데 일반적인 유저가 사용하지 않는 것들로 root 권한이 필요하다.
ex) init, ip, mount

/usr/bin

유저가 선택적으로 설치하는 바이너리들
ex) gcc, perl

/usr/sbin

유저가 선택적으로 설치하는 바이너리 중에 root 권한이 필요한 것들
ex) nginx

/usr/local/bin

/usr/bin과 비슷하지만 Package manager로 설치한게 아닌 유저가 직접 컴파일한 바이너리들이 위치한다. 직접 컴파일한 것을 /usr/bin에 넣으면 추후 설치하는 스크립트 등에 의해 아무런 경고 없이 업그레이드 될 수 있으므로 그것을 피하기 위해선 /usr/local/bin에 넣어야 한다.

/usr/local/sbin

/usr/local/bin과 같지만 root 권한이 필요한 바이너리들이 위치한다.

출처

자세하게 설명된 포스트 : https://wookiist.tistory.com/10

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

[CentOS6] 최신 git 설치하기  (0) 2020.04.01
[CentOS7] /usr/lib/systemd/system, /etc/systemd/system 차이  (0) 2020.03.02
[CentOS6] openJDK8 설치  (0) 2019.01.18

+ Recent posts