개발자를 위한 인프라 기초 총정리



IT Infrastructure

개발자가 왜 인프라를 알아야 할까?

인프라 구성 요소


- 글 출처 : (링크)

'IT' 카테고리의 다른 글

기사 | '조직구조의 혁신으로 포털사이트의 1위가 된 네이버'  (0) 2019.03.13
기사분석 | '네이버 검색 시대가 끝나간다.'  (0) 2019.03.13
인프라 운영 직무  (0) 2019.03.01
OAuth  (0) 2019.02.27
OAuth 2.0  (0) 2019.02.20

기본적인 OAuth의 1.0과 2.0에 대한 정보

+) NAVER에서 사용하고 있는 내용



참고 문서(링크)

(1) NAVER D2 사이트 내의 OAuth에 대한 탄생과 사용 및 1.0과 2.0 소개

: https://d2.naver.com/helloworld/24942


(2) NAVER CLOUD PALTFORM 사이트 내의 OAuth 이용 가이드.

: https://docs.ncloud.com/ko/storage/storage-2-3.html



- 상세 내용

(1) 출처 : https://d2.naver.com/helloworld/24942

개요

네이버 클라우드 플랫폼과 네이버 클라우드 플랫폼 API를 정의하고, 네이버 클라우드 플랫폼 API 인증 방식인 OAuth 인증 방식을 소개합니다.

네이버 클라우드 플랫폼과 네이버 클라우드 플랫폼 API

네이버 클라우드 플랫폼은 NAVER의 최신 컴퓨팅 기술과 운영 노하우가 축적된 클라우드 서비스입니다. 네이버 클라우드 플랫폼에서 제공하는 여러 가지 상품군 중에 솔루션 상품을 이용할 수 있도록 제공하는 응용 프로그램 인터페이스(API)를 네이버 클라우드 플랫폼 API라고 합니다.

네이버 클라우드 플랫폼 API는 표준화된 OAuth 인증을 이용합니다. OAuth 인증 방식은 별도의 시스템이 로그인 정보를 저장하지 않도록 하고 암호화된 인증 토큰을 이용해야만 리소스에 접근할 수 있도록 허용하므로 안전합니다. 또한 다양한 플랫폼의 오픈 라이브러리(OAuth Client Library)를 사용할 수 있도록 지원합니다. 네이버 클라우드 플랫폼에서 제공하는 API는 이러한 OAuth 인증 방식의 통일된 체계를 통해 인증을 처리합니다.

OAuth 인증 방식

기존의 애플리케이션이나 웹 서비스에서는 아이디와 비밀번호를 사용하여 사용자 인증을 수행합니다. 이러한 인증 방식은 애플리케이션을 제작하거나 웹 서비스를 운영하는 회사마다 서로 다른 방법으로 사용자를 확인합니다. 하지만 표준화된 OAuth 인증 방식을 이용하면 해당 인증을 공유하는 애플리케이션이나 웹 서비스 사이에 별도의 인증이 필요 없어 개발자 입장에서는 더 간편하게 인증 절차를 구현할 수 있습니다.

3-legged OAuth 인증 방식

OAuth 인증 방식은 전형적으로 3 party system (User, Consumer, Service Provider) 형태로 고안되어 있습니다. 이러한 방식을 3-legged 인증 방식이라고 합니다.

  • User: Service Provider에 계정을 가진 사용자
  • Consumer: User를 대신하여 Service Provider에 접근하기 위해 OAuth를 사용하는 웹 서비스나 애플리케이션
  • Service Provider: OAuth를 통해 접근을 허용하는 웹 애플리케이션

2-legged OAuth 인증 방식

네이버 클라우드 플랫폼 API에서는 Consumer와 Service Provider가 동일하므로, 3-legged 인증 방식보다 간소화된 2-legged 인증 방식을 지원하고 있습니다.

즉, 3 party system 구조에서 Consumer가 생략되었다고 보면 됩니다. 2-legged OAuth 인증 방식의 흐름은 다음과 같습니다.

네이버 클라우드 플랫폼 API에서 사용하는 OAuth 버전은 OAuth 1.0a(http://tools.ietf.org/html/rfc5849)를 기반으로 합니다.

네이버 클라우드 플랫폼 API 인증 절차

OAuth 인증 방식을 적용해 네이버 클라우드 플랫폼 API를 사용하는 방법을 설명합니다.

네이버 클라우드 플랫폼 API 사용 절차

네이버 클라우드 플랫폼 API를 사용하려면 다음과 같은 과정을 거쳐야 합니다.

  • API 인증키 발급(생략 가능)
  • API URL 검증(생략 가능)
  • OAuth 인증 클라이언트 구현

API 인증키는 기본으로 발급되며 API URL 검증도 편의를 위해 제공되는 부분이므로 생략할 수 있습니다.

API 인증키 발급

네이버 클라우드 플랫폼 계정이 생성되면 기본적으로 네이버 클라우드 플랫폼 API 인증키가 하나 발급됩니다. 발급된 인증키는 네이버 클라우드 플랫폼 포털 웹사이트 로그인 후 마이페이지 > API 인증키 관리에서 확인할 수 있습니다.

인증키는 계정 생성 시 자동으로 발급되는 것 외에 사용자가 하나 더 생성할 수 있어서 두 개까지 발급받을 수 있습니다. 인증키를 사용 안 함으로 설정하거나 삭제하면 유효하지 않은 키로 인식됩니다.

API 인증키는 Access Key와 Secret Key 한 쌍으로 구성되어 있습니다. Access Key는 API를 인증할 때 파라미터로 직접 전달되며, Secret Key는 OAuth_signature 파라미터를 서명할 때 사용합니다.

API URL 검증

OAuth 인증 시 OAuth 파라미터를 전달하는 방식은 2가지입니다. HTTP 호출 시 GET 방식으로 URL 자체에 OAuth 인증에 필요한 파라미터를 붙여서 전달하는 방식과 HTTP 헤더에 넣어서 보내는 방식이 있습니다.

구분설명
목적API를 사용하기 위해 네이버 클라우드 플랫폼 사이트에서 얻어야 할 정보를 확인할 수 있습니다. 
API 인증키 값의 유효성을 확인할 수 있습니다. 
API 동작 여부와 결과를 확인할 수 있습니다.
UI 확인값URL 기능 동작 대상 URL(예: 컨테이너명/파일명) 
API URL (GET) 
http://restapi.fs.ncloud.com{URL}? 
&oauth_consumer_key={Access Key} 
&oauth_timestamp={timestamp} 
&oauth_signature={oauth signature} 
&oauth_nonce={nonce 값} 
&oauth_signature_method=HMAC-SHA1 
&oauth_version=1.0 
예) 
http://restapi.fs.ncloud.com/{컨테이너명}? 
{해당기능} 
&oauth_consumer_key={Access Key} 
&oauth_timestamp={timestamp} 
&oauth_signature={oauth signature} 
&oauth_nonce={nonce 값} 
&oauth_signature_method=HMAC-SHA1 
&oauth_version=1.0
입력값인증 Access Key 선택(예: xoiOTgN2sgjDb5oRzp8q).
인증 Secret Key(인증 Access Key 선택에 따라 자동으로 선택됨).
(예: SMSyGbEi7ucZpPnRGlomftWRNENz0TV1mMOI7MiA)
* 실제 API 사용 시 인증 Access Key와 인증 Secret Key를 사용해 인증 파라미터('oauth_'를 prefix로 사용하는 파라미터)를 생성하여 request에 추가하는데, URL 검증하기 기능을 통해 그 정보들의 구성 예를 확인할 수 있습니다.

서비스 서버에 적용되는 URL과 파라미터 관련 내용은 다음 절에서 설명합니다.

OAuth 인증 클라이언트 구현

네이버 클라우드 플랫폼 API를 사용하려면 아래 표에 명시된 URL과 필수 파라미터를 생성하여 요청해야 합니다.

구분필요 여부인자 설명
호출 URLY호출 대상 기능 URL. 형식은 http://restapi.fs.ncloud.com/{기능요청URL}?{파라미터}이다. 
예) http://restapi.fs.ncloud.com/{test}?list...
인증 파라미터YOAuth 인증을 위한 파라미터이며, 이 정보들은 header에 구성할 수 있습니다. 
oauth_consumer_key 
예) oauth_consumer_key=xoiOTgN2sgjDb5oRzp8q 
oauth_signature_method 
예) oauth_signature_method=HMAC-SHA1 
oauth_version 
예) oauth_version=1.0 
oauth_timestamp 
예) oauth_timestamp=1335419436 
oauth_nonce 
예) oauth_nonce=W4SkWT 
oauth_signature 
예) oauth_signature=qVsXa0Pf913BIhv55f06ont3aIE%3D

아래 3가지로 분류하여 소개합니다.

  • 인증 파라미터 생성 방법
  • 서명서(signature) 생성 절차 및 예제(자바)
  • 오픈 라이브러리를 이용한 인증 파라미터 생성

인증 파라미터 생성 방법

네이버 클라우드 플랫폼 API는 표준 OAuth를 지원하므로 다양한 오픈 Client Library를 사용하여 인증 파라미터를 생성할 수 있습니다.
생성된 인증 파라미터는 앞 절의 URL 예시처럼 HTTP Request에 파라미터로 추가하거나 헤더에 추가하여 사용할 수 있습니다.

구분설명
oauth_consumer_key마이페이지 > API 인증키 관리에서 생성한 Access Key 값. 
예) oauth_consumer_key=xoiOTgN2sgjDb5oRzp8q
oauth_signature_methodOAuth 서명 방식. 고정값으로 HMAC-SHA1를 사용합니다. 
예) oauth_signature_method=HMAC-SHA1
oauth_versionOAuth 버전. 고정값으로 1.0을 사용합니다. 
예) oauth_version=1.0
oauth_timestamptimestamp 값. 오픈 라이브러리를 사용할 수 있으며, 사용 예제는 "오픈 라이브러리를 이용한 인증 파라미터 생성“ 절을 참조합니다. 
예) oauth_timestamp=1335419436
oauth_nonce재전송 공격(replay attack)에 대한 예방으로 재사용 방지를 위한 일회성 파라미터입니다. 오픈 라이브러리를 사용할 수 있으며, 사용 예제는 "오픈 라이브러리를 이용한 인증 파라미터 생성“ 절을 참조합니다. 
예) oauth_nonce=W4SkWT
oauth_signature마이페이지 > API 인증키 관리에서 생성한 Secret Key를 사용한 서명이다(일회성). oauth_consumer_key와 쌍을 이룬 Secret Key여야 합니다. 
오픈 라이브러리를 사용할 수 있으며, 사용 예제는 "오픈 라이브러리를 이용한 인증 파라미터 생성" 절을 참고합니다. 
예) oauth_signature=qVsXa0Pf913BIhv55f06ont3aIE%3D

서명서(signature) 생성 절차 및 예제(자바)

서명서(signature)생성을 위한 절차를 기술합니다.

  • baseString 생성
  • signature 생성
  • 요청 URL 생성(queryString 방식 또는 Authorization Header 방식)

baseString 생성

  1. 요청 파라미터에 baseString 생성에 필요한 파라미터를 추가합니다.
  2. 요청 파라미터와 value 값을 알파벳순으로 정렬합니다.
  3. baseString = RequestMethod + '&' + oauthEncode(requestUrl) + '&' + oauthEncode(queryString)

java 예제)

  • 요청 파라미터에 baseString 생성에 필요한 파라미터를 추가합니다.
  • 요청 파라미터와 value 값을 알파벳순으로 정렬합니다.
  • 파라미터 넣는 부분은 API에서 받는 파라미터가 존재할 때만 넣습니다.

  • 예제 코드에서 사용자가 값을 변경해야 할 부분은 아래와 같습니다.

    • String consumerKey = "consumer-k1";
    • String consumerSecret = "consumer-secret1";
    • String requestUrl = " http://restapi.fs.ncloud.com/container/resource ";
    • String requestMethod = "GET";
    • requestParameters.put("list", null);
    • requestParameters.put("test_param1", Arrays.asList("a"));
    • requestParameters.put("test_param2", Arrays.asList("b2", "b1"));
    • requestParameters.put("test_param3", Arrays.asList("한글"));
    /**
     * signature base string을 만들기 위한 significantParameter 세팅
     * @param requestParameters
     * @param consumerKey
     * @return
     */
    private SortedMap<String, SortedSet<String>> getSignificantParametersForSignaturBaseString(Map<String, List<String>> requestParameters, String consumerKey) {
        SortedMap<String, SortedSet<String>> significantParameters = convertTypeToSortedMap(requestParameters);

        SortedSet<String> consumerKeySet = new TreeSet<String>();
        consumerKeySet.add(consumerKey);
        significantParameters.put("oauth_consumer_key", consumerKeySet);

        SortedSet<String> nonceSet = new TreeSet<String>();
        nonceSet.add(generateNonce());
        significantParameters.put("oauth_nonce", nonceSet);

        SortedSet<String> signatureMethodSet = new TreeSet<String>();
        signatureMethodSet.add("HMAC-SHA1");
        significantParameters.put("oauth_signature_method", signatureMethodSet);

        SortedSet<String> timestampSet = new TreeSet<String>();
        timestampSet.add(generateTimestamp());
        significantParameters.put("oauth_timestamp", timestampSet);

        SortedSet<String> versionSet = new TreeSet<String>();
        versionSet.add("1.0");
        significantParameters.put("oauth_version", versionSet);

        System.out.println("significantParameters : " + significantParameters);

        return significantParameters;
    }

    /**
     *
     * @param requestParameters
     * @return
     */
    private SortedMap<String, SortedSet<String>> convertTypeToSortedMap(Map<String, List<String>> requestParameters) {
        SortedMap<String, SortedSet<String>> significantParameters = new TreeMap<String, SortedSet<String>>();

        Iterator<String> parameterNames = requestParameters.keySet().iterator();
        while (parameterNames.hasNext()) {
            String parameterName = parameterNames.next();

            if (requestParameters.get(parameterName) == null || requestParameters.get(parameterName).size() == 0) {
                SortedSet<String> significantValues = new TreeSet<String>();
                significantValues.add("");
                significantParameters.put(parameterName, significantValues);
            } else {
                for (String parameterValue : requestParameters.get(parameterName)) {
                    if (parameterValue == null) {
                        parameterValue = "";
                    }

                    SortedSet<String> significantValues = significantParameters.get(parameterName);
                    if (significantValues == null) {
                        significantValues = new TreeSet<String>();
                        significantParameters.put(parameterName, significantValues);
                    }
                    significantValues.add(parameterValue);
                }
            }
        }
        return significantParameters;
    }

    private String generateTimestamp() {
        return Long.toString(System.currentTimeMillis() / 1000L);
    }

    private String generateNonce() {
        return Long.toString((new Random()).nextLong());
    }

a. baseString = RequestMethod + '&' + oauthEncode(requestUrl) + '&' + oauthEncode(queryString)

    /**
     * signature base string 생성을 위한 query string 생성
     * @param significantParameters
     * @return
     */
    private StringBuilder getQueryStringForBaseString(SortedMap<String, SortedSet<String>> significantParameters) {
        StringBuilder queryString = new StringBuilder();
        Iterator<Map.Entry<String, SortedSet<String>>> paramIt = significantParameters.entrySet().iterator();
        while (paramIt.hasNext()) {
            Map.Entry<String, SortedSet<String>> sortedParameter = paramIt.next();
            Iterator<String> valueIt = sortedParameter.getValue().iterator();
            while (valueIt.hasNext()) {
                String parameterValue = valueIt.next();
                queryString.append(OAuthCodec.oauthEncode(sortedParameter.getKey())).append('=').append(OAuthCodec.oauthEncode(parameterValue));
                if (paramIt.hasNext() || valueIt.hasNext()) {
                    queryString.append('&');
                }
            }
        }
        return queryString;
    }

    /**
     * signature base string 생성
     * @param requestMethod
     * @param requestUrl
     * @param significantParameters
     * @return
     */
    private String makeSignatureBaseString(String requestMethod, String requestUrl, SortedMap<String, SortedSet<String>> significantParameters) {
        StringBuilder queryString = getQueryStringForBaseString(significantParameters);

        requestUrl = normalizeUrl(requestUrl);
        requestUrl = OAuthCodec.oauthEncode(requestUrl);

        return new StringBuilder(requestMethod.toUpperCase()).append('&').append(requestUrl).append('&').append(OAuthCodec.oauthEncode(queryString.toString())).toString();
    }

    /**
     * @param url
     * @return
     */
    private String normalizeUrl(String url) {
        try {
            URL requestURL = new URL(url);
            StringBuilder normalized = new StringBuilder(requestURL.getProtocol().toLowerCase()).append("://").append(requestURL.getHost().toLowerCase());
            if ((requestURL.getPort() >= 0) && (requestURL.getPort() != requestURL.getDefaultPort())) {
                normalized.append(":").append(requestURL.getPort());
            }
            normalized.append(requestURL.getPath());
            return normalized.toString();
        } catch (MalformedURLException e) {
            throw new IllegalStateException("Illegal URL for calculating the OAuth signature.", e);
        }
    }

    class OAuthCodec extends URLCodec {

        private OAuthCodec() {
        }

        public static String oauthEncode(String value) {
            if (value == null)
                return "";
            try {
                return new String(URLCodec.encodeUrl(SAFE_CHARACTERS, value.getBytes("UTF-8")), "US-ASCII");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        }

        public static String oauthDecode(String value) throws DecoderException {
            if (value == null)
                return "";
            try {
                return new String(URLCodec.decodeUrl(value.getBytes("US-ASCII")), "UTF-8");
            } catch (UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }
        }

        protected static final BitSet SAFE_CHARACTERS;

        static {
            SAFE_CHARACTERS = (BitSet)URLCodec.WWW_FORM_URL.clone();
            SAFE_CHARACTERS.clear(42);
            SAFE_CHARACTERS.clear(32);
            SAFE_CHARACTERS.set(126);
        }
    }

signature 생성

Secret Key와 위에서 생성한 baseString을 가지고 HMAC SHA-1 해시알고리즘을 이용하여 서명서를 생성합니다.

java 예제)

    /**
     * base string과 consumer secret key를 가지고 signature 생성
     * @param signatureBaseString
     * @param consumerSecret
     * @return
     * @throws NoSuchAlgorithmException
     * @throws UnsupportedEncodingException
     * @throws InvalidKeyException
     */
    private String sign(String signatureBaseString, String consumerSecret) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeyException {
        Mac mac = Mac.getInstance("HmacSHA1");
        SecretKeySpec spec = new SecretKeySpec(new String(consumerSecret + "&").getBytes("UTF-8"), "HmacSHA1");
        mac.init(spec);
        byte text[] = signatureBaseString.getBytes("UTF-8");
        byte signatureBytes[] = mac.doFinal(text);
        signatureBytes = Base64.encodeBase64(signatureBytes);
        String signature = new String(signatureBytes, "UTF-8");
        System.out.println("signature : " + signature);
        return signature;
    }

요청 URL 생성(queryString 방식 또는 Authorization Header 방식)

java 예제)

  • queryString 방식
    /**
     * request URL 생성을 위한 query string 생성
     * @param significantParameters
     * @return
     */
    private StringBuilder getQueryStringForRequest(SortedMap<String, SortedSet<String>> significantParameters) {
        StringBuilder queryString = new StringBuilder();
        Iterator<Map.Entry<String, SortedSet<String>>> paramIt = significantParameters.entrySet().iterator();
        while (paramIt.hasNext()) {
            Map.Entry<String, SortedSet<String>> sortedParameter = paramIt.next();
            Iterator<String> valueIt = sortedParameter.getValue().iterator();
            while (valueIt.hasNext()) {
                String parameterValue = valueIt.next();
                if (StringUtils.isEmpty(parameterValue)) {
                    queryString.append(OAuthCodec.oauthEncode(sortedParameter.getKey()));
                } else {
                    queryString.append(OAuthCodec.oauthEncode(sortedParameter.getKey())).append('=').append(OAuthCodec.oauthEncode(parameterValue));
                }

                if (paramIt.hasNext() || valueIt.hasNext()) {
                    queryString.append('&');
                }
            }
        }
        return queryString;
    }

    /**
     * request URL 생성
     * @param requestUrl
     * @param significantParameters
     * @param signature
     * @return
     * @throws UnsupportedEncodingException
     */
    private String makeRequestUrl(String requestUrl, SortedMap<String, SortedSet<String>> significantParameters, String signature) throws UnsupportedEncodingException {
        StringBuilder queryString = getQueryStringForRequest(significantParameters);
        queryString.append('&').append("oauth_signature").append("=").append(URLEncoder.encode(signature, "UTF-8"));
        return new StringBuffer(requestUrl).append("?").append(queryString.toString()).toString();
    }

    /**
     * queryString 방식 요청 수행
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    public void request() throws IOException, NoSuchAlgorithmException, InvalidKeyException {

        String consumerKey = "consumer-k1";
        String consumerSecret = "consumer-secret1";
        String requestUrl = " http://restapi.fs.ncloud.com/container/resource";
        String requestMethod = "GET";
        Map<String, List<String>> requestParameters = new HashMap<String, List<String>>();
        requestParameters.put("list", null);
        requestParameters.put("test_param1", Arrays.asList("a"));
        requestParameters.put("test_param2", Arrays.asList("b2", "b1"));
        requestParameters.put("test_param3", Arrays.asList("한글"));

        SortedMap<String, SortedSet<String>> significantParameters = getSignificantParametersForSignaturBaseString(requestParameters, consumerKey);
        String baseString = makeSignatureBaseString(requestMethod, requestUrl, significantParameters);
        String signature = sign(baseString, consumerSecret);
        String signedUrl = makeRequestUrl(requestUrl, significantParameters, signature);
        System.out.println("signedUrl : " + signedUrl);

        URL obj = new URL(signedUrl);
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        con.setRequestMethod(requestMethod);

        int responseCode = con.getResponseCode();
        System.out.println("Response Code : " + responseCode);

        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();

        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();

        System.out.println(response.toString());
    }
  • Authorization Header 방식
    /**
     * @param significantParameters
     * @param signature
     * @return
     * @throws UnsupportedEncodingException
     */
    private String makeAuthorizationHeader(SortedMap<String, SortedSet<String>> significantParameters, String signature) throws UnsupportedEncodingException {
        StringBuilder authorizationHeaderString = new StringBuilder();

        Iterator<Map.Entry<String, SortedSet<String>>> paramIt = significantParameters.entrySet().iterator();
        while (paramIt.hasNext()) {
            Map.Entry<String, SortedSet<String>> sortedParameter = paramIt.next();
            int valueSize = sortedParameter.getValue().size();
            Iterator<String> valueIt = sortedParameter.getValue().iterator();

            if ("oauth_consumer_key".equals(sortedParameter.getKey())) {
                if (valueSize != 1) {
                    throw new IllegalArgumentException("oauth_consumer_key is empty or one more value.");
                }
                authorizationHeaderString.append("oauth_consumer_key").append('=').append('\"').append(valueIt.next()).append('\"');
                authorizationHeaderString.append(", ");
            }

            if ("oauth_nonce".equals(sortedParameter.getKey())) {
                if (valueSize != 1) {
                    throw new IllegalArgumentException("oauth_nonce is empty or one more value.");
                }
                authorizationHeaderString.append("oauth_nonce").append('=').append('\"').append(valueIt.next()).append('\"');
                authorizationHeaderString.append(", ");
            }

            if ("oauth_signature_method".equals(sortedParameter.getKey())) {
                if (valueSize != 1) {
                    throw new IllegalArgumentException("oauth_signature_method is empty or one more value.");
                }
                authorizationHeaderString.append("oauth_signature_method").append('=').append('\"').append(valueIt.next()).append('\"');
                authorizationHeaderString.append(", ");
            }

            if ("oauth_timestamp".equals(sortedParameter.getKey())) {
                if (valueSize != 1) {
                    throw new IllegalArgumentException("oauth_timestamp is empty or one more value.");
                }
                authorizationHeaderString.append("oauth_timestamp").append('=').append('\"').append(valueIt.next()).append('\"');
                authorizationHeaderString.append(", ");
            }

            if ("oauth_version".equals(sortedParameter.getKey())) {
                if (valueSize != 1) {
                    throw new IllegalArgumentException("oauth_version is empty or one more value.");
                }
                authorizationHeaderString.append("oauth_version").append('=').append('\"').append(valueIt.next()).append('\"');
                authorizationHeaderString.append(", ");
            }
        }

        return new StringBuffer().append("OAuth ").append(authorizationHeaderString.toString()).append("oauth_signature").append('=').append('\"').append(URLEncoder.encode(signature, "UTF-8")).append('\"').toString();
    }

    /**
     * Authorization Header 방식 요청 수행
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeyException
     */
    public void request2() throws IOException, NoSuchAlgorithmException, InvalidKeyException {
        String consumerKey = "consumer-k1";
        String consumerSecret = "consumer-secret1";
        String requestUrl = " http://restapi.fs.ncloud.com/container/resource";
        String requestMethod = "GET";
        Map<String, List<String>> requestParameters = new HashMap<String, List<String>>();
        requestParameters.put("list", null);
        requestParameters.put("test_param1", Arrays.asList("a"));
        requestParameters.put("test_param2", Arrays.asList("b2", "b1"));
        requestParameters.put("test_param3", Arrays.asList("한글"));

        SortedMap<String, SortedSet<String>> significantParameters = getSignificantParametersForSignaturBaseString(requestParameters, consumerKey);
        String baseString = makeSignatureBaseString(requestMethod, requestUrl, significantParameters);
        String signature = sign(baseString, consumerSecret);

        StringBuilder queryString = getQueryStringForRequest(convertTypeToSortedMap(requestParameters));
        requestUrl += "?" + queryString.toString();
        System.out.println("requestUrl : " + requestUrl);

        String authorizationHeaderValue = makeAuthorizationHeader(significantParameters, signature);
        System.out.println("authorizationHeaderValue : " + authorizationHeaderValue);

        URL obj = new URL(requestUrl);
        HttpURLConnection con = (HttpURLConnection) obj.openConnection();
        con.setRequestMethod(requestMethod);
        con.setRequestProperty("Authorization", authorizationHeaderValue);

        int responseCode = con.getResponseCode();
        System.out.println("response Code : " + responseCode);

        BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()));
        String inputLine;
        StringBuffer response = new StringBuffer();

        while ((inputLine = in.readLine()) != null) {
            response.append(inputLine);
        }
        in.close();

        System.out.println(response.toString());
    }

오픈 라이브러리를 이용한 인증 파라미터 생성

인증 파라미터를 생성할 때 오픈 라이브러리를 활용할 수 있습니다. 
이 예제에서는 signpost-core 라이브러리를 사용합니다. 
이 라이브러리를 사용하게 되면 헤더에 인증 파라미터가 붙어서 전달됩니다.

signpost-core 라이브러리를 직접 다운로드하거나, Maven을 사용한다면 pom.xml에 아래와 같이 설정합니다.

<dependency>
   <groupId>oauth.signpost</groupId>
   <artifactId>signpost-core</artifactId>
   <version>1.2</version>
   <scope>compile</scope>
</dependency>

다음의 예제 코드를 참고하여 OAuth 인증 파라미터를 포함한 HTTP 요청을 생성합니다.

  • 예제 코드에서 사용자가 값을 변경해야 할 부분은 아래와 같습니다.
    • String consumerKey = "consumer-k1";
    • String consumerSecret = "consumer-secret1";
    • String requestUrl = " http://restapi.fs.ncloud.com/container/resource";
    • String requestMethod = "GET";
    • requestParameters.put("list", null);
    • requestParameters.put("test_param1", "a");
    • requestParameters.put("test_param2", Arrays.asList("b2", "b1"));
    /**
     * 라이브러리 요청 수행
     * @throws IOException
     * @throws OAuthMessageSignerException
     * @throws OAuthExpectationFailedException
     * @throws OAuthCommunicationException
     */
    public void request() throws IOException, OAuthMessageSignerException, OAuthExpectationFailedException, OAuthCommunicationException {

        String consumerKey = "consumer-k1";
        String consumerSecret = "consumer-secret1";
        String requestUrl = " http://restapi.fs.ncloud.com/container/resource";
        String requestMethod = "GET";

        Map<String, Object> requestParameters = new HashMap<String, Object>();
        requestParameters.put("list", null);
        requestParameters.put("test_param1", "a");
        requestParameters.put("test_param2", Arrays.asList("b2", "b1"));

        StringBuffer queryString = new StringBuffer();
        int index= 1;
        int requestParamSize = requestParameters.size();
        for (String key : requestParameters.keySet()) {
            if (index == 1) {
                queryString.append("?");
            }
            queryString.append(key);

            if (requestParameters.get(key) != null) {
                queryString.append('=');
                queryString.append(URLEncoder.encode(requestParameters.get(key).toString(), "UTF-8"));
            }
            if (index!= requestParamSize) {
                queryString.append("&");
                index ++;
            }
        }

        OAuthConsumer consumer = new DefaultOAuthConsumer(consumerKey, consumerSecret);

        URL url = new URL(requestUrl + queryString.toString());
        HttpURLConnection con = (HttpURLConnection)url.openConnection();

        con.setRequestMethod(requestMethod);
        con.setConnectTimeout(500000);
        con.setUseCaches(false);
        con.setDefaultUseCaches(false);

        consumer.sign(con);

        con.connect();

        int responseCode = con.getResponseCode();
        System.out.println("ResponseCode : " + responseCode);

        if (responseCode == HttpURLConnection.HTTP_OK) {
            BufferedReader resultReader = new BufferedReader(new InputStreamReader(con.getInputStream(), "utf-8"));
            StringBuffer requestResult = new StringBuffer();
            String readString = null;
            while ((readString = resultReader.readLine()) != null) {
                requestResult.append(readString);
            }

            System.out.println(requestResult.toString());
        }
    }

부록. 약어/용어

약어 정리

OAuth     Open standard for Authorization

HMAC     Hash-based Message Authentication Code

SHA     Secure Hash Algorithm

연관 정보 바로가기

아래 가이드에서 연관 정보를 확인할 수 있습니다.


[ISTQB 자격증 공부에 도움이 되는 사이트 정리]


- ISTQB® CTFL V.2018 Syllabus 한글판 ISTQB® CTFL Ver.2018 샘플문제 한글판 (링크)

2019년 2월 26일 특별시험부터는 ver.2018를 바탕으로 시험문제가 출제됩니다. 

ISTQB® Foundation Level이 2018 버전으로 새롭게 업데이트되었습니다.

 

2019년 2월 26일 특별시험 부터 진행되는 ISTQB CTFL 자격시험은 ver.2018로 시행됩니다.


실라버스와 샘플문제가 한글본과 영어본으로 첨부돼 있으니 ISTQB CTFL 자격증 취득을 목표로 하시는 분들은 ver.2018을 확인하시고 자료 다운받으시기 바랍니다.


감사합니다.


- 테스팅 용어사전 (링크)

NTS에서 테스팅 직무로 일하고 있는 대학동기 승원이의 권유로, 

테스팅 직무 취업에 도움이 된다는 ISTQB자격증 취득을 위한 공부를 시작하려 한다. 

* 오늘은 2019.02.03.일요일로, 내일모레(화)요일이 구정이라 이미 설연휴가 시작되었다.

자꾸 어깨랑 허리가 안좋아지는 엄마 도와서 장도 보고 전도 부치고 만두도 만들어야 하고

내일은 오전에 친할머니와 할아버지 뵈러 파주에 있는 산소에 가고, 내일모레는 주안에 있는 외가댁으로 간다.

그러니 틈틈히 가톨릭대 열람실 찾아와서 공부해야 한다!


시험응시는 2019.02.26에 있는 ISTQB CTFL 특별시험 (실라버스ver.2018,한글)을 하려하고,

접수기간은 2019.02.11~15이라 잊지않고 접수할 것이다.


시험응시료가 많이 비싼데, 꼭 취득해서 2019 상반기 취업 때, 테스트직무로도 지원해 볼 예정이다.

지현아, 넌 할 수 있어.

*

내가 준 사람은

눈에 남아

눈물이 되어 나온다.


내가 받은 사람은

가슴에 남아

나오지 못하고

아픔이 된다.



*

사랑을 내세워

내가 어떤 짓을 저지른 건지

마지막이 돼서야 알게 된다.



*

연애 내내 끌려다니다가

헤어지자고 말한 것도 억울한데

답장까지 기다리면서 끌려다니는 그 기분이

얼마나 더럽고 거지 같고

비참하고 서러운지 알아요?

_ 아, 뭐라고 하면 되는데요!

고마웠다고! ...!!

그거 어려운 거 아니잖아요. 어려운 거 아니잖아요... 



*

너 변해가고 있어.

이대로 가면 우린 헤어질거야.

그 사람에게 알릴 방법은 이별밖에 없었다.

우린 겨울이 되서야

지난여름이 그립다.


출처)

YOUTUBE | EP.5 헤어진 여자를 공략해야 하는 이유 [웹드라마 짧은대본] "시영"편



0123




현대 오토에버 2019 상반기 신입사원 수시채용 글(링크)을 보다가

'ICT - 어플리케이션 개발/운영'의 모집분야에 SAP CO 모듈 개발/운영이라는 업무를 담당하는 직무를 모집한다기에 SAP CO 모듈이라는 것에 생소한 나는 궁금한 건 바로바로 찾아봐야 하기에, 검색해봤다.

그 중 이해하기 쉽고, 기억하고 싶었던 글의 내용을 발췌해왔다. 내 스토리에도 간직하여 두고두고 읽고 싶어서.


하단의 글 출처 - https://krksap.tistory.com/307


--------------------------------------------------


SAP CO에 대해 알아보자 - 제0편 Prologue



1.SAP CO란?

CO는 Controlling에서 온 말입니다. 한국말로 번역하면 '관리회계'라는 뜻이죠. 위에 짤에도 써있듯이 이번에 살펴볼 공식 교재인 TFIN20_1의 교재 이름도 'Management Accounting I'이라고 되어 있어요. 말 그대로 '관리회계'입니다.


그럼 '관리회계'는 무엇을 하는 걸까요?


관리회계는 SAP의 Core중 Core인 것 같아요. '의사결정'을 내리기 위한 레포트 작성이 '관리회계'입니다. 그리고 이런 '레포트'를 작성하기 위한 데이터를 체계적으로 분류하고 관리하기 위한 모듈이 CO라고 할 수 있습니다.


여기에서 말하는 레포트는 '원가 분석', '생산 관리', '매출 이익률 분석' 등등 여러가지 회사의 사업이나 활동 등이 회사가 돈을 얼마나 썼고 얼마나 벌었는지 이런 정보를 분석한게 레포트라고 할 수 있겠지요.



CO에 대해서 안다는 것은 아래 3가지를 알고 있으면 CO에 대해 알고 있다고 할 수 있습니다.

1.CO 마스터 데이터의 생김새와 활용 방법

2.기업 활동에 대한 데이터의 흐름

3.결산


이렇게 3가지 인데 포함하는 범위가 너무 넓어서 한번에 알기는 힘들 것 같네요.


그러면 다음편부터 하나씩 알아보도록 할게요.


SAP CO에 대해 알아보자 - 제1편 Organization Unit




이번 포스트는 제목과 마찬가지로 CO를 구성하는 Organization Unit(조직 구성 요소)에 대해 알아볼거에요. SAP FI, SD, CO 등 대부분의 모듈의 교재(공식교재 포함)에서 가장 처음에 나오는게 'Organization Unit'이에요.


인간을 구성하는 요소인 인간의 장기(위장, 심장, 대장, 소장, 비장 등)도 Organization이라고 표현 하듯이 CO를 구성 하는 요소도 Organization이라고 표현을 해요.


인체의 장기 중에 '위장', '심장' 같은게 없으면 인간으로서 역할을 제대로 할 수가 없듯이 CO도 CO를 구성하는 Organization Unit들을 잘 설정 해주고 관계를 맺어 주어야 제 역할을 할 수 있습니다. 그렇기 때문에 각각의 요소들에 대해 하나씩 알아보는게 중요하겠지요?


그래서 제1장은 항상 Organization Unit인 것 같아요. 그러면 CO의 구성 요소들에 대해 하나씩 알아볼게요.




1.Overhead Cost(경상비)

CO를 시작하면 가장 먼저 접하는 개념이 'Overhead Cost'일거에요. Overhead Cost라는 말을 살펴보면 'Over'라는 단어가 들어가 있어서 넘친다거나 '부담'을 준다는 느낌이 들어요. 그리고 'Cost'는 '비용'이지요.


회계는 궁극적으로 얼마를 썼는지(비용)와 얼마를 벌었는지(수익)를 돈으로 나타낸거라고 할 수 있죠? 이를테면 아이폰 1대 만드는데 원가가 5만원인데 판매가는 80만원이라고 하면 비용은 5만원인거고 수익은 80만원 벌은거고 순수익은 75만원이고 이런 것들을 기록한 것이 회계이지요.


'Overhead'는 사용한다는 뜻이 있어요. IT쪽을 하다보면 'CPU 오버헤드를 준다, 오버헤드가 걸렸다'이런 말들을 들어볼 수 있는데 말 그대로 '사용'한다는 뜻이에요.


'OverheadeCost'(경상비)는 말 그대로 '기업이 항상 사용하는 비용'이라는 뜻이에요.


'비용'이라는건 돈을 쓰는거지요?


기업이 돈을 쓰는데가 어디겠어요? 물건 만들어 파는 기업인 자동차 회사 같은 경우는 재료 사오는 비용인 '원가 재료비', 자동차 신제품 출시하려면 연구 해야되지요. '연구비', 사람들 쓰는데 들어가는 비용 '인건비' 그리고 영업하는데 들어가는 영업소 유지 비용 같은 '판매 관리비', TV에 CF 광고 내보내는데 드는 비용인 '광고 홍보비' 이런거지요?


그런데 이런 돈은 자동차 회사가 자동차를 만들어내고 판매 하는데 '계속 들어가는 돈'입니다. 그래서 '경상비'라는 어려운 말은 살짝 풀어보면 '계속 들어가는 돈'입니다.


CO는 이러한 '경상비'를 관리(Controlling)하는 '관리회계' 모듈이에요. 다들 잘 알겠지만 '관리'는 뭐냐면 보통 우리들이 많이 쓰는 말 중에 '돈 관리', '시간 관리'라는 말이 있지요. '관리'는 '쓸때 쓰고 아낄건 아낀다'라는 뜻이에요. 시간관리는 '시간을 써야할 때 쓰고 아껴야 할 때 아낀다'이런 뜻입니다. '경상비 관리'는 '돈을 써야 할 곳에 쓰고 아껴야 할 곳에 아낀다'라는거겠지요.


'연구비'에 돈을 더 쓸지, '홍보비'에 돈을 더 쓸지 어디에 돈을 쓰는게 더 효율적인지를 관리하는게 관리회계 모듈인 CO가 하는 일이기 때문에 Overhead Cost(경상비)라는 말이 가장 먼저 나오는 거에요.




2.CO와 FI의 관계

인터넷을 검색해 보면 보통 CO는 '관리회계'이고 Internal Reporting 목적으로 사용하고, FI는 '재무회계'라고 하고 External한 목적으로 사용한다고나와요.


FI는 기업의 성적표인 '재무제표'를 작성하기 위한 모듈 이라고 보면 됩니다. 학점이 4.5 만점에 4.0인데 중급회계는 A+고 상법은 A0이고 교양과목은 B+가 나왔다는걸 보여주는게 FI에요.


CO는 4.0이라는 점수를 받기 위해서 내가 들인 노력과 시간 이를테면 중급회계 공부하는데는 1주일에 6시간, 상법을 공부하는데는 3시간 운동하는데는 2시간인데 '상법'을 공부 할 때는 학교에서 들은 수업 말고 인터넷 강의도 들었다고 하면 3시간 중에 인터넷 강의 들은 시간과 그에 따른 비용을 얼마 썼는지에 대한 내용이에요.


이런 것들을 잘 관리 해서 내가 인터넷 강의를 1시간 더 들을건지 학원을 다닐건지 혼자 2시간을 더 공부할건지 이런 것들을 결정해서 더 좋은 학점을 받기 위한 '전략'을 세우기 위한 모듈이 CO입니다.


[기본 위젯 - 텍스트 뷰의 속성]


* 텍스트 뷰

- text 

: 텍스트 뷰에 보이는 문자열을 설정할 수 있음.

- textColor 

: 텍스트 뷰에서 표시하는 문자열의 색상을 설정함.

: 색상 설정은 '#AARRGGBB' 포맷을 일반적으로 사용함(Alpha, Red, Green, Blue).

: 투명도를 나타내는 Alpha(색상만 표현할 때 : 'FF', 투명 : '00', 반투명 - '88').

- textSize 

: 텍스트 뷰에서 표시하는 문자열의 크기를 설정함.

: 'dp'나 'sp' 또는 'px' 등의 단위 값을 사용함.

- textStyle 

: 텍스트 뷰에서 표시하는 문자열의 스타일 속성을 설정함.

: 'normal', 'bold', 'italic' 등의 값을 지정할 수 있음.

- textFace 

: 텍스트 뷰에서 표시하는 문자열의 폰트를 설정함.

: 'normal', 'sans', 'serif', 'monospace'.

- maxLines="1" 

: 텍스트 뷰에서 표시하는 문자열이 한 줄로만 표시되도록 설정함.


cf. lineSpacingExtra 

: 텍스트 뷰에서 두줄 이상의 텍스트를 사용할 때, 줄 간격을 조정.

: 'dp'나 'sp' 또는 'px' 등의 단위 값을 사용함.


* 워라밸 / 복지 / 연봉 등 일하기 좋은 중소기업 565개사 리스트 (2019.01.23 기준)

참고사이트 : NAVER CAFE 'SPEC UP'(링크)


+ Recent posts