■ OAuth 인증을 사용하는 방법을 보여준다.
[TestServer 프로젝트]
▶ 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:9389", "sslPort" : 44373 } }, "profiles" : { "IIS Express" : { "commandName" : "IISExpress", "launchBrowser" : true, "environmentVariables" : { "ASPNETCORE_ENVIRONMENT" : "Development" } }, "TestServer" : { "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 TestServer { /// <summary> /// 상수 헬퍼 /// </summary> public class ConstantHelper { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Public #region Field /// <summary> /// 발급자 /// </summary> public const string Issuer = Audiance; /// <summary> /// 청중 /// </summary> public const string Audiance = "https://localhost:44373/"; /// <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 |
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.Text; using System.Threading.Tasks; namespace TestServer { /// <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() { 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/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 |
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 TestServer.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 = "BABABABABA"; // 사용자명과 패스워드에 따라 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) /// <summary> /// 토큰 페이지 처리하기 /// </summary> /// <param name="grant_type">그랜트 타입</param> /// <param name="code">코드</param> /// <param name="redirect_uri">재전송 URI</param> /// <param name="client_id">클라이언트 ID</param> /// <returns></returns> public async Task<IActionResult> Token(string grant_type, string code, string redirect_uri, string client_id) { // code 값에 따라 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 : DateTime.Now.AddHours(1), signingCredentials ); string access_token = new JwtSecurityTokenHandler().WriteToken(token); var responseObject = new { access_token, token_type = "Bearer", 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 } } |
▶ Views/OAuth/Loging.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> |
[TestClient 프로젝트]
▶ 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:13283", "sslPort" : 44303 } }, "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 |
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:44373/oauth/login"; options.TokenEndpoint = "https://localhost:44373/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.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 |
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace TestClient.Controllers { /// <summary> /// 홈 컨트롤러 /// </summary> public class HomeController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 인덱스 페이치 처리하기 - Index() /// <summary> /// 인덱스 페이치 처리하기 /// </summary> /// <returns>액션 결과</returns> public IActionResult Index() { return View(); } #endregion #region 비밀 페이지 처리하기 - Secret() /// <summary> /// 비밀 페이지 처리하기 /// </summary> /// <returns>액션 결과</returns> [Authorize] public IActionResult Secret() { return View(); } #endregion } } |
▶ Views/Home/Index.cshtml
1 2 3 4 |
<h1>클라이언트 인덱스 페이지</h1> <hr /> |
▶ Views/Home/Secret.cshtml
1 2 3 4 |
<h1>클라이언트 비밀 페이지</h1> <hr /> |