■ OAuth 인증을 사용하는 방법을 보여준다. (액세스 토큰 갱신 추가)
[TestAuthorizationServer 프로젝트]
▶ Properties/launchSettings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
{ "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:50000", "sslPort" : 44300 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestAuthorizationServer" : { "commandName" : "Project", "launchBrowser" : true, "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ ConstantHelper.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
namespace TestAuthorizationServer { /// <summary> /// 상수 헬퍼 /// </summary> public class ConstantHelper { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Public #region Field /// <summary> /// 발급자 /// </summary> public const string Issuer = Audiance; /// <summary> /// 청중 /// </summary> public const string Audiance = "https://localhost:44300/"; /// <summary> /// 패스워드 /// </summary> public const string Password = "not_too_short_secret_otherwise_it_might_error"; #endregion } } |
▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 |
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.IdentityModel.Tokens; using System; using System.Text; using System.Threading.Tasks; namespace TestAuthorizationServer { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddAuthentication("OAuth") .AddJwtBearer ( "OAuth", options => { byte[] passwordByteArray = Encoding.UTF8.GetBytes(ConstantHelper.Password); SymmetricSecurityKey key = new SymmetricSecurityKey(passwordByteArray); options.Events = new JwtBearerEvents() { OnMessageReceived = context => { if(context.Request.Query.ContainsKey("access_token")) { context.Token = context.Request.Query["access_token"]; } return Task.CompletedTask; } }; options.TokenValidationParameters = new TokenValidationParameters() { ClockSkew = TimeSpan.Zero, ValidIssuer = ConstantHelper.Issuer, ValidAudience = ConstantHelper.Audiance, IssuerSigningKey = key }; } ); services.AddControllersWithViews() .AddRazorRuntimeCompilation(); } #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.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapDefaultControllerRoute(); } ); } #endregion } } |
▶ Controllers/HomeController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using Microsoft.AspNetCore.Mvc; namespace TestAuthorizationServer.Controllers { /// <summary> /// 홈 컨트롤러 /// </summary> public class HomeController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 인덱스 페이지 처리하기 - Index() /// <summary> /// 인덱스 페이지 처리하기 /// </summary> /// <returns>액션 결과</returns> public IActionResult Index() { return View(); } #endregion } } |
▶ Controllers/OAuthController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Security.Claims; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; namespace TestAuthorizationServer.Controllers { /// <summary> /// OAUTH 컨트롤러 /// </summary> public class OAuthController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 로그인 페이지 처리하기 - Login(client_id, scope, response_type, redirect_uri, state) /// <summary> /// 로그인 페이지 처리하기 /// </summary> /// <param name="client_id">클라이언트 ID</param> /// <param name="scope">범위</param> /// <param name="response_type">인증 플로우 타입</param> /// <param name="redirect_uri">재전송 URI</param> /// <param name="state">상태</param> /// <returns>액션 결과</returns> [HttpGet] public IActionResult Login(string client_id, string scope, string response_type, string redirect_uri, string state) { QueryBuilder queryBuilder = new QueryBuilder(); queryBuilder.Add("redirectURI", redirect_uri); queryBuilder.Add("state" , state ); return View(model : queryBuilder.ToString()); } #endregion #region 로그인 페이지 처리하기 - Login(userName, password, redirectURI, state) /// <summary> /// 로그인 페이지 처리하기 /// </summary> /// <param name="userName">사용자명</param> /// <param name="password">패스워드</param> /// <param name="redirectURI">재전송 URI</param> /// <param name="state">상태</param> /// <returns>액션 결과</returns> [HttpPost] public IActionResult Login(string userName, string password, string redirectURI, string state) { const string code = "TESTCODE"; // 사용자명과 패스워드에 따라 code를 설정한다. QueryBuilder queryBuilder = new QueryBuilder(); queryBuilder.Add("code" , code ); queryBuilder.Add("state", state); return Redirect($"{redirectURI}{queryBuilder.ToString()}"); } #endregion #region 토큰 페이지 처리하기 - Token(grant_type, code, redirect_uri, client_id, refresh_token) /// <summary> /// 토큰 페이지 처리하기 /// </summary> /// <param name="grant_type">그랜트 타입</param> /// <param name="code">코드</param> /// <param name="redirect_uri">재전송 URI</param> /// <param name="client_id">클라이언트 ID</param> /// <param name="refresh_token">갱신 토큰</param> /// <returns></returns> public async Task<IActionResult> Token(string grant_type, string code, string redirect_uri, string client_id, string refresh_token) { // 그랜트 타입, 코드, 갱신 토큰 값에 따라 JWT를 발행한다. List<Claim> claimList = new List<Claim>() { new Claim(JwtRegisteredClaimNames.Sub , "ID0001" ), new Claim(JwtRegisteredClaimNames.Birthdate, "1990-01-01" ), new Claim(JwtRegisteredClaimNames.Email , "test@daum.net"), new Claim(JwtRegisteredClaimNames.Gender , "Male" ), new Claim("CompanyGroup" , "영업1그룹" ), new Claim("CompanyDepartment" , "영업1팀" ), new Claim("CompanyTitle" , "대리" ), new Claim("EmployeeID" , "EMP0001" ), new Claim("EmployeeName" , "홍길동" ) }; byte[] passwordByteArray = Encoding.UTF8.GetBytes(ConstantHelper.Password); SymmetricSecurityKey key = new SymmetricSecurityKey(passwordByteArray); string algorithm = SecurityAlgorithms.HmacSha256; SigningCredentials signingCredentials = new SigningCredentials(key, algorithm); JwtSecurityToken token = new JwtSecurityToken ( ConstantHelper.Issuer, ConstantHelper.Audiance, claimList, notBefore : DateTime.Now, expires : grant_type == "refresh_token" ? DateTime.Now.AddMinutes(5) : DateTime.Now.AddMilliseconds(1), // 테스트용 코드 signingCredentials ); string access_token = new JwtSecurityTokenHandler().WriteToken(token); var responseObject = new { access_token, token_type = "Bearer", refresh_token = "REFRESH_TOKEN_SAMPLE_VALUE", raw_claim = "oauthTurorial" }; string responseJSON = JsonConvert.SerializeObject(responseObject); byte[] responseByteArray = Encoding.UTF8.GetBytes(responseJSON); await Response.Body.WriteAsync(responseByteArray, 0, responseByteArray.Length); return Redirect(redirect_uri); } #endregion #region 검증 페이지 처리하기 - Validate() /// <summary> /// 검증 페이지 처리하기 /// </summary> /// <returns>액션 결과</returns> [Authorize] public IActionResult Validate() { if(HttpContext.Request.Query.TryGetValue("access_token", out var accessToken)) { return Ok(); } return BadRequest(); } #endregion } } |
▶ Controllers/SecretController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System; namespace TestAuthorizationServer.Controllers { /// <summary> /// 비밀 컨트롤러 /// </summary> public class SecretController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 인덱스 페이지 처리하기 - Index() /// <summary> /// 인덱스 페이지 처리하기 /// </summary> /// <returns>문자열</returns> [Authorize] public IActionResult Index() { return Json($"인증 서버 비밀 메시지 {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); } #endregion } } |
▶ Views/Home/Index.cshtml
1 2 3 4 |
<h1>인증 서버 인덱스 페이지</h1> <hr /> |
▶ Views/OAuth/Login.cshtml
1 2 3 4 5 6 7 8 9 10 11 12 13 |
@model string @{ var url = $"/OAuth/Login{Model}"; } <h1>인증 서버 로그인 페이지</h1> <hr /> <form action="@url" method="post"> <p>사용자명 <input type="text" name="userName" /></p> <p>패스워드 <input type="password" name="password" /></p> <p><input type="submit" value="로그인" /></p> </form> |
[TestAPIServer 프로젝트]
▶ Properties/launchSettings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
{ "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:50010", "sslPort" : 44310 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestAPIServer" : { "commandName" : "Project", "launchBrowser" : true, "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ Requirement/JWTRequirement.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 |
using Microsoft.AspNetCore.Authorization; namespace TestAPIServer.Requirement { /// <summary> /// JWT 요청 /// </summary> public class JWTRequirement : IAuthorizationRequirement { } } |
▶ Requirement/JWTRequirementAuthorizationHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using System.Net; using System.Net.Http; using System.Threading.Tasks; namespace TestAPIServer.Requirement { /// <summary> /// JWT 요청 권한 핸들러 /// </summary> public class JWTRequirementAuthorizationHandler : AuthorizationHandler<JWTRequirement> { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// HTTP 클라이언트 /// </summary> private readonly HttpClient httpClient; /// <summary> /// HTTP 컨텍스트 /// </summary> private readonly HttpContext httpContext; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - JWTRequirementAuthorizationHandler(factory, accessor) /// <summary> /// 생성자 /// </summary> /// <param name="factory">HTTP 클라이언트 팩토리</param> /// <param name="accessor">HTTP 컨텍스트 접근자</param> public JWTRequirementAuthorizationHandler(IHttpClientFactory factory, IHttpContextAccessor accessor) { this.httpClient = factory.CreateClient(); this.httpContext = accessor.HttpContext; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 요청 처리하기 (비동기) - HandleRequirementAsync(context, requirement) /// <summary> /// 요청 처리하기 (비동기) /// </summary> /// <param name="context">컨텍스트</param> /// <param name="requirement">요청</param> /// <returns>태스크</returns> protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, JWTRequirement requirement) { if(this.httpContext.Request.Headers.TryGetValue("Authorization", out var authorizationHeaderValue)) { string accessToken = authorizationHeaderValue.ToString().Split(' ')[1]; string url = $"https://localhost:44300/oauth/validate?access_token={accessToken}"; HttpResponseMessage response = await this.httpClient.GetAsync(url); if(response.StatusCode == HttpStatusCode.OK) { context.Succeed(requirement); } } } #endregion } } |
▶ CustomAuthenticationHandler.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
using Microsoft.AspNetCore.Authentication; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using System.Text.Encodings.Web; using System.Threading.Tasks; namespace TestAPIServer { /// <summary> /// 커스텀 인증 핸들러 /// </summary> public class CustomAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions> { //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - CustomAuthenticationHandler(options, loggerFactory, urlEncoder, systemClock) /// <summary> /// 생성자 /// </summary> /// <param name="options">옵션</param> /// <param name="loggerFactory">로그 기록기 팩토리</param> /// <param name="urlEncoder">URL 인코더</param> /// <param name="systemClock">시스템 시계</param> public CustomAuthenticationHandler ( IOptionsMonitor<AuthenticationSchemeOptions> options, ILoggerFactory loggerFactory, UrlEncoder urlEncoder, ISystemClock systemClock ) : base(options, loggerFactory, urlEncoder, systemClock) { } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Protected #region 인증 처리하기 (비동기) - HandleAuthenticateAsync() /// <summary> /// 인증 처리하기 (비동기) /// </summary> /// <returns>인증 결과</returns> protected override Task<AuthenticateResult> HandleAuthenticateAsync() { return Task.FromResult(AuthenticateResult.Fail("Failed Authentication")); } #endregion } } |
▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 |
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using TestAPIServer.Requirement; namespace TestAPIServer { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddAuthentication("DefaultAuth") .AddScheme<AuthenticationSchemeOptions, CustomAuthenticationHandler>("DefaultAuth", null); services.AddAuthorization ( options => { var defaultAuthBuilder = new AuthorizationPolicyBuilder(); var defaultAuthPolicy = defaultAuthBuilder .AddRequirements(new JWTRequirement()) .Build(); options.DefaultPolicy = defaultAuthPolicy; } ); services.AddScoped<IAuthorizationHandler, JWTRequirementAuthorizationHandler>(); services.AddHttpClient() .AddHttpContextAccessor(); services.AddControllersWithViews(); } #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.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapDefaultControllerRoute(); } ); } #endregion } } |
▶ Controllers/HomeController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using Microsoft.AspNetCore.Mvc; namespace TestAPIServer.Controllers { /// <summary> /// 홈 컨트롤러 /// </summary> public class HomeController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 인덱스 페이지 처리하기 - Index() /// <summary> /// 인덱스 페이지 처리하기 /// </summary> /// <returns>액션 결과</returns> public IActionResult Index() { return View(); } #endregion } } |
▶ Controllers/SecretController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System; namespace TestAPIServer.Controllers { /// <summary> /// 비밀 컨트롤러 /// </summary> public class SecretController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 인덱스 페이지 구하기 - Index() /// <summary> /// 인덱스 페이지 구하기 /// </summary> /// <returns>문자열</returns> [Authorize] public IActionResult Index() { return Json($"API 서버 비밀 메시지 {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); } #endregion } } |
▶ Views/Home/Index.cshtml
1 2 3 4 |
<h1>API 서버 인덱스 페이지</h1> <hr /> |
[TestClient 프로젝트]
▶ Properties/launchSettings.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
{ "iisSettings" : { "windowsAuthentication" : false, "anonymousAuthentication" : true, "iisExpress" : { "applicationUrl" : "http://localhost:50030", "sslPort" : 44320 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestClient" : { "commandName" : "Project", "launchBrowser" : true, "applicationUrl" : "https://localhost:5001;http://localhost:5000", "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } } } } |
▶ Startup.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
using Microsoft.AspNetCore.Authentication.OAuth; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Collections.Generic; using System.Security.Claims; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using IdentityModel; namespace TestClient { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddAuthentication ( options => { options.DefaultAuthenticateScheme = "TestClientCookie"; options.DefaultSignInScheme = "TestClientCookie"; options.DefaultChallengeScheme = "TestServer"; } ) .AddCookie("TestClientCookie") .AddOAuth ( "TestServer", options => { options.ClientId = "CLIENTID0001"; options.ClientSecret = "CLIENTSECRET0001"; options.CallbackPath = "/oauth/callback"; options.AuthorizationEndpoint = "https://localhost:44300/oauth/login"; options.TokenEndpoint = "https://localhost:44300/oauth/token"; options.SaveTokens = true; options.Events = new OAuthEvents() { OnCreatingTicket = context => { string accessToken = context.AccessToken; string payload = accessToken.Split('.')[1]; byte[] payloadByteArray = Base64Url.Decode(payload); string json = Encoding.UTF8.GetString(payloadByteArray); Dictionary<string, string> claimDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); foreach(KeyValuePair<string, string> claimKeyValuePair in claimDictionary) { context.Identity.AddClaim(new Claim(claimKeyValuePair.Key, claimKeyValuePair.Value)); } return Task.CompletedTask; } }; } ); services.AddHttpClient(); services.AddControllersWithViews() .AddRazorRuntimeCompilation(); } #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.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints ( endpoints => { endpoints.MapDefaultControllerRoute(); } ); } #endregion } } |
▶ Controllers/HomeController.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; namespace TestClient.Controllers { /// <summary> /// 홈 컨트롤러 /// </summary> public class HomeController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// HTTP 클라이언트 팩토리 /// </summary> private readonly IHttpClientFactory factory; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - HomeController(factory) /// <summary> /// 생성자 /// </summary> /// <param name="factory">HTTP 클라이언트 팩토리</param> public HomeController(IHttpClientFactory factory) { this.factory = factory; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 인덱스 페이치 처리하기 - Index() /// <summary> /// 인덱스 페이치 처리하기 /// </summary> /// <returns>액션 결과</returns> public IActionResult Index() { return View(); } #endregion #region 비밀 페이지 처리하기 - Secret() /// <summary> /// 비밀 페이지 처리하기 /// </summary> /// <returns>액션 결과</returns> [Authorize] public async Task<IActionResult> Secret() { HttpResponseMessage authorizationServerResponse = await SecureRequest(() => GETSecureRequest("https://localhost:44300/secret/index")); if(authorizationServerResponse.IsSuccessStatusCode) { string result = await authorizationServerResponse.Content.ReadAsAsync<string>(); ViewData["AuthorizationServerData"] = result; } HttpResponseMessage apiServerResponse = await SecureRequest(() => GETSecureRequest("https://localhost:44310/secret/index")); if(apiServerResponse.IsSuccessStatusCode) { string result = await apiServerResponse.Content.ReadAsAsync<string>(); ViewData["APIServerData"] = result; } return View(); } #endregion ////////////////////////////////////////////////////////////////////////////////////////// Private #region GET 보안 요청하기 - GETSecureRequest(url) /// <summary> /// GET 보안 요청하기 /// </summary> /// <param name="url">URL</param> /// <returns>HTTP 응답 메시지</returns> private async Task<HttpResponseMessage> GETSecureRequest(string url) { string accessToken = await HttpContext.GetTokenAsync("access_token"); HttpClient client = this.factory.CreateClient(); client.DefaultRequestHeaders.Add("Authorization", $"Bearer {accessToken}"); HttpResponseMessage response = await client.GetAsync(url); return response; } #endregion #region 액세스 토큰 갱신하기 - RefreshAccessToken() /// <summary> /// 액세스 토큰 갱신하기 /// </summary> /// <returns>태스크</returns> private async Task RefreshAccessToken() { string refreshToken = await HttpContext.GetTokenAsync("refresh_token"); HttpClient httpClient = this.factory.CreateClient(); Dictionary<string, string> requestDictionary = new Dictionary<string, string> { ["grant_type" ] = "refresh_token", ["refresh_token"] = refreshToken }; string requestURL = "https://localhost:44300/oauth/token"; HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, requestURL) { Content = new FormUrlEncodedContent(requestDictionary) }; string sourceAuthorization = "username:password"; byte[] sourceAuthorizationByteArray = Encoding.UTF8.GetBytes(sourceAuthorization); var targetAuthorization = Convert.ToBase64String(sourceAuthorizationByteArray); request.Headers.Add("Authorization", $"Basic {targetAuthorization}"); HttpResponseMessage response = await httpClient.SendAsync(request); string responseJSON = await response.Content.ReadAsStringAsync(); Dictionary<string, string> responseDictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(responseJSON); string newAccessToken = responseDictionary.GetValueOrDefault("access_token" ); string newRefreshToken = responseDictionary.GetValueOrDefault("refresh_token"); AuthenticateResult authenticateResult = await HttpContext.AuthenticateAsync("TestClientCookie"); authenticateResult.Properties.UpdateTokenValue("access_token" , newAccessToken ); authenticateResult.Properties.UpdateTokenValue("refresh_token", newRefreshToken); await HttpContext.SignInAsync("TestClientCookie", authenticateResult.Principal, authenticateResult.Properties); } #endregion #region 보안 요청하기 - SecureRequest(requestFunction) /// <summary> /// 보안 요청하기 /// </summary> /// <param name="requestFunction">요청 함수</param> /// <returns>HTTP 응답 메시지 태스크</returns> private async Task<HttpResponseMessage> SecureRequest(Func<Task<HttpResponseMessage>> requestFunction) { HttpResponseMessage response = await requestFunction(); if(response.StatusCode == HttpStatusCode.Unauthorized) { await RefreshAccessToken(); response = await requestFunction(); } return response; } #endregion } } |
▶ Views/Home/Index.cshtml
1 2 3 4 |
<h1>클라이언트 인덱스 페이지</h1> <hr /> |
▶ Views/Home/Secret.cshtml
1 2 3 4 5 6 |
<h1>클라이언트 비밀 페이지</h1> <hr /> <p>인증 서버 데이터 : @ViewData["AuthorizationServerData"]</p> <p>API 서버 데이터 : @ViewData["APIServerData"]</p> |