<개요>


우리가 많이 들어본 콘웨이 법칙이다.

- 조직/조직구조의 커뮤니케이션 구조가 소프트웨어의 구조를 결정한다.

우리가 일반적으로 사용하고 있는 MVC구조도 사실 여기에서 영향을 받았다. (UI - 벡엔드 - 데이터)  그래서 비지니스 적으로는 응집력이 낮기 때문에 다른 여러가지 시도가 이루어지고 있는 것이다.


 
한편 역 콘웨이 법칙도 존재한다.


- 소프트웨어 아키텍처 구조가 회사 조직구조를 결정한다.
그래서 만들고 싶은 소프트웨어의 방향에 따라서 조직구성을 인위적으로 하는 것이다. 그리고 그 구성을 매우 유연하게 자주 바꿀 수 있도록 한다.
 


콘웨이법칙은 워낙 개발자들 사이에서 진리를 통하는 법칙인지라 다양한 인용과 해석이 존재하는데 재미있는 표현 몇가지를 찾아봤다.

- 하나의 컴파일러를 만들기 위해서 4개의 팀이 조직된다면, 4단계로 빌드하는 컴파일러가 나오게 된다.

- N명의 그룹이 코볼컴파일러를 구현한다면 N-1단계가 될것이다. (왜냐하면 한명은 관리자가 되어야 하기 때문에)

- 영웅개발자가 만든 소프트웨어는 기발할지 모르지만 에러도 무지 많다.

- 시스템 설계를 자유롭게 하고 싶다면 조직역시 변화에 대비해야 한다.

조직의 구조때문에 만들 수 없다고 여기고 있는 더 나은 설계가 존재하는가

 

<Self 적용>

요즘은 이런 생각을 한다.
MSA라는 구조가 매우 일반적이되었는데 이것은 특정 소프트웨어 조직의 커뮤니케이션 구조라기보다는 이 세상의 일반적인 동작방식과 유사하다는 생각이 들었다. 

- 아주 예전에 작성했던 글
https://icthuman.tistory.com/entry/IT%EC%8B%9C%EC%8A%A4%ED%85%9C%EA%B3%BC-%ED%98%84%EC%8B%A4%EC%84%B8%EA%B3%84%EC%9D%98-%EA%B4%80%EA%B3%84

 

IT시스템과 현실세계의 관계

IT시스템의 구현은 현실세계와 밀접한 관련이 있다고 생각합니다.짧지않은 기간동안 공부하고 경험한바를 바탕으로 마구 써내려가봅니다. =======================================================================

icthuman.tistory.com

- Rebecca Wirfs-Brock 선생님 께서 Nature of Order 를 SW에 비교하여 설명하신 내용
(Nature of Order 라는 자연적 질서에 대해 서술한 책이 있는데 매우 재미있다.)
https://www.youtube.com/watch?v=NZ5mI6-tNUc

Design Matters - Rebecca wirffs-brock

 

1. Message, Event

개인을 각각의 서비스로 상상해보면 일반적인 의사소통을 하면 전달하는방식 (언어, 글쓰기 등)은 유사하지만 그 안에 담기는 내용은 다르며, 같은 메시지도 해석하여 동작하는 방식이 다르다.
 유사한 내용을 다루어 본 사람끼리 더 잘 통하고 이해하며, 내가 잘 모르는 내용에 대해서는 반만 듣고 반은 버린다.
때로는 상대의 공격적인 언행에 대해서는 그냥 내가 필터링을 하거나 한귀로 듣고 한귀로 흘려버리기도 한다.
 
또  어느날 어떤 사람이 컨디션이 평상시와 다르면 말을 더 많이 하거나, 혹은 말을 하지 않기도 하며
가끔은 누군가 괜찮은지 안부인사를 묻기도 한다.
 
2. Service
개인을 각 서비스로 또 상상해보자.
서비스가 동작하는 것은 누군가에게 어떠한 기능을 제공해주기 위해서 존재하기도 하지만 스스로의 목적을 추구하기도 한다.
누군가를 위해서 어떤 일을 해주기도 하고 혹은 누군가가 해주는 어떤 일을 받기도 해서 내가 원하는 바를 달성한다.
혼자서 모든일 을 처리할 수 없기 때문에 다양한 타인/조직, 물건,생물등 소통하는 것은 당연한 것이다.
 
3. Role
맡고 있는 역할과 책임이 존재한다. 
누군가의 스승, 누군가의 동료, 누군가의 배우자. 
각각의 역할에는 기대하는 바가 있고 그 기대하는 바가 적절히 충족되어야 상대방이 만족한다.
만약 그 기대하는 바가 완벽하게 충족되지 않는다면 스스로 방법을 찾아보거나 부족한 부분을 대신할 수 있는 다른 것을 찾게 된다.
 
4. Error / Fault / Failure
Error는 언제나 발생한다. 늦잠을 잘 수도 있고, 과식을 할 수도 있고, 물건을 놓칠 수도 있다.
Error가 가끔은 Fault로 연결된다. 지각을 하기도 하고, 소화가 안되고, 물건이 땅에 떨어진다. (놓쳐다가 잡으면 안떨어진거니까..)
그리고 이것들이 누적되면 Failure로 연결되며 우리는 타격을 입게 된다.
성적을 망친다던지, 앓아눕던지, 소중한 물건을 망가뜨리거나..
 
실수를 막기 위해서 시계알람을 울리거나, 미끄럼 방지스티커를 부착하는 것 같이 작은 안전장치가 큰 불이익을 막을 수 있다.
 
5. Waiting
전자제품이 고장났다. ->  AS센터에 전화를 한다. -> 전화를 안받는다. 받을 때까지 전화를 건다. ->
전화를 받았다. -> 담당AS기사를 확인해보고 10분뒤에 전화를 준다고 한다. -> 10분동안 전화를 기다린다. -> 전화가 다시 온다.
글로만 읽어도 답답하고 에너지소모가 많다.
 
요즘은
전자제품이 고장났다 -> App을 통해서 접수한다.  -> 내 할일 한다.
잠시 후 담당AS기사 방문일정이 알람으로 온다. -> 확인하고 다시 내 할일 한다.
 
물론 매우 중요한 일은 여전히 기다려야 한다. e.g) 인증, 결제 ARS
 

<정리>

우리의 이러한 생활을 관찰하다보면 Software Architecture에서의 해답점과 연결점을 많이 찾아낼 수 있다.

 

1. Protocol

- 주고 받는 메시지의 규약을 정하고, 그에 해당하는 메시지를 받았을 때 수행하는 동작을 서로 약속한다. (e.g HTTP, TCP/IP, Json 등)

- Health Check를 통해서 각 노드/서비스의 상태를 주기적으로 확인한다.


 
2. Component / Sequence

- 최종 얻기 위한 결과물을 위해서 필요한 구성요소를 정의하고 (Component)

- 어떠한 순서로 요청하여 받은 결과물을 활용할 것인지 그려본다. (Sequence)

 

3. Exception Catch / Retry / Side-car

- 명시적으로 일어날 수 있는 오류에 대해서는 사전에 처리를 하고 예방한다.

- 일시적인 오류에 대해서는 다시 시도해보고

- 보다 큰 오류로 전파되는 것을 미리 막아둔다. (나만 죽는게 낫다)

 
4. Async / Non-blocking / Timeout

- 반드시 모든 작업이 동시에 끝나야 할 필요는 없다. (Async)

- 오래 기다려야 하는 작업(내가 제어할 수 없는 일) 은 맡겨놓고 다른 작업을 한다. (Non-blocking)

- 완료되었다는 것을 인지하면 그 때 후속작업을 수행한다. (callback, future)

- 동시에 끝나야 하는 작업이 있을 수도 있고, 응답을 반드시 확인해야 하는 경우도 있다. (Sync)

- 언제까지 끝난다고 장담할 수 있는 일은 없다. 적절한 시점에는 포기해야 한다. (unbounded delay, timeout)
 

<참조>

https://johngrib.github.io/wiki/Conway-s-law/ 
https://wiki.wooridle.net/NatureOfOrder

 

NatureOfOrder - Wooridle Wiki

ChristopherAlexander가 질서의 본질적인 특성 (Nature of Order)에 대해서 저술한 책. PatternLanguage에 대해서 정리한 그는, 패턴을 마구잡이로 적용하는 경우가 많은 것을 발견하고, 어떤 원리에 따라 구성

wiki.wooridle.net

 

<이전글>

https://icthuman.tistory.com/entry/AWS-Cognito-1-%EC%82%AC%EC%9A%A9%EC%9E%90%EA%B4%80%EB%A6%ACOAuth-20

 

AWS Cognito (1) - 사용자관리/OAuth 2.0

- 대부분의 서비스에서 공통적으로 필요한 기능이 사용자 관리/로그인 기능이다. 단순한 것 같지만 생각보다 많은 리소스가 필요한 시스템이다. (특히 권한연계까지 들어갈 경우) - 기존에는 자

icthuman.tistory.com

 

<개요>

- 가장 많이 사용되는 Authorization Code Grant방식을 이용해서 JWT Token 을 발급받는다.

- 토큰으로부터 claims를 얻고 유효한지 검증한다.

- 기존방식 토큰과의 호환성을 검토한다. (JWT / JWS, Secret Key / RSA)

 

<내용>

1. Authorization Code Request

- Cognito의 설정을 마쳤으니 호스팅UI를 통해서 로그인을 시도한다.

- 반드시 다음값을 파라미터로 넘겨주어야 한다.

 a. client_id : Cognito에서 생성한 클라이언트 ID

 b. response_type : code

 c. scope : resource에 접근할 수 있는 범위

 d. redirect_uri : authorization_code를 받아서 리다이렉트할 주소

 

2. Authorization Code Redirect ( redirect_uri )

- 해당 코드값을 통해서 /oauth2/token에 접근한다.

- access_token, id_token 등이 발급되면 응답을 처리한다.

- 코드로 살펴보면 다음과 같다.

CognitoTokenRequest cognitoTokenRequest = cognitoTokenRequestFactory.newCognitoTokenRequest(code);

        Mono<String> ret = webClient
                .post()
                .uri("/oauth2/token")
                .headers(h -> h.setBasicAuth(cognitoTokenRequest.getBasicAuth()))
                .contentType(MediaType.APPLICATION_FORM_URLENCODED)
                .body(BodyInserters.fromObject(cognitoTokenRequest.getMap()))
                .retrieve()
                .onStatus(HttpStatus::is4xxClientError, clientResponse -> Mono.error(new RuntimeException(clientResponse.bodyToMono(String.class).block())))
                .bodyToMono(String.class)
                .onErrorReturn(null);
        ;

        CognitoTokenResponse cognitoTokenResponse = null;
        String temp = ret.block(Duration.ofMillis(cognitoTokenApiTimeout));
        try {
            cognitoTokenResponse = objectMapper.readValue(temp , CognitoTokenResponse.class );
        } catch (JsonProcessingException e) {
            log.error("error cognito's response is not valid", e.getLocalizedMessage());
        }
        return CompletableFuture.completedFuture(cognitoTokenResponse);

- 일반적으로 MediaType.APPLICATION_FORM_URLENCODED 방식으로 호출할 경우 MultiValueMap 을 사용하게 되는데 해당 값을 FirstCollection 형태로 처리하고 Factory를 통해서 만들도록 하여 소스를 간결하게 처리했다.

- 또한 /oauth2/token에 접근하기 위해서는 basicAuth로 인증을 해야 하는데 이 때 필요한 Secret값은 다음과 같은 형태로 생성한다.

Base64.getEncoder().encodeToString( (clientId+":" + clientSecret).getBytes() );

- Response는 다음과 같은 형태로 확인할 수 있다.

{
"access_token":
    "id_token":
    "tokenType":
    "expiresIn":
}

- 이 때도 주의해야 하는 점이 있는데 AWS Cognito의 응답구조는 cameCase가 아니라 snakeCase로 넘어온다

ObjectMapper objectMapper = new ObjectMapper().setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);

- Mono,CompletableFuture에 대해 부연설명을 하자면 Spring WebFlux에서는 비동기/논블로킹을 일반적인 처리형태로 가져간다.

 다만 이번 소스처럼 일부 동기화 구간이 필요할 경우 block() 을 사용하게 되고 이때 별도의 쓰레드로 대기하도록 ComletableFuture를 활용하여 @Async 처리를 하는 것이 좋다. 과거글 참조

https://icthuman.tistory.com/entry/Spring-WebClient-%EC%82%AC%EC%9A%A9-2-MVC-WebClient-%EA%B5%AC%EC%A1%B0

 

Spring WebClient 사용 #2 (MVC + WebClient 구조)

- Spring 이후 버전에서는 RestTemplate가 deprecated될 예정이며 WebClient 사용을 권장하고 있다. - 현재 구성 중인 시스템에는 동기/비동기 API가 혼재되어 있으면서, 다양한 Application / DB를 사용중이기 때

icthuman.tistory.com

 

3. JWT Token 

- JWT Token은 {header}.{body}.{signature} 의 형태로 구성되어 있으며 Cognito에서 응답받은 accessToken토큰의 내용을 확인해보면 다음과 같다.

- Header부분에 해당 토큰을 생성할 때 사용된 알고리즘과 공개키(비대칭)방식일 경우 kid가 포함된다.

  이를 통해서 서명을 검증할 수 있으며 공개키는 발급자가 제공하는 uri에서 확인할 수 있고, 일반적으로 다음과 같다.

   {issuer_uri}/.well-known/jwks.json

 

- 알고리즘의 대칭 / 비대칭 알고리즘에 대해서는 따로 설명해야 할 만큼 긴데 단순히 요약하면

 a. 대칭 알고리즘 : 서로 같은 키를 사용

 b. 비대칭 알고리즘 : 서로 다른 키를 사용

  e.g) 개인키로 암호화하여 서명을 첨부하고, 공개키를 통해서 서명을 검증할 수 있다.

 

4. JWT Token Encode / Decode (Java)

- io.jsonwebtoken 의 DefaultJwtParser를 살펴보면 세부로직을 좀더 잘 파악할 수 있다.

-  기본 내부에서 사용하던 토큰은 이와 같은 방식으로 되어 있으며 secretKey를 사용하는 대칭 알고리즘 방식이었다.

Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(refreshToken);

(변수)  = claims.getBody().get("userId");
...
...

그러다 보니 같은 방식으로는 Cogtino의 토큰을 처리할 수 없어서 일단 다음과 같은 방식으로 변경하였다.

Jwt<Header, Claims> preclaims = Jwts.parser().parseClaimsJwt(getUnsignedTokenFromOriginalToken(token));


private static String getUnsignedTokenFromOriginalToken(String token){
        String[] splitToken = token.split("\\.");
        String unsignedToken = splitToken[0] + "." + splitToken[1] + ".";
        return unsignedToken;
} // {[0]}.{[1]}.

- 이와 같이 {signature} 부분을 제외하고 body부분을 읽어올 수 있으며 Header에 대한 정보도 같이 처리가 가능하다.

 다만 누군가 토큰을 위조할 수 있는 가능성이 있어서 아래 Verifier에서 약간 보완을 해보았다.

 

5. JWT / JWS / JWE / JWK

- JWT Token에 대해서 살펴보면 관련된 용어들이 자주 나오는데 간단히 살펴보면 다음과 같다.

 

 A. JWT : 인증을 위한 일반적인 메커니즘. JWS 나 JWE 로 구현된다. (implements)

 [header].[payload].[signature]

 

 B. JWS(JSON Web Signature)

 Claim의 내용이 노출되고 디지털 서명을 하는 방식이다. (Client 가 Claim을 사용하기 위해서 일반적으로 JWS를 사용한다.)

 다음 Signature 에서 일반적으로 사용되는 알고리즘이다.

  • HMAC using SHA-256 or SHA-512 hash algorithms (HS256, HS512)
  • RSA using SHA-256 or SHA-512 hash algorithms (RS256, RS512)

 

C. JWE(JSON Web Encryption)

Claim 자체를 암호화시키는 방식

"header":
{
    "alg" : "RSA-OAEP",                --------------------> For content encryption 
    "enc" : "A256GCM"                  --------------------> For content encryption algorithm
},
 "encrypted_key" : "qtF60gW8O8cXKiYyDsBPX8OL0GQfhOxwGWUmYtHOds7FJWTNoSFnv5E6A_Bgn_2W"
"iv" : "HRhA5nn8HLsvYf8F-BzQew",       --------------------> initialization vector
"ciphertext" : "ai5j5Kk43skqPLwR0Cu1ZIyWOTUpLFKCN5cuZzxHdp0eXQjYLGpj8jYvU8yTu9rwZQeN9EY0_81hQHXEzMQgfCsRm0HXjcEwXInywYcVLUls8Yik",
"tag" : "thh69dp0Pz73kycQ"             --------------------> Authentication tag
}

 

D. JWK(JSON Web Key)

private key로 만들어진 signature 를 검증하기 위해서 public key를 제공하는 구조이다.

{
"alg":"RSA",

"mod": "0vx7agoebGcQSuuPiLJXZptN9nndrQmbXEps2aiAFbWhM78LhWx4cbbfAAtVT86zwu1RK7aPFFxuhDR1L6tSoc_BJECPebWKRXjBZCiFV4n3oknjhMs
tn64tZ_2W-5JsGY4Hc5n9yBXArwl93lqt7_RN5w6Cf0h4QyQ5v-65YGjQR0_FDW2QvzqY368QQMicAtaSqzs8KJZgnYb9c7d0zgdAZHzu6qMQvRL5hajrn1n91CbOpbI
SD08qNLyrdkt-bFTWhAI4vMQFh6WeZu0fM4lFd2NcRwr3XPksINHaQ-G_xBniIqbw0Ls1jF44-csFCur-kEgU8awapJzKnqDKgw",

"exp":"AQAB",

"kid":"2011-04-29"
}

 

 

6. Customize Token Verifier

private LoginUserDetails makeUserDetailsFromToken(String token) throws TimeoutException, ExecutionException, InterruptedException {
        Jwt<Header, Claims> preclaims = Jwts.parser().parseClaimsJwt(getUnsignedTokenFromOriginalToken(token));
        String iss = (String)preclaims.getBody().get("iss");

        if(StringUtils.hasText(iss) && iss.startsWith("https://cognito-idp.ap-northeast-2.amazonaws.com")){  // 1. Cognito Token

            CognitoUserInfoResponse cognitoUserInfoResponse = cognitoService.getAuthInfoFromAccessToken(token).get(3, TimeUnit.SECONDS);
            if(cognitoUserInfoResponse== null || cognitoUserInfoResponse.getSub().isEmpty()){
                return null;
            }
            Jwt<Header, Claims> claims = Jwts.parser().parseClaimsJwt(getUnsignedTokenFromOriginalToken(token));
        	...
            Collection<GrantedAuthority> authorityList = new ArrayList<>();
            List<String> cognitoGroups = (List<String>)claims.getBody().get("cognito:groups");
            if(null != cognitoGroups) {
                for (String group : cognitoGroups) {
                    authorityList.add(new SimpleGrantedAuthority(group));
                }
            }
            return new LoginUserDetails(......, authorityList);
        }else{                                                                                                // 2. Legacy Token
            Jws<Claims> claims = Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token);
            ...
            Collection<GrantedAuthority> authorityList = new ArrayList<>();
            List<String> authorities = (List<String>)claims.getBody().get("roles");
            if(null != authorities) {
                for (String auth : authorities) {
                    authorityList.add(new SimpleGrantedAuthority(auth));
                }
            }
            return new LoginUserDetails(..., authorityList);
        }
    }

 

 A. 발급자가 Cognito라면

 -- 발급된 토큰이 정상인지 확인요청을 한다. (/oauth/userInfo)

   => 이부분은 사실 원격요청을 하지 않고 제공되는 공개키, pem을 통해서 로컬에서 검증이 가능하지만 현재 java로는 직접 구현해야 하는 부분이 많고, 자주는 아니지만 공개키는 변경될 수 있기 때문에 키값을 확인하기 위해서는 결국 원격요청이 필요해서 쉽게 가기로 했다. 

 -- "cognito:groups" 정보를 이용해서 권한을 세팅한다.

 B. 기존 토큰이라면

 - secretKey를 이용해서 검증하고

 - "roles" 정보를 이용해서 권한을 세팅한다.

 

<정리>

이렇게 만들면 기존의 JWT Token(HS256) 와 신규 Cognito Token (RSA256) 를 모두 소화할 수 있다.

그러나 만들어 놓고보니 소스가 지저분하다. ResourceServer에서 권한을 확인하여 API접근제어를 해야하는데 좋은 구조는 아니다.

  - 로직이 복잡하고(구조 특성상 해당모듈을 그대로 copy해가는 방식이 될텐데 유지보수성이...영), secretKey 공유의 문제도 있다.

  - 꼭 두 가지 토큰을 소화할 필요가 있을까?

  - 기존 방식은 외부 사용자가 고려되지 않은 방식이고, OAuth 2.0은 출발자체가 3rd Party연계를 간편하게 하기 위한 방법이다.

 

다양한 확장을 위해서 인증은 각각의 방식을 유지하고 서비스를 사용자에 따라서 분리하는 것이 해결책이라는 판단을 내렸다.

- 내부 사용자가 이용하며 Client, Resource Server가 같은 Boundary에 있는 레거시의 경우 기존 토큰을 사용하고

- 외부 사용자가 이용해야 하는 API 서비스는 별도로 분리 구성하며, 이 서비스에서는 OAuth 2.0만을 활용하여 인가/인증을 하도록 한다.

  (이부분은 AWS API Gateway내에서 자격증명으로 Cognito를 연계하는 방식과 시너지를 낼 수 있을 것 같다.)

 

- 이를 위해서 보다 쉽게 Resource Server를 세팅하는 법을 Spring Security에서 제공하고 있는데 다음글에서 좀 더 자세히 적용해보고 서비스를 어떻게 분리하는 것이 보다 효과적일지 살펴본다.

 

<참조>

https://aws.amazon.com/ko/premiumsupport/knowledge-center/decode-verify-cognito-json-token/

 

Cognito JSON 웹 토큰의 서명 디코딩 및 확인

Amazon Cognito JSON 웹 토큰의 서명을 디코딩 및 확인하려면 어떻게 해야 합니까? 최종 업데이트 날짜: 2022년 9월 6일 Amazon Cognito 사용자 풀을 애플리케이션의 인증 방법으로 사용하고 싶습니다. 클라

aws.amazon.com

https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-verifying-a-jwt.html

 

Verifying a JSON web token - Amazon Cognito

Amazon Cognito might rotate signing keys in your user pool. As a best practice, cache public keys in your app, using the kid as a cache key, and refresh the cache periodically. Compare the kid in the tokens that your app receives to your cache. If you rece

docs.aws.amazon.com

https://www.loginradius.com/blog/engineering/guest-post/what-are-jwt-jws-jwe-jwk-jwa/

 

What are JWT, JWS, JWE, JWK, and JWA? | LoginRadius Blog

Learn about the JOSE framework and its specifications, including JSON Web Token (JWT), JSON Web Signature (JWS), JSON Web Encryption (JWE), JSON Web Key (JWK), and JSON Web Algorithms (JWA). For easier reference, bookmark this article.

www.loginradius.com

 

<개요>

- 대부분의 서비스에서 공통적으로 필요한 기능이 사용자 관리/로그인 기능이다. 단순한 것 같지만 생각보다 많은 리소스가 필요한 시스템이다. (특히 권한연계까지 들어갈 경우)

- 기존에는 자체적으로 사용자 관리 시스템을 운영하고 있었다. (Password Grant방식)

 그 이유는 특별한 사용자의 정보를 담지 않고 있으며 인증, Resource Server, Client가 모두 같은 System Boundary에 포함되어 있었기 때문에 가장 간편한 방식으로 개발하였다.

- 이제부터는 관리기능을 확장시키고 추가 정보를 담아야 하는 요건이 생겼으며, 이를 위해서 사용자 인증에 관련된 부분을 별도 시스템으로 분리하여 구성하는 것이 좋다고 판단하였다.

- AWS Cognito의 경우 외부연동, MFA인증등 편리한 기능을 기본적으로 가지고 있으며 JWT Token, IAM 연동도 가능한 것으로 보여서 가능성을 검토해본다.

 a. 기존 시스템을 유지하면서 개선할 것인지 (Spring Boot / Security / JWT Token)

 b. 새로 개발할 것인지 (e.g AWS Cognito)

 c. 혹은 a,b를 혼합하여 가져갈 수 있을지 (커스터마이징의 범위)

 

<내용>

1. 사용자 풀

사용자 풀

- 사용자를 생성하고 그룹을 할당할 수 있다.

- 이메일 확인 및 비밀번호 초기화 등의 기능을 기본으로 제공한다.

- 그룹은 개별적으로 추가 가능하다.

- AWS Cognito user pool은 기본적으로 대소문자를 구분하지 않도록 되어있다. (권장사항)

  만약에 대소문자를 구별하고 싶다면 Make user name case sensitive 옵션을 활성화 해야한다.

https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-case-sensitivity.html

 

User pool case sensitivity - Amazon Cognito

User pool case sensitivity Amazon Cognito user pools that you create in the AWS Management Console are case insensitive by default. When a user pool is case insensitive, user@example.com and User@example.com refer to the same user. When user names in a use

docs.aws.amazon.com

 

2. 연동자격증명 공급자

- 우리가 SNS연동을 하기 위해서는 일반적으로 App을 생성하고 할당받은 Client ID, Secret을 이용하여 API호출 및 연계개발을 하게 되는데 이부분이 쉽게 설정으로 가능하도록 되어있다. (많이 사용하는 Google / Apple / Facebook)

- 그 외에도 표준 OIDC를 준수한다면 쉽게 추가하고 속성을 매핑할 수 있는 구조로 되어있다.

자격 증명 공급자 : Google

 

3. SMS / SNS

-  스타트업에서는 내부에 문자 / 이메일 발송기능이 없는 경우도 많은데 AWS SES / SNS 등과 쉽게 연동하여 활용이 가능하다.

- 단, SMS는 현재 도쿄 리전에서만 사용이 가능하며 샌드박스 내에서는 등록된 번호로만 발신이 가능하다.

 

4. App 구성

- AWS Cognito도 결국 우리가 만든 서비스와 연결되기 위해서 여러가지 요소가 필요한데 이에 대한 부분도 제공하고 있다.

- 도메인 : Cognito 도메인 or 사용자 지정도메인 가능

- 리소스 서버 구성 (OAuth 2.0)

- 호스팅 UI, 앱 클라이언트

 

5. OAuth 2.0 

- 앱 유형을 선택하기 전에 OAuth 2.0에 대한 기본적인 이해도가 있으면 도움이 된다.

https://icthuman.tistory.com/entry/OAuth-20-Flow

 

OAuth 2.0 Flow

1. Authorization Code - 권한 부여 승인을 위해서 자체생성한 Authorization Code를 전달하는 방식 - 기본이 되는 방식 - Refresh Token 사용이 가능 2. Client Credentials - 클라이언트의 자격증명만으로 Access Token을

icthuman.tistory.com

 

6. 앱 클라이언트

- 앱 클아이언트 부분을 좀 더 자세히 살펴보면 다음과 같다.

- 클라이언트 보안키 생성유무 선택에 따라서 추후 토큰발급 엔드포인트를 호출할 때 방법에 차이가 있다.

- 세션, 토큰( Id/Access/Refresh )별 만료시간을 선택할 수 있으며 Revoke Token에 대해서 선택사항으로 정할 수 있다.

- 사용자를 찾을 수 없다고 응답을 하면 해킹에 취약해질 수 있기 때문에 최근 로그인 시스템에서는 이름 또는 암호가 잘못되었다고 묶어서 표현하고 있다.

 

6. 호스팅 UI

- 자체적으로 로그인 UI 를 제공해준다.

- 주의할 점은 사용자 풀에서 연동자격증명을 선택하였다면 여기서도 선택을 해주어야 UI 상에서 버튼이 활성화 된다.

- 해당 호스팅UI는 내가 등록한 App에서만 사용하는 것이다. 따라서 호출할 때 다음 파라미터들을 전달해주어야 정상적으로 동작한다.

- client_id, response_type, scope, redirect_uri

 

<정리>

- AWS Cognito에서 User Pool을 생성하고 Identity Providers를 추가한 뒤 호스팅 UI 를 통해서 로그인/가입까지 완료했다.

- 다음 글에서는 OAuth 2.0 기본 Flow를 따라서 Cognito 사용자 풀과 호스팅 UI를 연동하여 Authorization Code를 발급받고

- 이를 기반으로 엔드포인트에 접근하여 Token발급, UserInfo 조회하는 기능을 살펴본다.

 

<참고>

- https://aws.amazon.com/ko/cognito/details/

 

기능 | Amazon Cognito | Amazon Web Services(AWS)

 

aws.amazon.com

- https://docs.aws.amazon.com/ko_kr/cognito/latest/developerguide/what-is-amazon-cognito.html

 

Amazon Cognito란 무엇입니까? - Amazon Cognito

Amazon Cognito란 무엇입니까? Amazon Cognito는 웹 및 모바일 앱에 대한 인증, 권한 부여 및 사용자 관리를 제공합니다. 사용자는 사용자 이름과 암호를 사용하여 직접 로그인하거나 Facebook, Amazon, Google

docs.aws.amazon.com

 

1. Authorization Code

Authorization Code

- 권한 부여 승인을 위해서 자체생성한 Authorization Code를 전달하는 방식

- 기본이 되는 방식

- Refresh Token 사용이 가능

 

2. Client Credentials

- 클라이언트의 자격증명만으로 Access Token을 획득하는 방식

- 가장 간단한 방식

- 자격증명을 안전하게 보관할 수 있는 클라이언트에서만 사용되어야 함

- Refresh Token 사용 불가능

 

3. Implicit Grant

- 자격증명을 안전하게 저장하기 힘든 클라이언트에게 최적화된 방식

- Access Token이 바로 발급되기 때문에 만료기간을 짧게 설정할 필요가 있음

- Refresh Token 사용 불가능

 

4. Resource Owner Password Credentials Grant

- username, password로 Access Token을 받는 방식

- 클라이언트가 외부 프로그램일 경우 사용하면 안됨

- 권한서버, 리소스서버, 클라이언트가 모두 같은 시스템에 속해 있을때 사용해야 함 (대부분 비권장)

- 요청이 성공한 클라이언트는 메모리에서 자격증명을 폐기해야 함

- Refresh Token 사용 가능

 

<정리>

  Authorization Code Client Credentials Implicit Grant ROPC Grant
App 유형 SPA, Web App, Mobile Web Server Browser User / Client
권장사항 일반적     비권장
Refresh Token O X X O
Redirect Uri 필수 필수 권장 X

 

 

<참조>

- https://learn.microsoft.com/ko-kr/azure/active-directory/develop/v2-oauth2-auth-code-flow

- https://www.rfc-editor.org/rfc/rfc6749

<개요>

- Designing Data-Intensive Applications 를 읽고 그 중 분산시스템의 오류처리에 대한 부분 정리

https://icthuman.tistory.com/entry/The-Trouble-with-Distributed-Systems-1

 

<내용>

6. Timeouts and Unbounded Delays

- Timeout이 Fault를 감지하는 확실한 방법이라면 얼마로 설정해야 할까?

- Long timeout : Node가 죽었다는 것을 인지하기 위해서는 오래 기다려한다. (사용자는 기다리거나 에러메시지를 확인한다.)

- Short timeout : fault를 빠르게 감시할 수 있지만 잘못 인식할 있는 위험이 있다. (spike같이 일시적인 현상도 있기 때문에)

 

* 문제점

- 작업이 살아있고 수행하는 중이었는데 Node를 죽은 것으로 간주한다면, 작업이 종료되기 전에 다시 수행해서 중복 수행될 수 있다.

- 만약에 노드가 죽었다면 다른 노드에 이 사실을 전달해야하고 이것은 다른 노드나 네트워크에 추가적인 부하상황으로 이어질 수 있다.

  이미 시스템이 고부하상황이었고 노드가 죽었다고 잘못 판단할 경우 상황은 더 악화될 수 있다.

  특히 죽은 것이 아니라 overload로 인해서 응답이 지연되고 있었다면 (죽은게 아니었다면) 에러가 계속 전파되어서 모든 노드가 죽었다고 판단하면서 모든 작업이 멈춰버릴 수도 있는 극단의 상황도..

 

*아름다운 상상으로 접근 (fictitious system)

- 모든 패킷이 d 시간내에 전달된다고 하고, 살아있는 노드는 해당 request를 처리할때 r 시간내에 가능하다면 

- 모든 성공적인 request는 response time이 2d + r내로 들어올 것이고

- 해당 시간동안 응답을 받지 못한다면 network 나 node 가 동작하지 않는 것으로 간주할 수 있다.

- 그렇다면 2d + r 은 reasonable timeout 으로 사용할 수 있다.

 

*현실

- 불행하게도 대부분의 시스템은 이를 보장할 수가 없다.

- Asynchronous network 는 unbounded dealy를 가지고 있다.( 최대한 빨리 도착하도록 노력은 하지만.. upper limit이 존재하지 않는다는 점)

- 대부분의 서버 구현에서는 maximum time을 보장할 수가 없다. (Response time guarantees)

- Failure Detection을 위해서는 시스템이 빠르다는 것만으로는 충분하지 않다. Timeout이 너무 짧으면 위에서 살펴본것처럼 spike등이 발생하였을 때 system off-balance

 

* Network congestion and queueing

- 네트워크의 패킷 지연현상은 대부분 queueing 때문이다.

a. 여러 노드에서 동시에 한 곳으로 패킷을 보내면, 네트워크 스위치는 Queue에 채우고 Destination network link에 하나씩 넣어줘야 하는데, 패킷을 얻기 위해서 잠시 기다려야할 수도 있고 만약 Queue가 가득 차게되면 packet 이 drop되어서 다시 보내야한다.

b. 패킷이 Desination 머신에 도착했을 때 Cpu core가 모두 사용중이면 request처리준비를 할때까지 OS에서 queued된다.

c. 가상환경을 사용중이라면 OS가 종종 중지된다. 이 시간동안 VM은 network로부터 데이터를 소비할 수 없기 때문에 VM monitor에 의해서 queued (buffered) 된다.

d. TCP는 flow control을 수행하여 과부하를 방지하도록 속도를 제어하기도 한다. 또 TCP는 손실되는 패킷에 대해서 재전송을 해야하기 때문에 Delay를 두면서 timeout to expired 나 retransmitted packet을 기다린다.

(그래서 우리에게 이러한 기능이 필요없다면, 즉 유실방지,유량제어가 필요없고 지연된 데이터는 가치가 없는 상황이라면 UDP를 사용하는 것이 더 좋은 선택이 된다. 예를 들어서 VoIP call)

 

* 환경적인 문제

 Public Cloud 같이 여러 고객들이 같이 사용하는 네트워크 자원 (link, switch) ,각 NIC, CPU 등은 공유가 된다. 또 MapReduce같은 작업들은 병렬처리를 진행하면서 네트워크를 사용하기도 한다.

* 네트워크의 round trip시간의 분포를 적절하게 측정하여 예상되는 Delay 변동성을 결정하고,  Application의 특성을 고려하여 Failure detection delay  과  Risk of premature timeouts 간의 적절한 Trade Off를 결정할 수 있습니다.

* 더 좋은 방법은 상수값의 timeout보다는 Response time 과 Jitter를 지속적으로 측정하고 관찰된 응답시간의 분포에 따라서 Timeouts을 자동으로 조정하는 것입니다.

-  Phi Accrual failure detector (for example, Akka and Cassandra)

- TCP retransmission timeouts

<개요>

- Designing Data-Intensive Applications 를 읽고 그 중 분산시스템의 오류처리에 대한 부분 정리

- 언제나 늘 그렇듯이 새로운 개념이라기 보다는 얼마나 체계적으로 잘 정리해서 핵심을 간직하는가에 집중

 

<내용>

1. Faults and Partial Failures

- Single Computer에서 작업을 할 경우 same operation은 same result를 만들어낸다. (deterministic)

- 우리가 수 대의 컴퓨터에서 동작하는 소프트웨어를 개발할 때 (즉, 네트워크로 연결되어 있는 상태)는 이와는 다르다.

- 분산처리 시스템에서는 예측할 수 없니 특정 부분에서 문제가 발생하는 경우가 있는데 이를 partial failure 라고 부른다.

- partial failure의 어려운 점은 non-deterministic이라는 점이다. 여러 노드와 네트워크에 걸쳐서 무언가를 했을때 예상과는 다르게 성공하기도 하고 실패하기도 한다는 점인데 이러한 부분이 분산처리 시스템을 어렵게 만든다.

 

2. Building a Reliable System from Unreliable Components

- unreliable한 구성요소를 가지고 어떻게 하면 reliable한 시스템을 만들수 있을까? 몇 가지 아이디어를 소개한다.

a. error-correcting code를 가지고 어느부분이 오류가 발생했는지 체크하고 정확하게 전송할 수 있다.

b. TCP/IP : IP는 unreliable하다. 사라지거나 늦게 도착하거나 혹은 중복되거나.. 순서보장도 안된다. 여기에 TCP가 Transpory layer의 역할을 상위에서 해줌으로 사라진것은 다시 보내주고, 중복된것은 재거해주며, 발송순서에 맞게 재조립을 해준다.

물론 언제나 한계점은 존재한다. 데이터의 양이나, 혹은 네트워크 자체의 지연현상등은 커버하기 어렵다. 하지만 까다로운 일반적인 문제를 제거하고 나면 훨씬 쉬워지는것도 사실이다.

 

3. Unreliable Networks

- 분산처리에서는 shared-nothing구조를 유지하는 것이 일반적이다. 왜냐하면 다른 machine끼리는 네트워크로 연결이 되며 각자의 disk나 메모리등에는 접근할 수 없기 때문이다.

- 인터넷 환경에서 각 데이터 센터는 멀리 떨어져 있기 때문에 더욱 이러한 구조인데 대부분 비동기처리로 진행이 된다.(asynchronous packet networks)

- 메시지를 보내기는 하지만 언제 도착할지, 도착이 하기는 할지.. 보장할 수가 없다. 

 a. request가 사라질 수 있다.
 b. request가 늦게 도착할 수 있다.
 c. 원격 node가 죽을 수도 있고
 d. 원격 node가 잠깐 멈출수도 있고. (gc)
 e. 원격 node가 request처리를 했지만 response가 사라질수 있다.
 f. 원격 node가 request처리를 하고 reponse를 보냈지만 늦게 도착할수도 있다.
- 종합해 보면 메시지를 보낸쪽에서는 무엇이 문제인지 알수 있는 방법이 없다.

* 이러한 문제를 처리하는 방법이 일반적으로 "Timeout" 이다. 기다리는 것을 포기하는 것이다.

수신 node가 받았는지, 메시지가 사라졌는지는 여전히 알 수 없지만...

 

4. Network Faults in Practice

- 위에서 살펴본것처럼 reliable한 시스템을 만드는 법은 완벽한것은 없다. (왜? 네트워크는 여전히 불안하기 때문에..)

- 결국 소프트웨어에서 이를 처리할 수 있도록 해야한다. (이것을 지속적으로 테스트하도록 만든 프레임워크가 바로 Chaos Monkey)

 

5. Detecting Faults

- Faults를 감지해야 이후의 처리를 할수 있으니 감지하는 법을 살펴보자.

- 불확실성을 통해서 작동여부를 판단하는 것은 어려우니, 거꾸로 특정상황에서 작동하지 않는다는 것을 명시적으로 알려주는 FeedBack !

 a. 대상 포트에서 수신 프로세스가 없는 경우 OS에서 RST 또는 FIN을 전송한다.

 b. 노드 프로세스가 죽었지만 OS가 여전히 실행중이면 script로 다른 노드에 알릴 수 있다. (e.g Hbase)

 c. 데이터 센터에서 NIC 관리 기능을 사용중이면 하드웨어 수준으로 감지 할 수 있다.

 d. 라우터가 해당 IP에 연결할 수 없다고 확신하면 ICMP destination unreachable 패킷으로 응답할 수 있다. 

 

- 이렇게 빠르게 feedback처리를 하면 매우 유용함을 알 수 있다. 하지만 이것도 역시 신뢰할 수는 없다! (네트워크이니까)

- 결국 request가 성공적으로 처리되었는지는 application레벨에서의 positive response를 받는 것이 필요하다.

 

 

+ Recent posts