먼저 JWT의 기본 개념에 대해 설명드리겠습니다:
JWT는 당사자간에 정보를 JSON 객체로 안전하게 전송하기 위한 간단한 방법입니다. 이 정보는 디지털 서명되어 있어 신뢰할 수 있습니다.
JWT의 구조:
- Header: 토큰 유형과 사용된 서명 알고리즘
- Payload: 전달하려는 데이터(클레임)
- 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>
*/
이 예제 코드의 주요 특징:
- 토큰 생성 (GenerateJwtToken):
- 사용자 ID, 이름, 역할 등의 클레임 포함
- 토큰 만료 시간 설정 (1시간)
- HMAC SHA256 알고리즘으로 서명
- 토큰 검증 (ValidateToken):
- 토큰의 서명 검증
- 발급자(issuer)와 수신자(audience) 검증
- 만료 시간 검증
- 실제 사용 예시:
- 로그인 시 토큰 생성
- 보호된 페이지 접근 시 토큰 검증
3번의 로그인 버튼 쪽이랑 페이지 로드 부분은 별로 신경 안 쓰셔도 됩니다.
보통 클라이언트에서 위에 두개의 함수를 이용하여 토큰을 생성하고 검증하는 부분으로 사용되기 때문입니다.
구현 시 주의사항:
- 시크릿 키는 안전하게 보관해야 합니다
- HTTPS를 사용하여 통신해야 합니다
- 토큰 만료 시간을 적절히 설정해야 합니다
- 필요한 패키지를 설치해야 합니다:
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>
이 클라이언트 구현의 주요 특징과 기능을 설명드리겠습니다:
- 로그인 페이지 (Login.aspx):
- 사용자명과 비밀번호 입력 폼 제공
- AJAX를 사용한 비동기 로그인 처리
- 로그인 성공 시 JWT 토큰을 localStorage에 저장
- 에러 메시지 표시 기능
- 대시보드 페이지 (Dashboard.aspx):
- 인증된 사용자만 접근 가능
- 사용자 정보 표시
- 보호된 데이터 로드 기능
- 로그아웃 기능
- 주요 보안 기능:
- 모든 API 요청에 JWT 토큰 포함
- 인증되지 않은 접근 시 자동 리다이렉트
- 토큰 만료 시 자동 로그아웃
구현 시 주의사항:
- localStorage 대신 보안이 더 필요한 경우 httpOnly 쿠키 사용을 고려해 볼 수 있습니다.
- 실제 운영 환경에서는 HTTPS 사용 필수입니다.
- CSRF 공격 방지를 위한 대책을 추가해 주는 게 좋습니다.
이상 JWT에 대하여 간단하게 알아보았습니다.