반응형

유니티의 UI 시스템인 Canvas 컴포넌트에는 다양한 Render Mode가 있습니다. 각 모드는 UI가 화면에 렌더링되는 방식에 영향을 줍니다. 자세히 살펴보겠습니다.

 

1. Screen Space - Overlay

이 모드는 UI가 항상 화면 최상단에 표시되며 게임 세계와 별개로 동작합니다.

특징:

  • UI가 화면 해상도에 따라 자동 조정됩니다
  • 카메라 설정과 무관하게 항상 보입니다
  • 게임 화면 위에 직접 렌더링됩니다

예시: 체력바, 점수 표시, 메뉴 버튼과 같이 항상 플레이어에게 보여야 하는 요소에 적합합니다.

 
Canvas myCanvas = GetComponent<Canvas>();
myCanvas.renderMode = RenderMode.ScreenSpaceOverlay;
 

2. Screen Space - Camera

이 모드는 특정 카메라 앞에 UI를 배치합니다. UI는 카메라와의 거리에 따라 크기가 변경될 수 있습니다.

특징:

  • 지정된 카메라의 시야에 따라 렌더링됩니다
  • 카메라와의 거리를 조절할 수 있습니다
  • 게임 세계와 약간의 상호작용이 가능합니다

예시: 3D 게임에서 플레이어 주변에 떠다니는 상태 표시 UI나, 카메라 움직임에 따라 약간의 반응을 보여야 하는 요소에 적합합니다.

 
Canvas myCanvas = GetComponent<Canvas>();
myCanvas.renderMode = RenderMode.ScreenSpaceCamera;
myCanvas.worldCamera = Camera.main; // 메인 카메라 지정
myCanvas.planeDistance = 10f; // 카메라로부터의 거리

3. World Space

UI 요소가 완전히 게임 세계의 3D 공간 내에 존재하며 게임 오브젝트처럼 동작합니다.

특징:

  • 3D 공간에서 실제 오브젝트처럼 배치됩니다
  • 일반 3D 오브젝트와 상호작용할 수 있습니다
  • 다양한 카메라 각도에서 볼 수 있으며, 다른 오브젝트에 가려질 수 있습니다

예시:

  1. NPC 머리 위의 이름표나 대화창
  2. 게임 내 가상 모니터나 터치스크린
  3. VR/AR 게임의 상호작용 요소
 
Canvas myCanvas = GetComponent<Canvas>();
myCanvas.renderMode = RenderMode.WorldSpace;
// 이제 Transform을 통해 3D 공간에서 위치, 회전, 크기 조절 가능
myCanvas.transform.position = new Vector3(0, 2, 0);
myCanvas.transform.rotation = Quaternion.Euler(0, 30, 0);
myCanvas.transform.localScale = new Vector3(0.01f, 0.01f, 0.01f);

실제 사용 예시

Screen Space - Overlay 예시

// 게임의 메인 UI 설정
public class MainUISetup : MonoBehaviour
{
    void Start()
    {
        Canvas canvas = GetComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceOverlay;
        
        // 해상도 조정을 위한 설정
        CanvasScaler scaler = GetComponent<CanvasScaler>();
        scaler.uiScaleMode = CanvasScaler.ScaleMode.ScaleWithScreenSize;
        scaler.referenceResolution = new Vector2(1920, 1080);
    }
}
 

Screen Space - Camera 예시

// 메인 카메라에 연결된 UI 설정
public class CameraUISetup : MonoBehaviour
{
    public Camera mainCamera;
    
    void Start()
    {
        Canvas canvas = GetComponent<Canvas>();
        canvas.renderMode = RenderMode.ScreenSpaceCamera;
        canvas.worldCamera = mainCamera;
        canvas.planeDistance = 5f;
        
        // 카메라가 줌인/줌아웃할 때도 UI가 적절하게 보이도록 설정
        CanvasScaler scaler = GetComponent<CanvasScaler>();
        scaler.dynamicPixelsPerUnit = 100f;
    }
}

World Space 예시

// NPC 위에 표시되는 정보 UI
public class NPCInfoDisplay : MonoBehaviour
{
    public Transform npcHead;
    
    void Start()
    {
        Canvas canvas = GetComponent<Canvas>();
        canvas.renderMode = RenderMode.WorldSpace;
        
        // NPC 머리 위에 배치
        RectTransform rectTransform = GetComponent<RectTransform>();
        rectTransform.position = npcHead.position + new Vector3(0, 0.5f, 0);
        
        // 항상 카메라를 향하도록 설정
        rectTransform.localScale = new Vector3(0.005f, 0.005f, 0.005f);
    }
    
    void Update()
    {
        // 항상 플레이어를 바라보도록 회전
        transform.LookAt(Camera.main.transform);
        transform.rotation = Quaternion.Euler(0, transform.rotation.eulerAngles.y, 0);
    }
}
 
 

각 Render Mode는 고유한 장단점이 있으므로, 제작하려는 게임 유형과 UI 요소의 목적에 따라 적절한 모드를 선택하는 것이 중요합니다.

 
 
반응형
반응형

1. 기본 문법

const newArray = originalArray.filter(callback(element[, index[, array]])[, thisArg]);
  • originalArray: 원본 배열.
  • callback: 배열의 각 요소에 대해 실행할 함수.
    • element: 현재 처리 중인 요소.
    • index (선택): 현재 요소의 인덱스.
    • array (선택): filter()를 호출한 배열 자체.
  • thisArg (선택): callback 함수 내에서 사용될 this 값.

2. 콜백 함수의 역할

콜백 함수는 배열의 각 요소마다 호출되며, 각 호출 시 다음과 같이 전달됩니다:

  • element: 현재 요소.
  • index: 현재 요소의 인덱스.
  • array: 원본 배열 전체.

콜백 함수는 true 또는 false를 반환해야 합니다.

  • true를 반환하면, 해당 요소는 새 배열에 포함됩니다.
  • false를 반환하면, 해당 요소는 제외됩니다.

예를 들어:

const numbers = [1, 2, 3, 4, 5];
const evenNumbers = numbers.filter((num) => {
  return num % 2 === 0;  // 짝수일 경우에만 true 반환
});
console.log(evenNumbers); // [2, 4]
 

3. thisArg 매개변수

두 번째 인자로 전달하는 thisArg는 콜백 함수 내부에서 this로 사용됩니다.

예를 들어, 객체의 속성을 비교할 때 유용합니다:

 
const context = { threshold: 10 };
const numbers = [5, 12, 8, 130, 44];

const filtered = numbers.filter(function(num) {
  return num > this.threshold;
}, context);

console.log(filtered); // [12, 130, 44]

4. 다양한 사용 예제

4.1. 배열 내의 객체 필터링

객체 배열에서 특정 조건을 만족하는 객체만 골라낼 수 있습니다.

const users = [
  { name: 'Alice', age: 25 },
  { name: 'Bob', age: 30 },
  { name: 'Charlie', age: 35 }
];

const usersOver30 = users.filter(user => user.age > 30);
console.log(usersOver30); // [ { name: 'Charlie', age: 35 } ]

4.2. 중첩 배열에서 특정 요소 제거

특정 값이 포함된 배열을 제거하는 경우:

const fruits = ['apple', 'banana', 'orange', 'banana'];
const filteredFruits = fruits.filter(fruit => fruit !== 'banana');
console.log(filteredFruits); // ['apple', 'orange']

4.3. 조건에 따른 복합 필터링

여러 조건을 조합하여 필터링할 수도 있습니다.

const items = [
  { name: 'Book', price: 12, inStock: true },
  { name: 'Pen', price: 2, inStock: false },
  { name: 'Notebook', price: 5, inStock: true }
];

const affordableInStock = items.filter(item => item.inStock && item.price < 10);
console.log(affordableInStock); // [ { name: 'Notebook', price: 5, inStock: true } ]

5. filter()의 특성과 주의 사항

  • 불변성 유지: filter()는 원본 배열을 변경하지 않고, 조건에 맞는 요소들로 새 배열을 생성합니다.
  • 희소 배열(Sparse Array): 배열 내에 비어있는 요소(holes)가 있는 경우, 해당 빈 요소들은 callback 함수에서 호출되지 않습니다.
  • 반환 값: 조건에 맞는 요소가 하나도 없으면 빈 배열 ([]) 을 반환합니다.
  • 고차 함수(Higher-Order Function): filter()는 콜백 함수를 인자로 받으므로, 다른 함수와 조합하여 사용하기 좋습니다.
  • 실행 순서: 배열의 각 요소에 대해 순서대로 콜백 함수가 호출됩니다. 따라서 인덱스에 의존하는 조건문 작성 시 주의해야 합니다.

6. filter()와 다른 배열 메서드 비교

  • map(): 배열의 각 요소를 변환하여 새 배열을 만듭니다. (길이는 동일)
  • reduce(): 배열의 모든 요소를 누적하여 하나의 값으로 축소합니다.
  • find(): 조건을 만족하는 첫 번째 요소만 반환합니다.
  • forEach(): 배열의 각 요소에 대해 주어진 함수를 실행하지만, 반환 값을 저장하지 않습니다.

filter()는 배열에서 특정 조건에 맞는 요소들만 골라내는 데에 최적화되어 있으며, 반환된 배열은 원본 배열과 별개이므로 후속 조작에 안전하게 사용할 수 있습니다.


7. 결론

JavaScript의 filter() 메서드는 배열 데이터를 다룰 때 매우 강력하고 유연하게 사용할 수 있는 도구입니다. 다양한 상황에서 원하는 조건에 맞는 데이터를 선별하고, 원본 배열을 건드리지 않으면서 새로운 배열을 생성할 수 있기 때문에, 코드의 가독성과 유지보수성을 높이는 데 큰 도움을 줍니다.

이처럼 filter()의 기본 사용법부터 고급 사용법까지 이해하면, 복잡한 데이터 필터링 작업도 간단하고 직관적으로 해결할 수 있습니다.

반응형
반응형

유니티나 안드로이드 스튜디오등의 안드로이드 앱이나 게임 개발하고 업로드하는데는 업로드 키가 필요합니다.

 

업로드키를 분실하거나 키의 패스워드를 잊어버렸을때의 대처방법을 소개합니다.

 

새 업로드 키 생성

 

우선 새 업로드 키가 필요합니다. 만드는 방법은 기존 방법과 같습니다.

 

Project Setting > Player > Publishing Settings

 

 

 

간단하게 생성해줍니다.

 

새 업로드 인증서를 생성해서 구글 콘돌에 업로드

 

keytool -export -rfc -keystore 키의파일명 -alias Alias이름 -file upload_certificate.pem

새로운 키가 있는 장소로 이동하여 터미널을 열고 위와 같은 명령어를 입력해줍니다.

 

구글공식문서

 

해당앱의 테스트 및 출시 > 설정 -> 앱 서명 항목에서 업로드 키 재설정 요청을 선택합니다.
앞에서 생성한 새 업로드 인증서를 업로드 합니다. 

 

 

접수되면 메일로 재설정 요청이 왔고 언제부터 새로운 업로드 키로 업로드가 가능한지 안내받을 수 있습니다.

 

보통 2일정도 걸리는 듯 합니다.

 

해당 기간이 지나고 이제 다시 새로운 키로 업로드 하실 수 있습니다.

 

이상 슬기로운 개발생활 되세요.

반응형
반응형

JavaScript에서 data 속성은 주로 HTML 요소에 사용자 정의 데이터를 저장할 때 사용하는 HTML5의 커스텀 데이터 속성을 의미합니다. 이 속성들은 data-로 시작하며, JavaScript를 통해 쉽게 접근하고 조작할 수 있습니다.

HTML5 커스텀 데이터 속성이란?

  • 정의: HTML 요소에 추가 정보를 저장할 수 있는 속성입니다.
  • 형식: data-키이름="값" 형식으로 사용합니다.
  • 사용 목적: 별도의 시각적 표현이 필요 없는, 추가적인 정보를 요소에 담고 싶을 때 사용합니다.

JavaScript에서의 접근 방법

HTML 요소에 설정된 커스텀 데이터 속성은 JavaScript의 dataset 프로퍼티를 통해 접근할 수 있습니다.

  • data- 뒤의 이름은 카멜 표기법으로 변환되어 접근합니다.
    예) data-user-id → element.dataset.userId

예제

다음은 HTML 요소에 커스텀 데이터 속성을 설정하고, JavaScript에서 읽고 수정하는 예제입니다.

 
<!-- HTML -->
<div id="userInfo" data-user-id="101" data-user-name="JohnDoe">
  사용자 정보
</div>
 
// JavaScript
const userInfo = document.getElementById('userInfo');

// data 속성 읽기
console.log(userInfo.dataset.userId);    // 출력: "101"
console.log(userInfo.dataset.userName);  // 출력: "JohnDoe"

// data 속성 변경
userInfo.dataset.userName = 'JaneDoe';
console.log(userInfo.dataset.userName);  // 출력: "JaneDoe"

요약

  • HTML: <div id="userInfo" data-user-id="101" data-user-name="JohnDoe"></div>
  • JavaScript: document.getElementById('userInfo').dataset를 통해 data-user-id는 userId, data-user-name은 userName으로 접근

이처럼 HTML5의 data 속성을 활용하면, 요소에 추가적인 정보를 쉽게 저장하고 JavaScript에서 이를 읽어 동적으로 활용할 수 있습니다.

 

여기서 조금더 알아봅시다.

 

id 속성 없이도, CSS 선택자를 이용하여 data-user-id="101" 속성을 가진 요소를 선택할 수 있습니다.
이를 위해 JavaScript의 document.querySelector나 document.querySelectorAll 메서드를 사용할 수 있습니다.

그리고 추가로 해당 데이터의 값을 변경하는 법을 알아보도록 하겠습니다.

 

예제를 통해 알아보겠습니다.

HTML 코드 예제

<div data-user-id="101" data-user-name="JohnDoe">
  사용자 정보
</div>

JavaScript 코드 예제

 
// data-user-id가 "101"인 요소 선택 (첫 번째 요소만 선택)
const element = document.querySelector('[data-user-id="101"]');

if (element) {
  // 기존 값 확인
  console.log(element.dataset.userId); // 출력: "101"
  
  // data-user-id 값을 수정하는 방법 1: dataset 프로퍼티 사용
  element.dataset.userId = '202';
  console.log(element.dataset.userId); // 출력: "202"
  
  // data-user-id 값을 수정하는 방법 2: setAttribute 메서드 사용
  element.setAttribute('data-user-id', '303');
  console.log(element.getAttribute('data-user-id')); // 출력: "303"
}

설명

  1. 요소 선택하기:
    • document.querySelector('[data-user-id="101"]')를 사용하여 data-user-id 속성의 값이 "101"인 요소를 선택합니다.
    • 여러 요소를 선택하려면 document.querySelectorAll('[data-user-id="101"]')를 사용하여 NodeList를 받을 수 있습니다.
  2. 값 수정하기:
    • 방법 1: dataset 프로퍼티를 사용하여 element.dataset.userId로 접근한 후 원하는 값으로 변경합니다.주의: data-user-id 속성은 dataset에서는 userId로 접근합니다.
    • 방법 2: setAttribute 메서드를 사용하여 data-user-id 속성을 직접 수정할 수 있습니다.

이처럼, id 없이도 data 속성을 가진 요소를 쉽게 선택하고, 값을 읽거나 수정할 수 있습니다.

반응형
반응형

1. 기본적인 this 동작 방식

this는 함수가 호출되는 방식에 따라 값이 달라집니다. 주요한 패턴은 다음과 같습니다.

1.1 전역 컨텍스트에서의 this

 
console.log(this); // 브라우저에서는 Window 객체, Node.js에서는 global 객체
 

전역에서 this를 출력하면 브라우저 환경에서는 window 객체를, Node.js 환경에서는 global 객체를 참조합니다.


1.2 일반 함수에서의 this (엄격 모드와 일반 모드)

function showThis() {
    console.log(this);
}
showThis(); // 브라우저에서는 window, Node.js에서는 global

"use strict";
function strictShowThis() {
    console.log(this);
}
strictShowThis(); // undefined (엄격 모드에서는 `this`가 undefined)
  • 일반 모드: this는 전역 객체(window 또는 global)를 참조합니다.
  • 엄격 모드("use strict"): this는 undefined가 됩니다.

1.3 객체의 메서드에서의 this

const obj = {
    name: "Alice",
    showThis: function () {
        console.log(this.name);
    }
};
obj.showThis(); // "Alice"
  • 메서드를 호출한 객체(obj)가 this가 됩니다.

하지만 함수 내부에서 새로운 함수가 호출되면 this가 바뀔 수 있습니다.

const obj = {
    name: "Alice",
    showThis: function () {
        function inner() {
            console.log(this);
        }
        inner(); // 일반 함수로 호출되므로 `this`는 `window` 또는 `undefined`(엄격 모드) 
    }
};
obj.showThis();
  • 해결 방법:
    1. self 또는 that 변수를 활용 (var self = this;)
    2. .bind(this) 사용
    3. 화살표 함수 사용 (아래에서 설명)

2. 생성자 함수와 this

function Person(name) {
    this.name = name;
}

const p1 = new Person("Alice");
console.log(p1.name); // "Alice"
  • new 키워드를 사용하여 생성자 함수를 호출하면 this는 새로 생성된 객체를 참조합니다.

만약 new 없이 호출하면 this가 전역 객체를 가리키거나(비엄격 모드) undefined(엄격 모드)로 설정됩니다.

const p2 = Person("Bob"); // new 없이 호출
console.log(p2); // undefined
console.log(global.name); // "Bob" (Node.js의 경우)

해결 방법:

  • new 없이 호출될 경우 this가 올바른 객체를 참조하도록 강제하는 패턴 사용
function Person(name) {
    if (!(this instanceof Person)) {
        return new Person(name);
    }
    this.name = name;
}

3. 화살표 함수에서의 this

화살표 함수는 일반 함수와 다르게 this를 바인딩하지 않고, 자신을 포함하는 외부 스코프(렉시컬 스코프)의 this를 사용합니다.

const obj = {
    name: "Alice",
    showThis: function () {
        const arrow = () => {
            console.log(this.name);
        };
        arrow();
    }
};
obj.showThis(); // "Alice"
  • arrow 함수는 obj.showThis()의 this를 유지합니다.

하지만 화살표 함수는 생성자 함수로 사용할 수 없습니다.

const Person = (name) => {
    this.name = name;
};
const p = new Person("Alice"); // TypeError: Person is not a constructor

4. bind, call, apply를 이용한 this 변경

4.1 call()과 apply()

call()과 apply()를 사용하면 특정한 this를 설정하여 함수를 실행할 수 있습니다.

 
function showName() {
    console.log(this.name);
}

const user = { name: "Alice" };
showName.call(user); // "Alice"
showName.apply(user); // "Alice"
  • call(thisArg, arg1, arg2, ...) → 개별 인자로 전달
  • apply(thisArg, [arg1, arg2, ...]) → 배열로 전달

4.2 bind()

bind()는 새로운 함수를 반환하여 이후에 this가 고정된 상태로 실행되도록 합니다.

function showName() {
    console.log(this.name);
}
const user = { name: "Alice" };

const boundFn = showName.bind(user);
boundFn(); // "Alice"
  • bind()를 사용하면 this가 영구적으로 고정됩니다.

5. 이벤트 핸들러에서의 this

5.1 일반 함수에서의 this

const button = document.querySelector("button");
button.addEventListener("click", function () {
    console.log(this); // 클릭된 button 요소를 참조
});

5.2 화살표 함수에서의 this

button.addEventListener("click", () => {
    console.log(this); // window 객체를 참조
});
  • 화살표 함수는 이벤트 핸들러로 사용될 때 this가 window가 되어 문제가 발생할 수 있습니다.

해결 방법:

  • bind(this)를 사용하거나, 일반 함수 표현식을 사용

6. 클래스에서의 this

클래스 메서드에서는 this가 자동으로 인스턴스를 가리킵니다.

class Person {
    constructor(name) {
        this.name = name;
    }
    showName() {
        console.log(this.name);
    }
}
const p = new Person("Alice");
p.showName(); // "Alice"

하지만, 클래스 내부에서 setTimeout 등을 사용할 경우 this가 바뀔 수 있습니다.

class Timer {
    start() {
        setTimeout(function () {
            console.log(this); // window 또는 undefined
        }, 1000);
    }
}
const t = new Timer();
t.start();
  • 해결 방법: bind(this) 또는 화살표 함수 사용
class Timer {
    start() {
        setTimeout(() => {
            console.log(this); // Timer 객체 유지
        }, 1000);
    }
}

7. 정리

호출 방식this 값

전역 실행 (일반 모드) window (브라우저) / global (Node.js)
전역 실행 (엄격 모드) undefined
객체의 메서드 해당 객체
화살표 함수 외부 this를 따름 (렉시컬 this)
생성자 함수 새로 생성된 객체 (new 사용 시)
call / apply 명시적으로 지정된 객체
bind 영구적으로 고정된 this
DOM 이벤트 핸들러 (일반 함수) 이벤트가 발생한 요소
DOM 이벤트 핸들러 (화살표 함수) window

this는 문맥에 따라 다르게 동작하므로, 실행 방식과 함수 선언 형태를 잘 이해해야 합니다.

 

특히 클래스 사용시에 전달값을 함수로해서 this를 사용하는 경우에 바인등을 주의깊게 살피지 않으시면 뜬금없이 에러가 발생하므로 주의깊게 사용하셔야 이상한 동작을 할 경우가 줄어듭니다.

반응형
반응형

먼저 JWT의 기본 개념에 대해 설명드리겠습니다:

JWT는 당사자간에 정보를 JSON 객체로 안전하게 전송하기 위한 간단한 방법입니다. 이 정보는 디지털 서명되어 있어 신뢰할 수 있습니다.

JWT의 구조:

  1. Header: 토큰 유형과 사용된 서명 알고리즘
  2. Payload: 전달하려는 데이터(클레임)
  3. Signature: 헤더와 페이로드를 기반으로 한 서명

주요 사용 사례:

  • 인증(Authentication)
  • 정보 교환
  • 상태 유지(Stateless) 서버 구현

 

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using Microsoft.IdentityModel.Tokens;

public class JwtAuthenticationExample : System.Web.UI.Page
{
    private readonly string _secretKey = "your-256-bit-secret-key-here";
    
    // JWT 토큰 생성 메서드
    protected string GenerateJwtToken(string userId, string username, string role)
    {
        var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secretKey));
        var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);

        var claims = new[]
        {
            new Claim(JwtRegisteredClaimNames.Sub, userId),
            new Claim(JwtRegisteredClaimNames.UniqueName, username),
            new Claim(ClaimTypes.Role, role),
            new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()),
            new Claim(JwtRegisteredClaimNames.Iat, DateTime.UtcNow.ToString())
        };

        var token = new JwtSecurityToken(
            issuer: "your-issuer",
            audience: "your-audience",
            claims: claims,
            expires: DateTime.UtcNow.AddHours(1),
            signingCredentials: credentials
        );

        return new JwtSecurityTokenHandler().WriteToken(token);
    }

    // JWT 토큰 검증 메서드
    protected bool ValidateToken(string token)
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes(_secretKey);

        try
        {
            tokenHandler.ValidateToken(token, new TokenValidationParameters
            {
                ValidateIssuerSigningKey = true,
                IssuerSigningKey = new SymmetricSecurityKey(key),
                ValidateIssuer = true,
                ValidIssuer = "your-issuer",
                ValidateAudience = true,
                ValidAudience = "your-audience",
                ValidateLifetime = true,
                ClockSkew = TimeSpan.Zero
            }, out SecurityToken validatedToken);

            return true;
        }
        catch
        {
            return false;
        }
    }

    // 로그인 처리 예제
    protected void LoginButton_Click(object sender, EventArgs e)
    {
        // 실제 구현에서는 데이터베이스에서 사용자 검증
        string userId = "123";
        string username = "testuser";
        string role = "user";

        string token = GenerateJwtToken(userId, username, role);
        
        // 토큰을 클라이언트에 전달 (예: 쿠키로 저장)
        Response.Cookies.Add(new HttpCookie("jwt_token", token));
    }

    // 보호된 리소스 접근 예제
    protected void Page_Load(object sender, EventArgs e)
    {
        HttpCookie tokenCookie = Request.Cookies["jwt_token"];
        
        if (tokenCookie != null && ValidateToken(tokenCookie.Value))
        {
            // 인증된 사용자 처리
            // 보호된 리소스에 접근 허용
        }
        else
        {
            // 인증되지 않은 사용자 처리
            Response.Redirect("~/Login.aspx");
        }
    }
}

// Web.config 설정
/*
<configuration>
  <system.web>
    <authentication mode="None" />
  </system.web>
  <system.webServer>
    <modules>
      <remove name="FormsAuthentication" />
    </modules>
  </system.webServer>
</configuration>
*/

 

 

이 예제 코드의 주요 특징:

  1. 토큰 생성 (GenerateJwtToken):
    • 사용자 ID, 이름, 역할 등의 클레임 포함
    • 토큰 만료 시간 설정 (1시간)
    • HMAC SHA256 알고리즘으로 서명
  2. 토큰 검증 (ValidateToken):
    • 토큰의 서명 검증
    • 발급자(issuer)와 수신자(audience) 검증
    • 만료 시간 검증
  3. 실제 사용 예시:
    • 로그인 시 토큰 생성
    • 보호된 페이지 접근 시 토큰 검증

3번의 로그인 버튼 쪽이랑 페이지 로드 부분은 별로 신경 안 쓰셔도 됩니다. 

보통 클라이언트에서 위에 두개의 함수를 이용하여 토큰을 생성하고 검증하는 부분으로 사용되기 때문입니다.

 

구현 시 주의사항:

  1. 시크릿 키는 안전하게 보관해야 합니다
  2. HTTPS를 사용하여 통신해야 합니다
  3. 토큰 만료 시간을 적절히 설정해야 합니다
  4. 필요한 패키지를 설치해야 합니다:

 

 

 

JWT를 활용한 클라이언트 구현을 자세히 만들어보겠습니다.

<!-- Login.aspx -->
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Login.aspx.cs" Inherits="JwtExample.Login" %>

<!DOCTYPE html>
<html>
<head>
    <title>로그인</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <style>
        .login-container {
            max-width: 400px;
            margin: 50px auto;
            padding: 20px;
            border: 1px solid #ddd;
            border-radius: 5px;
        }
        .form-group {
            margin-bottom: 15px;
        }
        .form-group label {
            display: block;
            margin-bottom: 5px;
        }
        .form-group input {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        .error-message {
            color: red;
            margin-top: 10px;
            display: none;
        }
    </style>
</head>
<body>
    <div class="login-container">
        <h2>로그인</h2>
        <div class="form-group">
            <label for="username">사용자명:</label>
            <input type="text" id="username" name="username" />
        </div>
        <div class="form-group">
            <label for="password">비밀번호:</label>
            <input type="password" id="password" name="password" />
        </div>
        <button onclick="login()">로그인</button>
        <div id="errorMessage" class="error-message"></div>
    </div>

    <script>
        function login() {
            const username = $('#username').val();
            const password = $('#password').val();

            // 로그인 요청
            $.ajax({
                url: 'Login.aspx/LoginUser',
                type: 'POST',
                contentType: 'application/json',
                data: JSON.stringify({
                    username: username,
                    password: password
                }),
                success: function(response) {
                    if (response.d.success) {
                        // JWT 토큰 저장
                        localStorage.setItem('jwt_token', response.d.token);
                        // 사용자 정보 저장
                        localStorage.setItem('user_info', JSON.stringify(response.d.userInfo));
                        // 메인 페이지로 리다이렉트
                        window.location.href = 'Dashboard.aspx';
                    } else {
                        $('#errorMessage').text(response.d.message).show();
                    }
                },
                error: function() {
                    $('#errorMessage').text('로그인 중 오류가 발생했습니다.').show();
                }
            });
        }
    </script>
</body>
</html>

<!-- Dashboard.aspx -->
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Dashboard.aspx.cs" Inherits="JwtExample.Dashboard" %>

<!DOCTYPE html>
<html>
<head>
   <title>대시보드</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <style>
        .dashboard-container {
            max-width: 800px;
            margin: 20px auto;
            padding: 20px;
        }
        .header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 20px;
        }
        .user-info {
            margin-bottom: 20px;
        }
        .protected-content {
            border: 1px solid #ddd;
            padding: 20px;
            border-radius: 5px;
        }
    </style>
</head>
<body>
    <div class="dashboard-container">
        <div class="header">
            <h2>대시보드</h2>
            <button onclick="logout()">로그아웃</button>
        </div>
        <div id="userInfo" class="user-info"></div>
        <div class="protected-content">
            <h3>보호된 컨텐츠</h3>
            <div id="protectedData"></div>
        </div>
    </div>

    <script>
        // 페이지 로드 시 인증 확인
        $(document).ready(function() {
            checkAuth();
            loadUserInfo();
            loadProtectedData();
        });

        // 인증 확인
        function checkAuth() {
            const token = localStorage.getItem('jwt_token');
            if (!token) {
                window.location.href = 'Login.aspx';
                return;
            }
        }

        // 사용자 정보 로드
        function loadUserInfo() {
            const userInfo = JSON.parse(localStorage.getItem('user_info'));
            if (userInfo) {
                $('#userInfo').html(`
                    <p>사용자명: ${userInfo.username}</p>
                    <p>역할: ${userInfo.role}</p>
                `);
            }
        }

        // 보호된 데이터 로드
        function loadProtectedData() {
            $.ajax({
                url: 'Dashboard.aspx/GetProtectedData',
                type: 'POST',
                contentType: 'application/json',
                headers: {
                    'Authorization': 'Bearer ' + localStorage.getItem('jwt_token')
                },
                success: function(response) {
                    $('#protectedData').html(response.d.data);
                },
                error: function(xhr) {
                    if (xhr.status === 401) {
                        // 인증 실패 시 로그인 페이지로 리다이렉트
                        logout();
                    } else {
                        $('#protectedData').html('데이터 로드 중 오류가 발생했습니다.');
                    }
                }
            });
        }

        // 로그아웃
        function logout() {
            localStorage.removeItem('jwt_token');
            localStorage.removeItem('user_info');
            window.location.href = 'Login.aspx';
        }

        // API 요청을 위한 공통 함수
        function apiRequest(url, method, data) {
            return $.ajax({
                url: url,
                type: method,
                contentType: 'application/json',
                headers: {
                    'Authorization': 'Bearer ' + localStorage.getItem('jwt_token')
                },
                data: JSON.stringify(data)
            });
        }
    </script>
</body>
</html>
 

이 클라이언트 구현의 주요 특징과 기능을 설명드리겠습니다:

  1. 로그인 페이지 (Login.aspx):
    • 사용자명과 비밀번호 입력 폼 제공
    • AJAX를 사용한 비동기 로그인 처리
    • 로그인 성공 시 JWT 토큰을 localStorage에 저장
    • 에러 메시지 표시 기능
  2. 대시보드 페이지 (Dashboard.aspx):
    • 인증된 사용자만 접근 가능
    • 사용자 정보 표시
    • 보호된 데이터 로드 기능
    • 로그아웃 기능
  3. 주요 보안 기능:
    • 모든 API 요청에 JWT 토큰 포함
    • 인증되지 않은 접근 시 자동 리다이렉트
    • 토큰 만료 시 자동 로그아웃

구현 시 주의사항:

  1. localStorage 대신 보안이 더 필요한 경우 httpOnly 쿠키 사용을 고려해 볼 수 있습니다.
  2. 실제 운영 환경에서는 HTTPS 사용 필수입니다.
  3. CSRF 공격 방지를 위한 대책을 추가해 주는 게 좋습니다.

이상 JWT에 대하여 간단하게 알아보았습니다.

반응형

+ Recent posts