들어가는 말

 

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

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

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

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


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