• 이미 사용 허가를 받아서 믿을 수 있는 사용자

1. ansible에서 authorized_keys 저장하는 순서

  1. ansible 서버에서 ansible 클라이언트에 접속 시도
  2. ansible 클라이언트는 ~/.ssh/authorized_keys 파일의 존재 확인
  3. authorized_keys 파일 내용에 ansible 서버의 정보가 저장되어있는 지 확인하고, 있다면 접속을 허가

2. ssh-keygen 명령어 사용

  • ssh-keygen 명령어를 통해 생성된 공개키(.pub)를 ansible 클라이언트에 전달하여 암호없이 접근 가능하게 해줌
  • authorized_keys에 저장하는 데 사용하는 명령어 ssh-keygen 옵션 설명
    1. -b 옵션 : 생성할 키의 비트수를 지정
    2. 2048 : rsa 키는 2048 비트로 결정 (rsa는 최소 768 비트가 필요)
    3. -t 옵션 : 암호화 타입을 결정
    4. rsa : 공개키 암호화에서 가장 널리 사용되는 알고리즘
    5. -f ~/.ssh/id_rsa : 저장할 파일명 지정
    6. -q 옵션 : ssh-keygen의 질의 응답을 생략
    7. -N "" : 새 암호를 빈값으로 해서 제공
       $ ssh-keygen -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N ""

3. 자동으로 authorized_keys를 저장해주는 플레이북(playbook)

  1. hosts: all → /etc/ansible/hosts에 저장되어있는 모든 IP 호스트들을 대상

  2. gather_facts: no → facts를 수집하지 않는다는 것 → Facts 는 원격 대상 시스템의 호스트 네임, CPU, Memory 정보 등을 수집하는 setup 모듈 (불필요한 시간 소모를 막아줌)

  3. connection=local → Ansible 이 ssh로 명령어를 날리지 않도록 하기 위한 것

  4. ignore_errors: yes → ansible 스크립트에서 실행한 결과가 성공인지 실패인지 출력하지 않고 실행만 시킴

  5. run_once: true → 명령어 1회만 실행됨

  6. register: id_pub → 플레이북에서의 결과를 id_pub 변수로 지정

  7. lineinfile → 이미 존재하는 파일에 내가 추가할 라인이 있는지 체크하고, 없는 경우에만 추가하고 싶은 경우에 lineinfile module을 사용 (ansible의 멱등성 지원)

  8. dest → id_pub가 저장되는 위치인 /root/.ssh/authorized_keys임

  9. id_pub.stdout → /root/.ssh/authorized_keys에 id_rsa.pub을 저장

    $ vi auto_authorized_keys.yml
    ---
    - hosts: all
     gather_facts: no
    
     tasks:
     - name: ssh-keygen
       connection: local
       command: "ssh-keygen -b 2048 -t rsa -f ~/.ssh/id_rsa -q -N ''"
       ignore_errors: yes
       run_once: true
    
     - name: read id_rsa.pub
       connection: local
       command: "cat ~/.ssh/id_rsa.pub"
       register: id_pub
       run_once: true
    
     - name: remote lineinfile for authorized_keys
       lineinfile:
         dest: /root/.ssh/authorized_keys
         line: "{{ id_pub.stdout }}"

4. 플래이북 실행 결과

  • ansible 서버에서 플레이북 실행

    $ ansible-playbook auto_authorized_keys.yml -k
    SSH password:
    
    PLAY [all] *******************************************************************
    
    TASK [ssh-keygen] ************************************************************
    changed: [192.168.1.13]
    
    TASK [read id_rsa.pub] *********************************************************
    changed: [192.168.1.13]
    
    TASK [remote lineinfile for authorized_keys] ****************************************
    changed: [192.168.1.13]
    
    PLAY RECAP *******************************************************************
    192.168.1.13             : ok=3    changed=3    unreachable=0    failed=0    skipped=0    rescued=0                                                                                                ignored=0
    
    # -k 옵션 없이 ansible 서버에서 ansible 클라이언트 ansible 명령어 실행
    $ ansible all -m ping
    192.168.1.13 | SUCCESS => {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": false,
        "ping": "pong"
    }
  • ansible 클라이언트인 192.168.1.13에서 /root/.ssh/authorized_keys 파일 확인
    $ cat /root/.ssh/ssh
    ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuIvAkdFC/ltBW3j1oMIzoVAvo0Gr0rWPuZ8e+YdMnXd3YMCZ0DTrqd+B9TW/C56rCU7oD7thMb4JgZiLoUim87xYmVStSmacCGLEpfU6vcLJN/s4iu+oH0aSf/rWCDqElGun8kU5SdyILFLG8dtR/hHy7mvZjmE5Fm9OzuXIo+6wWeXx7I7UHUwL4b1FXaQwlw72IjLMC0BEcJCqxbnw+N9GWtzxkPMog++8EQ0JrLsnhGPNGoH8viI6jzitJwDQsxFenHEmoMZPTZxr4z2Yzct45GiTa5BRIpiqx3Oq/2wFuqC67oJIMOl+v/0xxa4UXv1qmL42KaSlUbtzz2wHj

ansible의 플레이북(playbook)

  • 플레이북(playbook)의 원래 뜻은 각본, 작전, 계획
  • ansible에서 플레이북(playbook)은 대량의 서버에 설치 및 실행할 때의 순서를 정의
  • ansible 플레이북을 이용하여 대량의 서버에 nginx 설치하고 실행하는 순서
    1. nginx 설치
    2. 파일 전송
    3. nginx 재시작

1. ansible에서 멱등성

  • 연산을 여러 번 적용하다라도 결과가 달라지지 않는 성질

  • 멱등성이 없는 경우(단순 명령어)

    # 아래 명령어를 계속 반복하면 /etc/ansible/hosts 아래에 같은 구문이 계속 생김
    # 아래 명령어 실행할 때 마다 추가됨
    echo -e "[playbook]\\n192.168.1.13" >> /etc/ansible/hosts
  • 멱등성이 있는 경우 (playbook이용)

    $ vi playbook.yml
    ---
    - name: Ansible_vim
      host: localhost
    
      tasks:
        - name: Add ansible hosts
          blockinfile:
            path: /etc/ansible/hosts
            block: |
              [playbook]
              192.168.1.13

2. ansible에서 YAML

  • YAML은 마크업 언어가 아님을 표방해서 나왔지만 마크업 언어로 사용됨

  • 사용자가 쉽게 작성하고 이해할 수 있도록 작성

  • XML을 넘어 JSON과 유사 혹은 거의 동일

  • YAML 파일에 대한 설명

    1. name : 실행하는 playbook의 이름

    2. host : 실행되는 장소

    3. tasks : 실행할 작업들

    4. name : 작업에 대한 이름

    5. blockinfile : 모듈의 이름으로 특정 블록을 파일에 기록하는 역할을 함

    6. path : 저장되는 파일의 이름

    7. block : block을 기록하는 시작 포인터를 | 으로 사용

    8. 끝은 지정하지 않았으면 내용은 [playbook]\n192.168.1.13으로 기록

      $ vi playbook.yml
      ---
      - name: Ansible_vim
      host: localhost
      
      tasks:
        - name: Add ansible hosts
          blockinfile:
            path: /etc/ansible/hosts
            block: |
              [playbook]
              192.168.1.13



플레이북(playbook)을 이용하여 nginx 설치 및 실행

  • ansible 서버에 플레이북을 정의하여 ansible 클라이언트에 nginx 설치 및 실행을 시킴

1. nginx 설치하여 실행할 YAML 파일 생성

$ vi install_nginx.yml
---
- hosts: all
  remote_user: root
  tasks:
    - name: install epel-release
      yum: name=epel-release state=latest
    - name: install nginx web server
      yum: name=nginx state=present
    - name: Upload basic index.html for web server
      copy: src=index.html dest=/usr/share/nginx/html/ mode=0644
    - name: start nginx web server
      service: name=nginx state=started

2. ansible 서버에 nginx 기본 시작 페이지를 다운받음

  • 각 ansible 클라이언트의 기본 시작 페이지를 지정

    $ curl -o index.html <https://www.nginx.com>
      % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                     Dload  Upload   Total   Spent    Left  Speed
    100 97615    0 97615    0     0  31715      0 --:--:--  0:00:03 --:--:-- 31724
    
    $ ls
    index.html

3. 플레이북 실행

$ ansible-playbook install_nginx.yml -k
SSH password:

PLAY [all] *************************************************************************

TASK [Gathering Facts] **************************************************************
ok: [192.168.1.13]

TASK [install epel-release] *************************************************************
ok: [192.168.1.13]

TASK [install nginx web server] **********************************************************
changed: [192.168.1.13]

TASK [start nginx web server] ********************************************************
changed: [192.168.1.13]

PLAY RECAP ************************************************************************
192.168.1.13             : ok=5    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

4. ansible 클라이언트인 192.168.1.13에서 실행 결과 확인


  • 한번의 명령어로 다수의 시스템에 작업

1. uptime 확인

  • all : ansible 클라이언트의 모든 노드를 대상
  • -m 옵션 : 사용할 모듈을 지정하기 위한 옵션
  • shell : -m 옵션의 값으로 shell을 지정하면 shell 명령어를 사용 가능
  • -a 옵션 : argument의 약어 shell에서 사용할 명령어를 지정하는 옵션
  • "uptime" : -a 옵션의 값으로 모든 ansible 클라이언트에게 uptime 명령어를 실행하게 함
  • -k 옵션 : SSH 접속을 위한 암호 입력을 가능하게 함
    $ ansible all -m shell -a "uptime" -k
    SSH password:
    8.8.8.8 | CHANGED | rc=0 >>
     00:41:11 up 58 days, 36 min,  1 user,  load average: 0.00, 0.01, 0.05

2. 디스크 용량 확인

  • all : ansible 클라이언트의 모든 노드를 대상
  • -m 옵션 : 사용할 모듈을 지정하기 위한 옵션
  • shell : -m 옵션의 값으로 shell을 지정하면 shell 명령어를 사용 가능
  • -a 옵션 : argument의 약어 shell에서 사용할 명령어를 지정하는 옵션
  • "df -h" : -a 옵션의 값으로 모든 ansible 클라이언트에게 df -h명령어를 실행하게 함 → df 명령어에 -h 옵션을 붙이려면 ""(큰 따옴표)를 사용해야 함
    # 이미 ssh 접속에 성공했기 때문에 -k 옵션 사용 X
    $ ansible all -m shell -a "df -h"
    8.8.8.8 | CHANGED | rc=0 >>
    Filesystem      Size  Used Avail Use% Mounted on
    devtmpfs         63G     0   63G   0% /dev
    tmpfs            63G  4.0K   63G   1% /dev/shm
    tmpfs            63G  163M   63G   1% /run
    tmpfs            63G     0   63G   0% /sys/fs/cgroup
    /dev/sda2       243G  9.4G  221G   5% /
    /dev/sda1       477M  149M  299M  34% /boot
    /dev/sdb1       1.5T   77M  1.4T   1% /cache1
    tmpfs            13G     0   13G   0% /run/user/0

3. 메모리 상태 확인

  • all : ansible 클라이언트의 모든 노드를 대상
  • -m 옵션 : 사용할 모듈을 지정하기 위한 옵션
  • shell : -m 옵션의 값으로 shell을 지정하면 shell 명령어를 사용 가능
  • -a 옵션 : argument의 약어 shell에서 사용할 명령어를 지정하는 옵션
  • "free -h" : -a 옵션의 값으로 모든 ansible 클라이언트에게 free -h명령어를 실행하게 함 → free 명령어에 -h 옵션을 붙이려면 ""(큰 따옴표)를 사용해야 함
    $ ansible all -m shell -a "free -h"
    8.8.8.8 | CHANGED | rc=0 >>
                  total        used        free      shared  buff/cache   available
    Mem:           125G        5.2G        110G        162M        9.6G        119G
    Swap:           31G          0B         31G

4. 새로운 유저 생성

  • all : ansible 클라이언트의 모든 노드를 대상
  • -m 옵션 : 사용할 모듈을 지정하기 위한 옵션
  • user : -m 옵션의 값으로 user을 지정하면 linux의 user 생성
  • -a 옵션 : argument의 약어로 id와 password를 지정
  • "name=hippo password=1234" : 생성되는 user의 이름을 hippo로 하고, 비밀번호를 1234로 함 → ""(큰 따옴표)를 사용해야 id와 password를 같이 지정 가능
    $ ansible all -m user -a "name=hippo password=1234" -k
    SSH password:
    [WARNING]: The input password appears not to have been hashed. The 'password' argument must be encrypted for this module to work properly.
    8.8.8.8 | CHANGED => {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": true,
        "comment": "",
        "create_home": true,
        "group": 1000,
        "home": "/home/hippo",
        "name": "hippo",
        "password": "NOT_LOGGING_PASSWORD",
        "shell": "/bin/bash",
        "state": "present",
        "system": false,
        "uid": 1000
    }

※ 경고 발생 → [WARNING]: The input password appears not to have been hashed. The 'password' argument must be encrypted for this module to work properly.

  • password=1234로 하면 비밀번호가 암호화 되지 않아서 문제 발생
  • 1234의 암호화된 password가 저장되어야 ssh 접속할 때 1234로 로그인 가능

5. 파일 전송

  • 특정 파일을 모든 서버에 전달하는 방법
  • all : ansible 클라이언트의 모든 노드를 대상
  • -m 옵션 : 사용할 모듈을 지정하기 위한 옵션
  • copy 모듈 : ansible 클라이언트에게 파일을 전달하기 위해 사용하는 모듈
  • -a 옵션 : argument의 약어 copy에서 사용할 파일과 경로를 지정하는 옵션
  • src : 출발지를 의미 → /root/ 디렉토리에 있는 "sudo-1.9.5-3.el6.x86_64.rpm" 파일 전달
  • dest : 도착지를 의미 → ansible 클라이언트의 /root/ 디렉토리에 파일 저장
    ansible all -m copy -a "src=./sudo-1.9.5-3.el6.x86_64.rpm dest=/root/" -k
    SSH password:
    8.8.8.8 | CHANGED => {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": true,
        "checksum": "4fda41de00dddeb32445e1ea4d4dae1ba2c24d9a",
        "dest": "/root/sudo-1.9.5-3.el6.x86_64.rpm",
        "gid": 0,
        "group": "root",
        "md5sum": "801fda76d88f8c619a2a931f2e5e29d1",
        "mode": "0644",
        "owner": "root",
        "size": 2235944,
        "src": "/root/.ansible/tmp/ansible-tmp-1613809840.71-45665-17231553755106/source",
        "state": "file",
        "uid": 0
    }

6. 서비스 설치

  • lshw 설치
  • all : ansible 클라이언트의 모든 노드를 대상
  • -m 옵션 : 사용할 모듈을 지정하기 위한 옵션
  • yum 모듈 : ansible 클라이언트의 yum을 이용하여 yum 업데이트 및 패키지 설치
  • -a 옵션 : argument의 약어로 설치하거나 업데이트할 패키지를 지정하기 위해 옵션 사용
  • name : 패키지의 이름
  • state=present → 패키지를 설치
  • state : absent → 패키지를 삭제
    $ ansible all -m yum -a "name=lshw state=present" -k
    8.8.8.8 | CHANGED => {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": true,
        "changes": {
            "installed": [
                "lshw"
            ]
        },
        "msg": "",
        "rc": 0,
        "results": [
            "Loaded plugins: fastestmirror\\nLoading mirror speeds from cached hostfile\\n * base: mirror.kakao.com\\n * epel: mirror.krmir.org\\n * extras: mirror.kakao.com\\n * updates: mirror.kakao.com\\nResolving Dependencies\\n--> Running transaction check\\n---> Package lshw.x86_64 0:B.02.18-17.el7 will be installed\\n--> Finished Dependency Resolution\\n\\nDependencies Resolved\\n\\n================================================================================\\n Package        Arch             Version                   Repository      Size\\n================================================================================\\nInstalling:\\n lshw           x86_64           B.02.18-17.el7            base           324 k\\n\\nTransaction Summary\\n================================================================================\\nInstall  1 Package\\n\\nTotal download size: 324 k\\nInstalled size: 941 k\\nDownloading packages:\\nRunning transaction check\\nRunning transaction test\\nTransaction test succeeded\\nRunning transaction\\n  Installing : lshw-B.02.18-17.el7.x86_64                                   1/1 \\n  Verifying  : lshw-B.02.18-17.el7.x86_64                                   1/1 \\n\\nInstalled:\\n  lshw.x86_64 0:B.02.18-17.el7                                                  \\n\\nComplete!\\n"
        ]
    }

  • ansible 명령어에서 사용할 옵션들

1. -i (--inventory-file) → 적용될 호스트들에 대한 파일

  • 8.8.8.8 IP를 list 파일에 저장하면, 8.8.8.8에만 ansible 명령을 내림

    $ cd ~
    
    # 인벤토리 파일에 8.8.8.8 저장
    $ echo -e "8.8.8.8" > list
    $ cat list
    8.8.8.8
    
    # list 파일에 적힌 8.8.8.8에만 ping을 실행 (8.8.8.8은 테스트 IP)
    $ ansible all -i ~/list -m ping -k
    SSH password:
    8.8.8.8 | SUCCESS => {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": false,
        "ping": "pong"
    }

2. -m (--module-name) → 모듈을 선택

  • ping은 ping 명령어가 아니라 python 모듈
  • 이미 사용자가 만들어 놓은 모듈을 사용하던가, 아니면 스스로 직접 만들어서 사용 가능

3. -k (--ask-pass) → 패스워드를 물어보도록 설정

  • ssh 접속할 때 암호가 있어야 접속이 가능

  • 암호가 저장되어 있지 않기 때문에 -k 옵션을 사용하지 않는 경우 ssh 접근이 안되어서 fail 발생

    # -k 옵션 사용 X -> 암호가 없기 때문에 ssh 접속에 실패하여 명령어 실행 X
    $ ansible all -m ping
    8.8.8.8 | UNREACHABLE! => {
        "changed": false,
        "msg": "Failed to connect to the host via ssh: Permission denied (publickey,gssapi-keyex,gssapi-with-mic,password).",
        "unreachable": true
    }
    
    # -k 옵션 사용 -> 암호를 입력하여 ssh 접속 가능 (명령어 실행)
    $ ansible all -m ping -k
    SSH password:
    8.8.8.8 | SUCCESS => {
        "ansible_facts": {
            "discovered_interpreter_python": "/usr/bin/python"
        },
        "changed": false,
        "ping": "pong"
    }

4. -K (--ask-become-pass) → 관리자로 권한 상승

  • root 계정을 사용하는 경우 사용 X
  • 유저 계정을 사용하는 경우 특정 명령어는 관리자 권한 상승이 필요

5. --list-hosts → 적용되는 호스트들을 확인

  • 명령어를 실행하기 전에 적용받을 호스트들을 확인 가능

  • 그룹에 따라 달라짐으로 실수를 방지하기 위해 미리 확인 필요

    # all은 모든 호스트들을 의미 -> 전체 호스트 확인 가능
    $ ansible all -m ping --list-hosts
      hosts (1):
        8.8.8.8
    
    # nginx 그룹에 적용된 호스트들을 확인 가능
    $ ansible nginx -m ping --list-hosts
      hosts (1):
        8.8.8.8
    
    # list 파일에 있는 호스트만 확인 가능
    $ ansible all -i ~/list -m ping --list-hosts
      hosts (1):
        8.8.8.8

  • ansible은 대상 서버들에 agent 설치 필요 X
  • ansible의 host 서버에만 설치하면, 관리 받는 대상 서버에 아무런 프로세스가 생행하지 않아도 됨
  • YAML 파일로 관리 받는 대상에 명령어 전달

ansible 서버에 ansible 설치 (ansible core 설치)

  • Centos7에서 사용
  • ansible 서버 : ansible 테스트 서버
  • ansible 클라이언트에는 다른 ansible 설치 필요 X
    $ yum install -y epel-release
    $ yum install -y ansible
  • ansible 설치 확인
    $ ansible --version
    ansible 2.9.27
    config file = /etc/ansible/ansible.cfg
    configured module search path = [u'/home/hippo/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
    ansible python module location = /usr/lib/python2.7/site-packages/ansible
    executable location = /usr/bin/ansible
    python version = 2.7.5 (default, Nov 16 2020, 22:23:17) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]


ansible 클라이언트에 tcp wrapper 설정과 iptables 설정

  • ansible 서버에서 클라이언트에 SSH 접속이 필요 → ansible은 SSH 포트 사용
  • tcp wrapper과 iptables 설정 필요
  • tcp wrapper 설정
    $ vi /etc/hosts.allow
    #ansible test
    ALL: [ansible 테스트 서버 IP]
  • iptables 설정

    # -A INPUT -p tcp -m tcp --dport 22 -j DROP 구문 위에 적용해야 ssh 접근 허용
    $ vi /etc/sysconfig/iptables
    -A INPUT -s [ansible 테스트 서버 IP] -p tcp -m tcp --dport 22 -j ACCEPT
    -A INPUT -p tcp -m tcp --dport 22 -j DROP
    
    # iptables 재실행
    $ systemctl restart iptables

  • XMLHttpRequest(XHR) 객체는 서버와 상호작용하기 위하여 사용
  • 전체 페이지의 새로고침 없이도 URL로부터 데이터를 받아올 수 있음 → 전체 페이지와는 독립적으로(비동기적) 데이터를 주고 받을 수 있음
  • 웹 페이지 전체를 다시 로딩하지 않고 일부분만을 갱신 가능
  • XMLHttpRequest 객체는 AJAX(Asynchronos Javascript And XML) 프로그래밍에 주로 사용
  • XMLHttpRequest 객체는 서버로부터 XML 데이터를 전송받아 처리하는 데 사용
  • 요즘 XMLHttpRequest 객체는 XML 뿐 아니라, 모든 종류의 데이터를 받아오는데 사용 가능
  • 현재 대부분의 주요 웹 브라우저는 서버에 데이터를 요청하기 위한 XMLHttpRequest 객체를 내장함
  • HTTP 이외의 프로토콜도 지원 → file과 ftp 포함

XMLHttpRequest 객체의 생성

  • 자바스크립트를 이용하여 XMLHttpRequest 객체를 생성하는 방법
    var xmlHttp = new XMLHttpRequest();

readyState 프로퍼티

  • readyState 프로퍼티는 XMLHttpRequest 객체의 현재 상태를 나타냄
  • readyState 프로퍼티의 값은 객체의 현재 상태에 따라 다음과 같은 주기로 변화
    1. UNSENT (숫자 0) → XMLHttpRequest 객체가 생성됨.
    2. OPENED (숫자 1) → open() 메소드가 성공적으로 실행됨.
    3. HEADERS_RECEIVED (숫자 2) → 모든 요청에 대한 응답이 도착함.
    4. LOADING (숫자 3) → 요청한 데이터를 처리 중임.
    5. DONE (숫자 4) → 요청한 데이터의 처리가 완료되어 응답할 준비가 완료됨.

status 프로퍼티

  • status 프로퍼티는 서버의 문서 상태를 나타냄
    1. 200 → 서버에 문서가 존재함.
    2. 404 → 서버에 문서가 존재하지 않음.

XMLHttpRequest 객체의 전송

  • 자바스크립트를 이용하여 XMLHttpRequest 객체를 전송하는 방법

    var xmlHttp = new XMLHttpRequest();                   // XMLHttpRequest 객체를 생성함.
    xmlHttp.onreadystatechange = function() {           // onreadystatechange 이벤트 핸들러를 작성함.
    
        // 서버상에 문서가 존재하고 요청한 데이터의 처리가 완료되어 응답할 준비가 완료되었을 때
        if(this.status == 200 && this.readyState == this.DONE) {
    
             // 요청한 데이터를 문자열로 반환함.
            document.getElementById("text").innerHTML = xmlHttp.responseText;            
        }
    };
    xmlHttp.open("GET", "/examples/media/xml_httpxmlrequest_data.txt", true);
    xmlHttp.send();
  • XMLHttpRequest를 http를 이용한 예제
    • open이란 메소드를 이용해서 형식, url, 비동기여부(디폴트 : 비동기) 에 원하는 변수를 넣어줌
    • XMLHttpRequest.open(method, url[, async[, user[, password]]]) 형식
      1. 형식 타입 : "GET", "POST", "PUT", "DELETE"
      2. url : 요청 URL
      3. 비동기 여부 : true(비동기), false(동기), defalut(true)
      4. user : 암호화를 사용하는 경우 사용 default null
      5. password : 암호화를 사용하는 경우 사용 default null
        var xmlHttp = new XMLHttpRequest();
        xmlHttp.open("GET", "/examples/media/xml_httpxmlrequest_data.txt", true);
        xmlHttp.send();

responseText 프로퍼티

  • responseText 프로퍼티는 서버에 요청하여 응답으로 받은 데이터를 문자열로 저장
    document.getElementById("text").innerHTML = xmlHttp.responseText;

responseXML 프로퍼티

  • responseXML 프로퍼티는 서버에 요청하여 응답으로 받은 데이터를 XML DOM 객체로 저장

    xmlObj = xmlHttp.responseXML;                                    // 요청한 데이터를 XML DOM 객체로 반환함.
    nameList = xmlObj.getElementsByTagName("name");     // XML DOM 객체에서 요소이름이 "name"인 요소들을 선택함.
    result = "";
    
    for (idx = 0; idx < nameList.length; idx++) {
        // id가 "name"인 요소들의 텍스트 노드를 찾아 그 값을 반환함.
        result += nameList[idx].childNodes[0].nodeValue + "<br>";
    }
    
    document.getElementById("text").innerHTML = result;
  • 위의 예제에서 사용된 programming_languages.xml 파일의 코드 예시
    <?xml version="1.0" encoding="UTF-8"?>
    <programming_languages>
        <language>
            <name>HTML</name>
            <category>web</category>
            <developer>W3C</developer>
            <version status="working draft">5.1</version>
            <priority rating="1">high</priority>
        </language>
        <language>
            <name>CSS</name>
            <category>web</category>
            <developer>W3C</developer>
            <version status="stable">3.0</version>
            <priority rating="3">middle</priority>
        </language>
        <language>
            <name korean="자바">Java</name>
            <category>application</category>
            <developer>Oracle</developer>
            <version status="stable">8.91</version>
            <priority rating="2">high</priority>
        </language>
        <language>
            <name korean="파이썬">Python</name>
            <category>application</category>
            <developer>Python</developer>
            <version status="stable">3.52</version>
            <priority rating="4">middle</priority>
        </language>
    </programming_languages>

비동기식(asynchronous) 요청

  • 서버에 비동기식 요청을 보내기 위해서는 open() 메소드의 세 번째 인수로 true를 전달
  • 비동기식으로 요청을 보내면 자바스크립트는 서버로부터 응답을 기다리면서 동시에 다른 일을 할 수 있음
    xmlHttp.open("GET", "/media/programming_languages.xml", true);

참고 URL : http://www.tcpschool.com/xml/xml_dom_xmlHttpRequest


  • 데이터를 제공하는 파일 서버를 구축해야 하는 경우 AWS의 S3와 CloudFront 서비스를 이용하면 손쉽게 구축 가능 → 이미지의 경우 자주 사용
  • AWS S3와 CloudFront를 통해 Webapp을 만들 경우 CORS관련 문제가 발생 가능
  • 브라우저의 기본 기능을 통해 파일을 직접 다운로드를 하거나 DOM을 이용해 ( 테그나 CSS) 이미지를 표시하기 때문에 문제 발생 X
  • XHR을 통해 데이터를 받아서 사용하는 경우에는 Webapp이 제공되는 도메인과 CloudFront 서비스의 도메인이 다르기 때문에 CORS 문제 발생
  • AWS S3에서 CORS 관련 설정을 제공하지만, 브라우저와 CloudFront의 캐시 기능과 엮이면서 S3의 CORS 기능이 재대로 동작하지 않는 경우가 빈번
  • Unity3d를 이용해서 Facebook Canvas 웹게임을 만들 때 CORS 문제가 발생
  • Unity3d의 기본 HTTP Client인 UnityWebRequest를 이용해서 데이터를 받아오도록 짜면 Facebook Canvas 빌드의 경우 XHR을 사용하도록 컴파일
  • Facebook을 통해 서비스가 되는데, 받아와야 하는 데이터는 AWS S3에 있기 때문에 CORS 문제를 피할 수 없게됨
  • Unity3d쪽은 가능하면 수정하고 싶지 않아 서버쪽에서 CORS 지원하도록 수정

크롬 브라우저 캐시로 인한 CORS 에러

  • 크롬 브라우저의 캐시 정책으로 발생한 문제
  • 광고 이미지를 Facebook Page에 게시물로 올리고, Canvas 게임 안에서도 배너로 표시
  • 유저가 Facebook Page에 접근해서 이미지를 본 후 링크를 눌러 게임에 진입했을 경우에, Unity 게임 안에서 동일한 이미지를 XHR로 받으려고 하면 CORS 에러가 발생
  • 에러 발생과정
    1. 유저가 Facebook Page에서 이미지를 봄 → 이미지는 img 태그로 표시되기 때문에 HTTP 통신을 보면 이미지 요청과 응답에는 CORS 관련 헤더가 없음 (크롬 브라우저는 요청과 응답을 캐시함)
    2. Unity가 XHR로 같은 이미지를 요청 → 요청에는 CORS 관련 헤더를 포함됨
    3. 크롬 브라우저의 네트워크 담당 부분에서 1에서 캐시한 응답 결과 리턴 → 자바스크립트에서 응답된 결과에 CORS 관련 헤더가 없기 때문에 에러가 발생함. (캐싱된 이미지는 CORS관련 헤더가 없음)
  • 2의 요청과 1의 요청은 헤더가 다르고 결과도 다른 경우인데, 크롬 브라우저가 같은 결과가 나올 것을 기대하고 캐시를 사용하는 것이 문제의 원인

  • 매우 마이너한 경우이고, 브라우저의 동작을 명확히 규정하는 표준도 없기 때문에 발생한 경우임

  • 크롬 브라우저가 2의 요청에 1에서 캐시한 결과를 사용하지 않도록 Vary Header를 사용하면 문제 해결됨

  • CloudFront에 Lambda Edge를 추가해서 응답 결과에 아래와 같이 Vary header를 추가

    'use strict';
    // If the response lacks a Vary: header, fix it in a CloudFront Origin Response trigger.
    
    exports.handler = (event, context, callback) => {
        const response = event.Records[0].cf.response;
        const headers = response.headers;
    
        if (!headers['vary'])
        {
            headers['vary'] = [
                { key: 'Vary', value: 'Access-Control-Request-Headers' },
                { key: 'Vary', value: 'Access-Control-Request-Method' },
                { key: 'Vary', value: 'Origin' },
            ];
        }
        callback(null, response);
    };



CloudFront Origin별 캐시 오동작

  • CloudFront가 CORS 요청에 비CORS 응답을 캐시해서 발생

  • S3는 CORS 관련 헤더가 요청에 있을 경우에만 응답에 CORS 관련 헤더를 추가해줌

  • CloudFront도 단순 URL 뿐만 아니라 HTTP 헤더에 따라 다른 결과를 캐시 필요 → Origin 헤더에 따라 다른 결과를 캐시하는 기능을 제공

  • 처음에는 잘 동작하는 듯 했는데, 한 시간 정도 지나면 CORS 요청에 비CORS 응답이 캐시되는 경우가 빈번히 발생

  • 프로필 이미지나 정적 데이터를 제공할 뿐인 단순한 경우라서 CloudFront의 모든 응답에 CORS관련 헤더를 강제로 추가

    'use strict';
    // If the response lacks a Vary: header, fix it in a CloudFront Origin Response trigger.
    // If the response lacks CORS header, ...
    
    exports.handler = (event, context, callback) => {
        const response = event.Records[0].cf.response;
        const headers = response.headers;
    
        if (!headers['vary'])
        {
            headers['vary'] = [
                { key: 'Vary', value: 'Access-Control-Request-Headers' },
                { key: 'Vary', value: 'Access-Control-Request-Method' },
                { key: 'Vary', value: 'Origin' },
            ];
        }
        if (!headers['access-control-allow-origin'])
        {
            headers['access-control-allow-origin'] = [
                { key: 'Access-Control-Allow-Origin', value: '*' }
            ];
        }
        if (!headers['access-control-allow-methods'])
        {
            headers['access-control-allow-methods'] = [
                { key: 'Access-Control-Allow-Methods', value: 'GET' },
                { key: 'Access-Control-Allow-Methods', value: 'HEAD' }
            ];
        }
        if (!headers['access-control-expose-headers'])
        {
            headers['access-control-expose-headers'] = [
                { key: 'Access-Control-Expose-Headers', value: 'ETag' },
                { key: 'Access-Control-Expose-Headers', value: 'Last-Modified' }
            ];
        }
        callback(null, response);
    };

참고 URL : https://medium.com/bgpworks/aws-s3-cloudfront%EB%A1%9C-cdn-%EA%B5%AC%EC%B6%95-%EC%8B%9C-cors-%EA%B0%95%EC%A0%9C-%EC%84%A4%EC%A0%95-eb271a3ca3c6


'HTTP > CORS' 카테고리의 다른 글

XHR (XML Http Request)이란  (0) 2022.07.24
3장. CORS의 HTTP 응답 헤더와 HTTP 요청 헤더  (0) 2022.07.24
2장. CDN을 통한 CORS 및 CORS 요청  (0) 2022.07.24
1장. CORS 기본 정리  (0) 2022.07.24

HTTP 응답 헤더

  • Cross-Origin 리소스 공유 명세에 정의된 대로 서버가 접근 제어 요청을 위해 보내는 HTTP 응답 헤더가 나열

1. Access-Control-Allow-Origin

  • 리턴된 리소스에는 다음 구문과 함께 하나의 Access-Control-Allow-Origin 헤더가 있을 수 있음

    # 특정 Origin 
    Access-Control-Allow-Origin: <origin>
    
    # 모든 Origin 표현을 위해 * (asterisk 사용)
    Access-Control-Allow-Origin: *
  • Access-Control-Allow-Origin은 단일 출처를 지정하여 브라우저가 해당 출처가 리소스에 접근하도록 허용
  • 자격 증명이 없는 요청의 경우, 와일드 카드(*)는 브라우저의 origin에 상관없이 모든 리소스에 접근하도록 허용
  • 예를들어 https://mozilla.org 의 코드가 리소스에 접근 할 수 있도록 하려면 아래와 같이 지정 가능
    Access-Control-Allow-Origin: https://mozilla.org
  • 서버가 와일드 카드(*) 대신에 하나의 origin을 지정하는 경우, 서버는 Vary 응답 헤더에 Origin을 포함해야 함
  • Origin은 화이트 리스트의 일부로 요청 Orgin에 따라 동적으로 변경될 수 있음
  • 서버 응답이 Origin 요청 헤더에 따라 다르다는것을 클라이언트에 알려줌

2. Access-Control-Expose-Headers

  • Access-Control-Expose-Headers 헤더를 사용하면 브라우저가 접근할 수 있는 헤더를 서버의 화이트리스트에 추가 가능
    Access-Control-Expose-Headers: <header-name>[, <header-name>]*
  • 예시) X-My-Custom-Header와 X-Another-Custom-Header 헤더가 브라우저에 전달
    Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

3. Access-Control-Max-Age

  • Access-Control-Max-Age 헤더는 preflight request 요청 결과를 캐시할 수 있는 시간
  • delta-seconds 파라미터는 결과를 캐시할 수 있는 시간(초)를 나타냄
    Access-Control-Max-Age: <delta-seconds>
  • 예시) preflight request 요청 결과를 1일 캐시
    Access-Control-Max-Age: 86400

4. Access-Control-Allow-Credentials

  • Access-Control-Allow-Credentials 헤더는 credentials 플래그가 true일 때 요청에 대한 응답을 표시할 수 있는지를 나타냄
  • preflight request에 대한 응답의 일부로 사용하는 경우, credentials을 사용하여 실제 요청을 수행할 수 있는지 나타냄
  • simple GET requests는 preflighted 되지 않으므로 credentials이 있는 리소스를 요청하면 반환되지 않음
  • Access-Control-Allow-Credentials 헤더가 없으면 브라우저에서 응답을 무시하고 웹 컨텐츠로 반환되지 않음
    Access-Control-Allow-Credentials: true

5. Access-Control-Allow-Methods

  • Access-Control-Allow-Methods 헤더는 리소스에 접근할 때 허용되는 메서드를 지정
  • Access-Control-Allow-Methods 헤더는 preflight request에 대한 응답으로 사용
    Access-Control-Allow-Credentials: true

6. Access-Control-Allow-Headers

  • preflight request에 대한 응답으로 Access-Control-Allow-Headers 헤더가 사용
  • 실제 요청시 사용할 수 있는 HTTP 헤더를 나타냄
    Access-Control-Allow-Headers: <header-name>[, <header-name>]*


HTTP 요청 헤더

  • cross-origin 공유 기능을 사용하기 위해 클라이언트가 HTTP 요청을 발행할 때 사용할 수 있는 헤더가 나열됨
  • HTTP 요청 헤더는 서버를 호출할 때 설정
  • cross-site XMLHttpRequest 기능을 사용하는 개발자는 프로그래밍 방식으로 cross-origin 공유 요청 헤더를 설정할 필요가 없음

1. Origin

  • Origin 헤더는 cross-site 접근 요청 또는 preflight request의 출처를 나타냄
  • Origin 은 요청이 시작된 서버를 나타내는 URI → 경로 정보는 포함하지 않고, 오직 서버 이름만 포함
  • Origin 값은 null 또는 URI 가 올 수 있음
    Origin: <origin>

2. Access-Control-Request-Method

  • Access-Control-Request-Method 헤더는 실제 요청에서 어떤 HTTP 메서드를 사용할지 서버에게 알려주기 위해, preflight request 할 때에 사용
    Access-Control-Request-Method: <method>

3. Access-Control-Request-Headers

  • Access-Control-Request-Headers 헤더는 실제 요청에서 어떤 HTTP 헤더를 사용할지 서버에게 알려주기 위해, preflight request 할 때에 사용
    Access-Control-Request-Headers: <field-name>[, <field-name>]*

'HTTP > CORS' 카테고리의 다른 글

XHR (XML Http Request)이란  (0) 2022.07.24
4장. AWS S3 + CloudFront로 CDN 구축 시 CORS 강제 설정  (0) 2022.07.24
2장. CDN을 통한 CORS 및 CORS 요청  (0) 2022.07.24
1장. CORS 기본 정리  (0) 2022.07.24

+ Recent posts