첨부 실행 코드는 나눔고딕코딩 폰트를 사용합니다.

728x90
반응형
728x170

TestSolution.zip
다운로드

[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
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요