서론

프로젝트에서 openAPI를 호출해서 사용하는 부분이 있었는데,

아니나 다를까 교차 출처 리소스 공유(CORS, Cross-origin resource sharing) 에러가 발생하였습니다.

(당연한거슬..)

 

 

 

자 그래서 이 문제를 천천히 해결해보았는데요..

일단 CORS 에러가 발생하는 이유는 브라우저는 서로 다른 도메인에 대한 요청을 보안상 제한하기 때문입니다.

즉, 내 사이트 (http://okayoon.com)에서 openAPI (https://api.ipify.org)로 요청을 하게 되는 경우 다른 도메인이기 때문에 CORS 에러가 발생합니다. 

 

그 다음 왜???? 제한하는지 간단히 이해하고 넘어가겠습니다.

JSON은 단순 데이터만이 아닌 Javascript 자체도 전달할 수 있는데, 이럴 경우에 해커나 나쁜사람들이 악성 스크립트를 보낼 수 있기 때문입니다. 그렇다면 정말 위험해 질 수 있겠죠.

 

그렇기 때문에 저의 상황에서는 당연히 CORS 에러가 발생했고,

이 문제를 해결하기 위해 JSONP 사용하도록 하겠습니다. 

그전에 JSON과 JSONP에 대해 간단히 알아보고 가겠습니다!

 

 

 

JSON (Javscript Object Notation) 

JSON은 '속성-값' 쌍 또는 '키-값' 쌍으로 이루어진 데이터 오브젝트를 전달하기 위해 인간이 읽을 수 있는 텍스트를 사용하는 개방형 표준 포맷입니다.

- 위키백과

 

예시

{
  "user": [
    {
      "name": "suzy",
      "age": "17"
    },
    {
      "name": "sunyoung",
      "age": "20"
    }
  ]
}

 

 

JSONP(JSON-P, JSON with Padding)

JSONP는 클라이언트가 아닌 각기 다른 도메인에 상주하는 서버로부터 데이터를 요청하기 위해 사용됩니다.동일-출처 정책을 우회하는 데이터의 공유를 가능하게 합니다.HTML <script> 요소는 외부 출처로부터 조회된 내용을 실행하는 것이 허용되어 있습니다.이러한 웹 브라우저의 특성을 이용해 JSON 데이터를 클라이언트가 지정한 콜백 함수를 호출하는 유효한 Javascript 문법으로 감싸 클라이언트에 전송합니다. 
외부 서비스는 JSON 데이터를 패딩하여 클라이언트에 보냅니다.
parseResponse({"Name": "Foo", "Id": 1234, "Rank": 7});
웹 브라우저는 이 데이터를 유효한 JavaScript 프로그램으로 받아들여 실행하고, 콜백 함수인 parseResponse가 실행되며 받아온 데이터를 처리할 수 있게 됩니다.

- 위키백과

 

 

 

즉, 브라우저에서 <script> 요소는 다른 도메인(스크립트 파일)에 임베드할 수 있기 때문에 보안상 제한되는 부분에 해당하지 않습니다. 그래서 JSONP는 이것을 이용해서 JSON 데이터를 한번 감싸서(padding) 클라이언트에 전송합니다.

JSONP는 jQuery에서 $.ajax() 스펙을 이용해서 간단히 구현할 수 있지만, axios에서는 지원하지 않기때문에 따로 JSONP 모듈을 설치하고 작업해야 합니다.

 

npm

npm i jsonp

// @see https://www.npmjs.com/package/jsonp

예시

// 사용법: jsonp(url, opts, fn)

// 사용 전
const res = await Axios.get('http://ipify.com/?format=json');

// 사용 후
await jsonp('//api.ipify.org?format=jsonp', null, (err, res) => {
  // .....
});

 

그 외의 방식으로 proxy로도 작업할 수 있습니다.

참고 https://velog.io/@hoit_98/SOP%EC%99%80-CORS

 

 

 


많은 사람들이 URI, URL은 혼용해서 사용하는 경우가 종종 있습니다.
뭐, 문제 없이 소통이 되기 때문이죠..

하지만 이 둘의 정확한 차이를 알고 넘어가지 않는다면, 우리에게 발전이 없으므로^^
오늘 URI와 URL의 차이를 짚고 넘어가보도록 하겠습니다.
플러스로 URN에 대해서도 알아봅시다.

 

URI (Uniform Resource Identifier) - 식별자

웹 기술에서 사용하는 논리적 또는 물리적 자원을 식별하는 고유한 문자열 시퀀스입니다.
자원을 나타내는 유일한 주소이며 인터넷 프로토콜이 항상 붙어다닙니다.

이미지를 보면 URI가 가장 상위개념인 것을 알 수 있습니다.

출처:  https://velog.io/@jch9537/URI-URL

 


URL (Uniform Resource Locator) - 위치

웹 주소라고하며 네트워크 상에서 자원이 어디 있는지 알려주기 위한 규약입니다. 흔히 웹 사이트 주소로 알고 있지만 URL은 웹사이트 주소뿐만 아니라 네트워크상의 자원의 위치를 모두 나타낼 수 있습니다.

 

 


URN (Uniform Resource Name) - 이름

영속적이고 위치에 독립적인 자원을 위한 지시자로 사용하기 위해 정의되었습니다.
URL의 한계로 인해 만들어졌으며, 자원의 이름을 가르키며 유일한 값이어야합니다.
실제 자원을 찾을때는 URL로 변환하여 이용합니다.

 

 

 

예시

예시 1

// URI, URL의 예시 (해당 위치로 접근하는 프로토콜이 포함되어있음)
telnet://192.168.0.10:8080/ // telnet://
http://nsinc.tistory.com/ // http://
mailto:myname@me.com // mailto:

// URN의 예시
urn:isbn:0451450523 // 1926년에 출간된 the Last Unicorn의 도서식별번호
urn:oid:2.16.840  // 미국을 의미하는 OID

 

 

예시 2

출처: https://www.charlezz.com/?p=44767

- index.html 파일은 위치와 주소를 포함하기에 URI와 URL에 해당됩니다.
- index는 파일의 위치는 맞으나 웹주소가 어디있는지 모르기때문에 URI만 해당됩니다. (https://charlezz.com/main 으로 URL을 지정한 후 index.html 문서를 보여줄 수 있기에..)

 


예시 3

조건: 사이트명: test.com  / 파일: index.html

https://test.com/index?id=tester&page=8


https://test.com/index 까지가 URL이고
id와 page의 식별자가 필요하므로
https://test.com/index?id=tester&page=8 는 URI이다.

 


예시 4

간단히 빗대어 설명해보겠습니다.
나는 오늘 경아(URI/URN)와 약속이 있어 경아의 직장으로 찾아갑니다.
경아는 성남시청(URI/URL)에서 일을하고 있습니다.
<strong>(= https://성남시청/)</strong>
성남시청을 찾아온 나는 안내데스크에 경아를 만나러왔다고 말합니다.
경아라는 이름의 직원 수가 많아서 내 친구 경아의 담당 부서(URI/URN)를 말합니다.
담당 직원은 나를 경아와 만날 수 있는 특별한 공간(URI)으로 안내합니다.
<strong>(= https://성남시청/방문?부서=인사과&직원명=경아)</strong>

 

 


 

참고 및 출처
https://www.charlezz.com/?p=44767
https://velog.io/@jch9537/URI-URL
https://nsinc.tistory.com/192

 

 

 

들어가는 말

 

저는 유지보수 업무를 주로 해왔었기 때문에 사용자 인증하는 절차나 관련 기술, 정의 등등에 대해

간단히 "그런게 있다더라~ 카더라~"로만 알고 있었는데,

이번에 회사에서 관련된 작업을 진행하게 되어 정의에 대해 알아보고자합니다.

(이직하고 (변명)적응하느라 포스팅을 거의 안썼는데, 다시 마음을 잡고 간단한 글이라도 쓰려고 노력 중)


JWS (Json Web Token)

정의

Json을 이용하여 사용자에 대한 속성을 저장하는 Claim 기반의 웹 토큰입니다.

JWT는 토큰 자체의 정보를 담고 사용하는 Self-Containerd 방식으로 정보를 전달합니다.

 

여기서 클레임(Claim) 기반이란 무엇일까요?

클레임(Claim)은 주체가 수행할 수 있는 작업보다는 주체가 무엇인지를 표현하는 이름과 값의 쌍이라고 합니다. 

예를들어 운전 면허증을 가지고 있다고 가정해보겠습니다.

생년월일(1999년 1월 21일)이 적혀있는 클레임의 이름은 DateOfBirth라고 할 수 있고, 클레임의 값은 19990121이며 발급자는 운전면허 발급기관이 됩니다.

클레임 기반 권한부여는 클레임 값을 검사한 후에 이 값을 기반으로 리소스에 대한 접근을 허용합니다.

그리고 운전면허증을 통해 인증을 거쳐하는 경우가 생긴다면 권한 부여 절차가 진행됩니다.

인증을 요구하는 경우가 생기면 접근을 허용하기 전에 먼저 클레임의 값(DateOfBitrth)와 발급기관을 신뢰할 수 있는지 여부부터 확인합니다.

 

간단한 회원인증 시의 흐름을 확인해보겠습니다.

 

어플 실행
스토리지에 인증 값이 있는가?
Y N
스토리지 값을 통해 인증 서버에서 JWT 발생 및 응답 헤더에 담아 보냄
  스토리지에 JWS를 저장
인증

이렇게 받아온 JWT를 HTTP 헤더에 담아 통신을 할 때마다 보내주게되면

서버에서 JWT가 허가된 것인지의 여부를 검사하게됩니다.

 

 

구조

JWT는 3개의 파트로 나누어지며 각 파트는 점으로 구분합니다.

구분되는 파트는 Header, Payload, Signature 이며 각 부분은 Json 형태인 (Base64로 인코딩된 형태로 표현)입니다.  

Base64 인코딩의 경우 “+”, “/”, “=”이 포함되지만 JWT는 URI에서 파라미터로 사용할 수 있도록 URL-Safe 한  Base64url 인코딩을 사용합니다.

(Base64는 암호화된 형태가 아니므로 매번 같은 인코딩 문자열을 반환한다고 합니다.)

 

JWT 구조 (출처: http://www.opennaru.com/opennaru-blog/jwt-json-web-token/)

 

JWT 구조 디테일 (출처: https://mangkyu.tistory.com/56)

Header

토큰의 타입과 해시 암호화 알고리즘으로 구성되어 있습니다.

{
	"alg": "HS256", // 해시 알고리즘 (HMAC, SHA256, RSA)
    "typ": "JWT" // 토큰 유형
}

 

Payload

토큰에 담을 클레임(Claim) 정보를 포함하고 있습니다.

클레임은 json 형태로 key-value 한 쌍으로 이루어져 있습니다.

클레임의 정보는 등록된 (registered) 클레임, 공개(public) 클레임, 비공개(private) 클레임으로 세 종류가 있습니다. 

{
	"name": "John",
    "age": "28"
}

 

등록된 클레임 (Registered Claim)

- 토큰 정보를 표현하기 위해 이미 정해진 종류의 데이터로 선택적으로 작성이 가능하며 사용이 권장됩니다.

 

iss 토큰 발급자(issuer)
sub 토큰 제목(subject)
aud 토큰 대상자(audience)
exp 토큰 만료 시간(expiration), NumericDate 형식으로 되어 있어야 함 ex) 1480849147370
nbf 토큰 활성 날짜(not before), 이 날이 지나기 전의 토큰은 활성화되지 않음
lat 토큰 발급 시간(issued at), 토큰 발급 이후의 경과 시간을 알 수 있음
jti JWT 토큰 식별자(JWT ID), 중복 방지를 위해 사용하며, 일회용 토큰(Access Token) 등에 사용

 

공개 클레임 (Public Claim)

- 사용자 정의 클레임으로 공개용 정보를 위해 사용되며 충돌 방지를 위해 URI 포맷을 이용합니다.

{
	"https://naver.com": true
}

 

비공개 클레임 (Private Claim)

- 사용자 정의 클레임으로 서버와 클라이언트 사이에 임의로 지정한 정보를 저장합니다.

{
	"token_type": "access"
}

 

Signature

서명은 비밀키를 포함하여 암호화되어있습니다.

토큰을 인코딩하거나 유효성을 검증할 때 사용하는 고유한 암호화 코드입니다.

헤더, 페이로드의 값을 Base64로 인코딩하고 이 값을 비밀 키를 이용해 헤더에서 정의한 알고리즘으로 해싱합니다.

그리고 해싱된 값을 다시 Base64로 인코딩하여 생성합니다.

 

JWT 사용 예시

생성된 토큰은 HTTP 통신 시 인증 시 value 값으로 사용되며 일반적으로 토큰 value 앞에는 'Bearer'을 붙여 사용합니다.

{
	accesstoken: Bearer <token>
}

 

 


 

참고

http://www.egocube.pe.kr/translation/content/asp-net-core-security/201701150001

http://www.opennaru.com/opennaru-blog/jwt-json-web-token/

https://meetup.toast.com/posts/239

 

 

Nuxt Universal Mode?

 

Nuxt 프로젝트를 설정하는 중에 Rendering mode에 대해 궁금증이 생겼다.

SPA나 SSR 방식은 알고있다고 생각했는데, Universal은 뭘까? 간단히 알아보기로 했다.

 

SSG (Server Side Generation or Static Side Generation)

(참고: https://velog.io/@longroadhome/FE-SSRServer-Side-Rendering-%EA%B7%B8%EB%A6%AC%EA%B3%A0-SSGStatic-Site-Generation-feat.-NEXT%EB%A5%BC-%EC%A4%91%EC%8B%AC%EC%9C%BC%EB%A1%9C)

 

 

Nuxt Universal Mode?

 

먼저 짧게 요약하자면

몇 가지 기능을 포함하여 SSR과 CSR 방식 둘 다 상황에 맞게 사용하여 단점을 개선한 모드이다.

초기 렌더링 속도를 위해 Server Side Rendering 방식을 사용하고 페이지간 이동 시에는 Client Side Rendering을 사용한다.

 

어떻게 구현할 수 있었을까?

핵심은 하이드레이션(Client Side Hydration), 코드 분할(Code Splitting), 프리패칭(Prefetching)이라고 한다.

 

 

 

1. 하이드레이션

참고 글을 보았지만 이해가가지 않아서.. 참고 글을 읽어보길 권유합니다.

초기 렌더링에서 서버로부터 정적 html과 자바스크립트 번들을 함께 가져온다.

그리고 이 번들을 통해 정적 html을 다이나믹 DOM 상태로 바꾼다.

다이나믹 DOM은 리렌더링없이 사용자와 상호작용을 할 수 있다고 한다. 

 

 

 

2. 코드분할

코드 전체를 로드하지 않고 필요한 곳에 맞춰 분할해서 가져오는 것을 말한다.

Universal 모드에서는 렌더링에 필요한 곳에 맞게 자동으로 코드를 분할하여 가져오게되어 

로딩속도가 빨라진다. 그리고 가져온 번들은 캐싱되어 재활용된다.

 

 

 

3. 프리패칭

<NuxtLink />가 페이지에 있을 경우, 해당 페이지들을 미리 서버에 요청하여 준비한다.

(뷰포트에 이동할 페이지 링크가 보이기만해도 번들에 요청)

따라서 이미 클라이언트에 준비가 되어있으므로 페이지간 이동 속도가 빠르다.

 

 


 

출처

https://joshua1988.github.io/vue-camp/nuxt/universal-mode.html

 

크롬 확장 프로그램을 아시나요?

크롬 확장프로그램

크롬 확장 프로그램 프로젝트를 진행하기 위한 셋팅을 알아보겠습니다.

 


 

1. 프로젝트 CRA(create-react-app)로 시작

npx create-react-app extension-project

 

설치가 완료되면 구조가 이러합니다.

create-react-app 구조

 

 

 

2. manifest.json 파일 수정

public > manifest.json

{
  "name": "your project name",
  "icons": {
    "16": "icon-16x16.png",
    "48": "icon-32x32.png",
    "64": "icon-64x64.png",
    "128": "icon-128x128.png"
  },
  "permissions": [],
  "manifest_version": 3,
  "version": "0.0.1",
  "action": {
    "default_popup": "index.html",
    "default_title": "your project title"
  }
}

여기서 참고할사항은 manifest_version입니다. 

검색을 해보면 대부분 2 버전으로 되어있는데, 2와 3을 잘 파악해서 쓰시기를 바랍니다.

action과 같은 몇개의 부분들이 변경된 듯합니다.

name에 프로젝트 이름과 함께 icons에 아이콘을 등록해줍시다.

16 사이즈는 아래와 같이 확장 플러그인 액세스 고정하면 나오는 아이콘이고 favicon.ico등 여러 확장자가 가능한 듯합니다.

액세스 고정시 나오는 영역

48, 64, 128 등의 사이즈는 확장 플러그인 관리창이나 다운로드 창에서 사용된다고 합니다.

확장 플러그인 관리창은 chrome://extensions/로 이동하거나 아래와 같이 아이콘을 클릭하면 나옵니다.

확장 프로그램 아이콘 클릭 > 확장 프로그램 관리

이동하면 보이는 아이콘들입니다.

아이콘들

 

permissions는 사용자의 컴퓨터에 권한을 설정하는 곳입니다. 즉 storage를 사용하기 위해서는 permissions에 추가해야합니다.

version은 출시하는 버전을 작성하면되고, 스토어에 버전정보가 노출됩니다.

actions은 2버전에서는 browser_action이었는데 3에서 통합되었다고 합니다.

확장 기능 액션 수행 시 필요한 곳입니다.

 

 

 

3. .env 파일

.env 파일은 루트에 생성하도록 합니다.

.env 생성

.env 파일은 환경 변수를 저장하는 파일인데, 아래와 같은 코드를 추가합니다.

extension에서는 인라인 자바스크립트를 허용하지 않기 때문에 추가해야한다고 하는데, 여튼 이게 없으면 오류가 납니다.

// .env
INLINE_RUNTIME_CHUNK=false

 

 

 

4. 빌드

자 이제 빌드해줍니다.

npm run build

build

 

 

 

5. 확장 프로그램 등록

파일을 등록해주겠습니다.

위에서 설명한 확장 프로그램 관리 페이지로 이동합니다.

chrome://extensions/ 

확장프로그램 페이지

1) 왼쪽 상단의 개발자 모드 버튼 클릭

2) 압축해제된 확장 프로그램을 로드합니다. 등의 버튼이 뜨면 해당 버튼 클릭

3) build 폴더를 찾아서 등록

build 폴더 등록

4) 등록된 것 확인

확장프로그램 등록된 캡쳐

5) 확장 프로그램 아이콘 클릭 > 내가 등록한 프로그램 고정

확장프로그램 고정 캡쳐

6) 등록확인

확장프로그램 등록 확인

 

 


https://developer.chrome.com/docs/extensions/mv3/intro/

 

설치

npm i vuex

 

store 폴더에 관리하기 위해 store폴더, 파일 생성

store 폴더

 

store/store.js

import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

const store = new Vuex.Store({
  state: {
  	//...
  },
  getter: {
  	//...
  },
  mutations: {
  	//...  
  },
  action: {
  	//...
  }
})

이런 형태로 작업하면 된다.

- state: 여러 컴포넌트에 공유되는 데이터

- getter: 연산된 state값을 접근하는 속성

- mutations: state값을 변경하는 이벤트 로직이나 메서드

- actions: 비동기 처리 로직을 선언하는 메서드

 

그리고 store 파일을 등록해줘야한다.

 

 

main.js

main.js

 

state

// vue파일 안에서 작업할 경우
// App.vue
data: {
    message: 'hello',
}
<p>{{ message }}</p>    

// store를 통해 작업할 경우
// store.js
state: {
    message: 'hello',
}

// App.vue
<p>{{ this.$store.state.message }}</p>
// App.vue
data: {
    message: 'hello',
}
<p>{{ message }}</p>    

// store.js
state: {
    message: 'hello',
}

// App.vue
<p>{{ this.$store.state.message }}</p>

 

 

getters

state에 접근하는 속성

computed()처럼 미리 연산된 값을 접근하는 속성

// store.js
getters: {
    getNumber(state){
        return state.num;
    },
    doubleNumber(state){
        return state.num * 2;
    }
}

// App.vue
<p>{{ this.$store.getters.getNumber }}</p>
<p>{{ this.$store.getters.doubleNumber }}</p>

 

 

mutations

state 값을 변경할 수 있는 유일한 방법,

mutations을 통해서 작업해야 devtools에서 추적이 가능.

devtools를 통해 유일한 추적

// store.js
mutations: {
    printNumbers(state) {
        return state.num
    },
    sumNumbers(state, anotherNum) {
        return state.num + anotherNum;
    }
},

// App.vue
this.$store.commit('printNumbers');
this.$store.commit('sumNumbers', 20);

// state를 변경하기 위해 mutations를 동작시킬 때 인자(payload)를 전달할 수 있음
// store.js
mutations: {
    modifyState(state, payload) {
        console.log(payload.str);
        return state.storeNum += payload.num;
    }
}

// App.vue
this.$store.commit('modifyState', {
    str: 'passed from payload',
    num: 20
});

 

 

actions

비동기 처리 로직을 선언하는 메서드. 비동기 로직을 담당하는 mutations

데이터 요청, promise, es6 async과 같은 비동기 처리는 모두 actions에 선언

// store.js
mutation: {
    doubleNumber(state) {
        state.num * 2;
    }
},
actions: {
    delayDoubleNumber(context) { 
                // context로 store의 메서드와 속성 접근
        context.commit('doubleNumber');
    }
}

// App.vue
// dispatch로 actions 호출
this.$store.dispatch('delayDoubleNumber');

actions에서 mutation에 접근하기 위한 경로로 첫번째 인자 context가 존재한다.

 

비동기 예제1

// store.js
mutations: {
    addCounter(state) {
        state.counter++;
    }
},
actions: {
    delayedAddCounter(context) {
        setTimeout(() => context.commit('addCounter'), 2000);
    }
}

// App.vue
methods: {
    incrementCounter(){
        this.$store.dispatch('delayedAddCounter');
    }
}

 

비동기 예제2

// store.js
mutations: {
    setData(state, fetchedData){
        state.product = fetchedData;
    }
},
actions: {
    fetchProductData(context) {
        return axios.get('https://domain.com/products/1')
.then(res => context.commit('setData', res));
    }
}

// App.vue
methods: {
    getProduct() {
        this.$store.dispatch('fetchProductData');
    }
}

+ Recent posts