728x90
반응형
728x170
[TestLibrary 프로젝트]
▶ Models/UserModel.cs
namespace TestLibrary.Models
{
/// <summary>
/// 사용자
/// </summary>
public class UserModel
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region ID - ID
/// <summary>
/// ID
/// </summary>
public int ID { get; set; }
#endregion
#region 사용자명 - UserName
/// <summary>
/// 사용자명
/// </summary>
public string UserName { get; set; }
#endregion
#region 패스워드 - Password
/// <summary>
/// 패스워드
/// </summary>
public string Password { get; set; }
#endregion
}
}
728x90
▶ Models/TestModel.cs
namespace TestLibrary.Models
{
/// <summary>
/// 테스트 모델
/// </summary>
public class TestModel
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 값 - Value
/// <summary>
/// 값
/// </summary>
public string Value { get; set; }
#endregion
}
}
300x250
[TestClient 프로젝트]
▶ Program.cs
using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
using TestLibrary.Models;
namespace TestClient
{
/// <summary>
/// 프로그램
/// </summary>
class Program
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Static
//////////////////////////////////////////////////////////////////////////////// Public
#region 프로그램 시작하기 - Main()
/// <summary>
/// 프로그램 시작하기
/// </summary>
static void Main()
{
UserModel user = new UserModel { UserName = "admin", Password = "1234"};
string token = Login("http://localhost:4035/api/authentication/login", user).GetAwaiter().GetResult();
Console.WriteLine($"JWT 토큰 : {token}");
Console.WriteLine("--------------------------------------------------");
TestModel test = new TestModel { Value = "테스트" };
string result = Test("http://localhost:4035/api/authentication/test", token, test).GetAwaiter().GetResult();
Console.WriteLine($"결과 : {result}");
Console.WriteLine("--------------------------------------------------");
Console.WriteLine("아무 키나 눌러 주시기 바랍니다.");
Console.ReadKey(true);
}
#endregion
#region HTTP 클라이언트 구하기 - GetHTTPClient(useCookie, baseAddress, tokenShema, token)
/// <summary>
/// HTTP 클라이언트 구하기
/// </summary>
/// <param name="useCookie">쿠키 사용 여부</param>
/// <param name="baseAddress">기본 주소</param>
/// <param name="tokenShema">토큰 스키마</param>
/// <param name="token">토큰</param>
/// <returns>HTTP 클라이언트</returns>
private static HttpClient GetHTTPClient(bool useCookie, string baseAddress, string tokenShema, string token)
{
HttpClient client;
if(useCookie)
{
HttpClientHandler httpClientHandler = new HttpClientHandler();
httpClientHandler.UseCookies = true;
httpClientHandler.CookieContainer = new CookieContainer();
client = new HttpClient(httpClientHandler);
}
else
{
client = new HttpClient();
}
client.DefaultRequestHeaders.Accept.Clear();
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
if(!string.IsNullOrWhiteSpace(baseAddress))
{
client.BaseAddress = new Uri(baseAddress);
}
if(!string.IsNullOrWhiteSpace(tokenShema))
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(tokenShema, token);
}
return client;
}
#endregion
#region 로그인하기 - Login(url, user)
/// <summary>
/// 로그인하기
/// </summary>
/// <param name="url">URL</param>
/// <param name="user">사용자</param>
/// <returns>JWT 토큰</returns>
private static async Task<string> Login(string url, UserModel user)
{
using(HttpClient client = GetHTTPClient(false, url, null, null))
{
var response = await client.PostAsJsonAsync<UserModel>(url, user);
if(response.IsSuccessStatusCode)
{
string token = await response.Content.ReadAsAsync<string>();
return token;
}
return null;
}
}
#endregion
#region 테스트하기 - Test(url, token, test)
/// <summary>
/// 테스트하기
/// </summary>
/// <param name="url">URL</param>
/// <param name="token">JWT 토큰</param>
/// <param name="test">테스트</param>
/// <returns>테스트 결과</returns>
private static async Task<string> Test(string url, string token, TestModel test)
{
using(HttpClient client = GetHTTPClient(false, url, "Bearer", token))
{
var response = await client.PostAsJsonAsync<TestModel>(url, test);
if(response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsAsync<string>();
return result;
}
else // http://로 호출한 경우 https://로 다시 호출한다.
{
string finalRequestURL = response.RequestMessage.RequestUri.AbsoluteUri;
if(finalRequestURL != url)
{
response = await client.PostAsJsonAsync<TestModel>(finalRequestURL, test);
if(response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsAsync<string>();
return result;
}
else
{
return null;
}
}
else
{
return null;
}
}
}
}
#endregion
}
}
[TestServer 프로젝트]
▶ Properties/launchSettings.json
{
"$schema" : "http://json.schemastore.org/launchsettings.json",
"iisSettings" :
{
"windowsAuthentication" : false,
"anonymousAuthentication" : true,
"iisExpress" :
{
"applicationUrl" : "http://localhost:4035",
"sslPort" : 44364
}
},
"profiles" :
{
"IIS Express" :
{
"commandName" : "IISExpress",
"launchBrowser" : false,
"environmentVariables" :
{
"ASPNETCORE_ENVIRONMENT" : "Development"
}
},
"TestProject" :
{
"commandName" : "Project",
"launchBrowser" : false,
"applicationUrl" : "https://localhost:5001;http://localhost:5000",
"environmentVariables" :
{
"ASPNETCORE_ENVIRONMENT" : "Development"
}
}
}
}
▶ appsettings.json
{
"ConnectionStrings" :
{
"DefaultConnection" : "Data Source=(localdb)\\MSSQLLocalDB;Initial Catalog=TestDB;Integrated Security=True;"
},
"Logging" :
{
"LogLevel" :
{
"Default" : "Information",
"Microsoft" : "Warning",
"Microsoft.Hosting.Lifetime" : "Information"
}
},
"AllowedHosts": "*"
}
▶ Database/DatabaseContext.cs
using Microsoft.EntityFrameworkCore;
using TestLibrary.Models;
namespace TestServer.Database
{
/// <summary>
/// 데이터베이스 컨텍스트
/// </summary>
public class DatabaseContext : DbContext
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 사용자 테이블 - User
/// <summary>
/// 사용자 테이블
/// </summary>
public DbSet<UserModel> User { get; set; }
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - DatabaseContext(options)
/// <summary>
/// 생성자
/// </summary>
/// <param name="options">DB 컨텍스트 옵션</param>
public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options)
{
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Protected
#region 모델 생성시 처리하기 - OnModelCreating(builder)
/// <summary>
/// 모델 생성시 처리하기
/// </summary>
/// <param name="builder">모델 빌더</param>
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.Entity<UserModel>().HasData
(
new UserModel { ID = 1, UserName = "admin" , Password = "1234" },
new UserModel { ID = 2, UserName = "tester", Password = "1234" }
);
}
#endregion
}
}
※ Enitity Framework Core를 사용해 데이터베이스를 액세스하므로 appsettings.json 파일의 DefaultConnection 항목에 설정한 DB 연결 문자열에 맞추어 데이터베이스를 생성하고 [패키지 관리자 콘솔]에서 아래 명령을 실행한다.
Update-Database
▶ Services/ITokenBuilder.cs
namespace TestServer.Services
{
/// <summary>
/// 토큰 빌더 인터페이스
/// </summary>
public interface ITokenBuilder
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
#region 토큰 만들기 - BuildToken(userName)
/// <summary>
/// 토큰 만들기
/// </summary>
/// <param name="userName">사용자명</param>
/// <returns>JWT 토큰</returns>
string BuildToken(string userName);
#endregion
}
}
▶ Services/TokenBuilder.cs
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace TestServer.Services
{
/// <summary>
/// 토큰 빌더
/// </summary>
public class TokenBuilder : ITokenBuilder
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 토큰 만들기 - BuildToken(userName)
/// <summary>
/// 토큰 만들기
/// </summary>
/// <param name="userName">사용자명</param>
/// <returns>토큰</returns>
public string BuildToken(string userName)
{
SymmetricSecurityKey securityKey = new SymmetricSecurityKey
(
Encoding.UTF8.GetBytes("placeholder-key-that-is-long-enough-for-sha256")
);
SigningCredentials signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
Claim[] claimArray = new Claim[]
{
new Claim(JwtRegisteredClaimNames.Sub, userName)
};
JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(claims : claimArray, signingCredentials : signingCredentials);
string token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
return token;
}
#endregion
}
}
▶ Controllers/AuthenticationController.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using TestLibrary.Models;
using TestServer.Database;
using TestServer.Services;
namespace TestServer.Controllers
{
/// <summary>
/// 인증 컨트롤러
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class AuthenticationController : ControllerBase
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Field
////////////////////////////////////////////////////////////////////////////////////////// Private
#region Field
/// <summary>
/// 데이터베이스 컨텍스트
/// </summary>
private readonly DatabaseContext databaseContext;
/// <summary>
/// 토큰 빌더
/// </summary>
private readonly ITokenBuilder tokenBuilder;
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - AuthenticationController(databaseContext, tokenBuilder)
/// <summary>
/// 생성자
/// </summary>
/// <param name="databaseContext">데이터베이스 컨텍스트</param>
/// <param name="tokenBuilder">토큰 빌더</param>
public AuthenticationController(DatabaseContext databaseContext, ITokenBuilder tokenBuilder)
{
this.databaseContext = databaseContext;
this.tokenBuilder = tokenBuilder;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 로그인하기 - Login(user)
/// <summary>
/// 로그인하기
/// </summary>
/// <param name="user">사용자</param>
/// <returns>액션 결과 태스크</returns>
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody]UserModel user)
{
UserModel existingUser = await this.databaseContext.User.SingleOrDefaultAsync(u => u.UserName == user.UserName);
if(existingUser == null)
{
return NotFound("USER NOT FOUND");
}
bool isValid = existingUser.Password == user.Password;
if(!isValid)
{
return BadRequest("COULD NOT AUTHENTICATE USER");
}
string token = this.tokenBuilder.BuildToken(user.UserName);
return Ok(token);
}
#endregion
#region 테스트하기 - Test(test)
/// <summary>
/// 테스트하기
/// </summary>
/// <param name="test">테스트</param>
/// <returns>액션 결과</returns>
[HttpPost("test")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<IActionResult> Test([FromBody]TestModel test)
{
Claim userNameClaim = User.Claims.SingleOrDefault();
if(userNameClaim == null)
{
return Unauthorized();
}
bool exist = await this.databaseContext.User.AnyAsync(user => user.UserName == userNameClaim.Value);
if(!exist)
{
return Unauthorized();
}
return Ok($"SUCCESS : {DateTime.Now:yyyy-MM-dd HH:mm:ss} {test.Value}");
}
#endregion
}
}
▶ Startup.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Text;
using TestServer.Database;
using TestServer.Services;
namespace TestServer
{
/// <summary>
/// 시작
/// </summary>
public class Startup
{
//////////////////////////////////////////////////////////////////////////////////////////////////// Property
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 구성 - Configuration
/// <summary>
/// 구성
/// </summary>
public IConfiguration Configuration { get; }
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Constructor
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 생성자 - Startup(configuration)
/// <summary>
/// 생성자
/// </summary>
/// <param name="configuration">구성</param>
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
#endregion
//////////////////////////////////////////////////////////////////////////////////////////////////// Method
////////////////////////////////////////////////////////////////////////////////////////// Public
#region 서비스 컬렉션 구성하기 - ConfigureServices(services)
/// <summary>
/// 서비스 컬렉션 구성하기
/// </summary>
/// <param name="services">서비스 컬렉션</param>
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<DatabaseContext>
(
options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
}
);
services.AddAuthentication
(
options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}
)
.AddJwtBearer
(
options =>
{
options.RequireHttpsMetadata = true;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
IssuerSigningKey = new SymmetricSecurityKey
(
Encoding.UTF8.GetBytes("placeholder-key-that-is-long-enough-for-sha256")
),
ValidateAudience = false,
ValidateIssuer = false,
ValidateLifetime = false,
RequireExpirationTime = false,
ClockSkew = TimeSpan.Zero,
ValidateIssuerSigningKey = true
};
}
);
services.AddScoped<ITokenBuilder, TokenBuilder>();
services.AddControllers();
}
#endregion
#region 구성하기 - Configure(app, environment)
/// <summary>
/// 구성하기
/// </summary>
/// <param name="app">애플리케이션 빌더</param>
/// <param name="environment">웹 로스트 환경</param>
public void Configure(IApplicationBuilder app, IWebHostEnvironment environment)
{
if(environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints
(
endpoints =>
{
endpoints.MapControllers();
}
);
}
#endregion
}
}
728x90
반응형
그리드형(광고전용)
'C# > ASP.NET MVC' 카테고리의 다른 글
[C#/ASP.NET MVC] 누겟 설치 : Microsoft.AspNetCore.Session (0) | 2020.10.14 |
---|---|
[C#/ASP.NET MVC] Kestrel 사용시 업로드 파일 크기 설정하기 (2) (0) | 2020.10.14 |
[C#/ASP.NET MVC] Kestrel 사용시 업로드 파일 크기 설정하기 (1) (0) | 2020.10.14 |
[C#/ASP.NET MVC] IIS Express 사용시 업로드 파일 크기 설정하기 (0) | 2020.10.14 |
[C#/ASP.NET MVC] 파일 업로드 액션 메소드에서 파일 정보를 전달받는 매개 변수의 데이터 타입 설정하기 (0) | 2020.10.14 |
[C#/ASP.NET MVC] JWT(Json Web Token) 인증 사용하기 (0) | 2020.10.13 |
[C#/ASP.NET MVC] 쿠키(Cookie) 인증 사용하기 (0) | 2020.10.12 |
[C#/ASP.NET MVC] 누겟 설치 : Microsoft.AspNetCore.Authentication.Cookies (0) | 2020.10.11 |
[C#/ASP.NET MVC] RequestFormLimitsAttribute 클래스 : 폼에서 특정 제한 설정하기 (0) | 2020.10.11 |
[C#/ASP.NET MVC] web.config 파일에서 최대 허용 컨텐트 길이 설정하기 (0) | 2020.10.11 |
[C#/ASP.NET MVC] KestrelServerOptions 클래스 : Limits 속성을 사용해 최대 요청 BODY 크기 설정하기 (0) | 2020.10.11 |
댓글을 달아 주세요