■ IdentityServer4 신원 서버에서 사용자 로그인을 인증하는 방법을 보여준다.
[TestIdentityServer 프로젝트]
▶ 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" } } } } |
▶ Program.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 |
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; namespace TestIdentityServer { /// <summary> /// 프로그램 /// </summary> public class Program { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 프로그램 시작하기 - Main(argumentArray) /// <summary> /// 프로그램 시작하기 /// </summary> /// <param name="argumentArray">인자 배열</param> public static void Main(string[] argumentArray) { IHost host = CreateHostBuilder(argumentArray).Build(); using(IServiceScope scope = host.Services.CreateScope()) { UserManager<IdentityUser> userManager = scope.ServiceProvider.GetRequiredService<UserManager<IdentityUser>>(); IdentityUser user = new IdentityUser("alice"); userManager.CreateAsync(user, "alice").GetAwaiter(); } host.Run(); } #endregion #region 호스트 빌더 생성하기 - CreateHostBuilder(argumentArray) /// <summary> /// 호스트 빌더 생성하기 /// </summary> /// <param name="argumentArray">인자 배열</param> /// <returns>호스트 빌더</returns> public static IHostBuilder CreateHostBuilder(string[] argumentArray) => Host.CreateDefaultBuilder(argumentArray) .ConfigureWebHostDefaults ( builder => { builder.UseStartup<Startup>(); } ); #endregion } } |
▶ Configuration.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 |
using System.Collections.Generic; using IdentityModel; using IdentityServer4; using IdentityServer4.Models; namespace TestIdentityServer { /// <summary> /// 구성 /// </summary> public static class Configuration { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Static //////////////////////////////////////////////////////////////////////////////// Public #region 신원 리소스 리스트 구하기 - GetIdentityResourceList() /// <summary> /// 신원 리소스 리스트 구하기 /// </summary> /// <returns>신원 리소스 리스트</returns> public static List<IdentityResource> GetIdentityResourceList() { return new List<IdentityResource>() { new IdentityResources.OpenId(), new IdentityResources.Profile() }; } #endregion #region API 범위 리스트 구하기 - GetAPIScopeList() /// <summary> /// API 범위 리스트 구하기 /// </summary> /// <returns>API 범위 리스트</returns> public static List<ApiScope> GetAPIScopeList() { return new List<ApiScope> { new ApiScope("API1", "API 1"), new ApiScope("API2", "API 2") }; } #endregion #region 클라이언트 리스트 구하기 - GetClientList() /// <summary> /// 클라이언트 리스트 구하기 /// </summary> /// <returns>클라이언트 리스트</returns> public static List<Client> GetClientList() { return new List<Client> { new Client { AllowedGrantTypes = GrantTypes.ClientCredentials, ClientId = "CLIENTID0002", ClientSecrets = { new Secret("CLIENTSECRET0002".ToSha256()) }, AllowedScopes = new List<string> { "API1" } }, new Client { AllowedGrantTypes = GrantTypes.Code, ClientId = "CLIENTID0003", ClientSecrets = { new Secret("CLIENTSECRET0003".ToSha256()) }, RedirectUris = { "https://localhost:44330/signin-oidc" }, AllowedScopes = new List<string> { "API1", "API2", IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile }, RequireConsent = false } }; } #endregion } } |
▶ Data/DatabaseContext.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.Identity.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; namespace TestIdentityServer.Data { /// <summary> /// 데이터베이스 컨텍스트 /// </summary> public class DatabaseContext : IdentityDbContext { //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - DatabaseContext(options) /// <summary> /// 생성자 /// </summary> /// <param name="options">데이터베이스 컨텍스트 옵션</param> public DatabaseContext(DbContextOptions<DatabaseContext> options) : base(options) { } #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 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using TestIdentityServer.Data; namespace TestIdentityServer { /// <summary> /// 시작 /// </summary> public class Startup { //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 서비스 컬렉션 구성하기 - ConfigureServices(services) /// <summary> /// 서비스 컬렉션 구성하기 /// </summary> /// <param name="services">서비스 컬렉션</param> public void ConfigureServices(IServiceCollection services) { services.AddDbContext<DatabaseContext>(options => { options.UseInMemoryDatabase("MemoryDB"); }); services.AddIdentity<IdentityUser, IdentityRole> ( options => { options.Password.RequiredLength = 4; options.Password.RequireDigit = false; options.Password.RequireNonAlphanumeric = false; options.Password.RequireUppercase = false; } ) .AddEntityFrameworkStores<DatabaseContext>() .AddDefaultTokenProviders(); services.ConfigureApplicationCookie ( options => { options.Cookie.Name = "IdentityServer.Cookie"; options.LoginPath = "/Auth/Login"; } ); services.AddIdentityServer() .AddAspNetIdentity<IdentityUser>() .AddInMemoryIdentityResources(Configuration.GetIdentityResourceList()) .AddInMemoryApiScopes(Configuration.GetAPIScopeList()) .AddInMemoryClients(Configuration.GetClientList()) .AddDeveloperSigningCredential(); 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.UseIdentityServer(); app.UseEndpoints ( endpoints => { endpoints.MapDefaultControllerRoute(); } ); } #endregion } } |
▶ Models/LoginViewModel.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 |
namespace TestIdentityServer.Models { /// <summary> /// 로그인 뷰 모델 /// </summary> public class LoginViewModel { ////////////////////////////////////////////////////////////////////////////////////////// Property //////////////////////////////////////////////////////////////////////////////// Public #region 사용자명 - UserName /// <summary> /// 사용자명 /// </summary> public string UserName { get; set; } #endregion #region 패스워드 - Password /// <summary> /// 패스워드 /// </summary> public string Password { get; set; } #endregion #region 반환 URL - ReturnURL /// <summary> /// 반환 URL /// </summary> public string ReturnURL { get; set; } #endregion } } |
▶ Models/RegisterViewModel.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 |
using System.ComponentModel.DataAnnotations; namespace TestIdentityServer.Models { /// <summary> /// 등록 뷰 모델 /// </summary> public class RegisterViewModel { //////////////////////////////////////////////////////////////////////////////////////////////////// Property ////////////////////////////////////////////////////////////////////////////////////////// Public #region 사용자명 - UserName /// <summary> /// 사용자명 /// </summary> [Required] public string UserName { get; set; } #endregion #region 패스워드 - Password /// <summary> /// 패스워드 /// </summary> [Required] [DataType(DataType.Password)] public string Password { get; set; } #endregion #region 확인 패스워드 - ConfirmPassword /// <summary> /// 확인 패스워드 /// </summary> [Required] [DataType(DataType.Password)] [Compare("Password")] public string ConfirmPassword { get; set; } #endregion #region 반환 URL - ReturnURL /// <summary> /// 반환 URL /// </summary> public string ReturnURL { get; set; } #endregion } } |
▶ Controllers/AuthController.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 |
using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using System.Threading.Tasks; using TestIdentityServer.Models; namespace TestIdentityServer.Controllers { /// <summary> /// 인증 컨트롤러 /// </summary> public class AuthController : Controller { //////////////////////////////////////////////////////////////////////////////////////////////////// Field ////////////////////////////////////////////////////////////////////////////////////////// Private #region Field /// <summary> /// 사용자 관리자 /// </summary> private UserManager<IdentityUser> userManager; /// <summary> /// 서명 관리자 /// </summary> private SignInManager<IdentityUser> signInManager; #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor ////////////////////////////////////////////////////////////////////////////////////////// Public #region 생성자 - AuthController(userManager, signInManager) /// <summary> /// 생성자 /// </summary> /// <param name="userManager">사용자 관리자</param> /// <param name="signInManager">서명 관리자</param> public AuthController(UserManager<IdentityUser> userManager, SignInManager<IdentityUser> signInManager) { this.userManager = userManager; this.signInManager = signInManager; } #endregion //////////////////////////////////////////////////////////////////////////////////////////////////// Method ////////////////////////////////////////////////////////////////////////////////////////// Public #region 로그인 페이지 처리하기 - Login(returnURL) /// <summary> /// 로그인 페이지 처리하기 /// </summary> /// <param name="returnURL">반환 URL</param> /// <returns>액션 결과</returns> [HttpGet] public IActionResult Login(string returnURL) { return View(new LoginViewModel { ReturnURL = returnURL }); } #endregion #region 로그인 페이지 처리하기 - Login(vm) /// <summary> /// 로그인 페이지 처리하기 /// </summary> /// <param name="vm">로그인 뷰 모델</param> /// <returns>액션 결과</returns> [HttpPost] public async Task<IActionResult> Login(LoginViewModel vm) { Microsoft.AspNetCore.Identity.SignInResult result = await this.signInManager.PasswordSignInAsync(vm.UserName, vm.Password, false, false); if(result.Succeeded) { return Redirect(vm.ReturnURL); } return View(); } #endregion #region 등록 페이지 처리하기 - Register(returnURL) /// <summary> /// 등록 페이지 처리하기 /// </summary> /// <param name="returnURL">반환 URL</param> /// <returns>액션 결과</returns> [HttpGet] public IActionResult Register(string returnURL) { return View(new RegisterViewModel { ReturnURL = returnURL }); } #endregion #region 등록 페이지 처리하기 - Register(vm) /// <summary> /// 등록 페이지 처리하기 /// </summary> /// <param name="vm">등록 뷰 모델</param> /// <returns>액션 결과</returns> [HttpPost] public async Task<IActionResult> Register(RegisterViewModel vm) { if(!ModelState.IsValid) { return View(vm); } IdentityUser identityUser = new IdentityUser(vm.UserName); IdentityResult result = await this.userManager.CreateAsync(identityUser, vm.Password); if(result.Succeeded) { await this.signInManager.SignInAsync(identityUser, false); return Redirect(vm.ReturnURL); } return View(); } #endregion } } |
▶ Views/Auth/Login.cshtml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@model LoginViewModel <h1>신원 서버 인증 로그인 페이지</h1> <hr /> <form asp-controller="Auth" asp-action="Login" method="post"> <input type="hidden" asp-for="ReturnURL" /> <div> <label>사용자명</label> <input asp-for="UserName" /> </div> <div> <label>패스워드</label> <input asp-for="Password" /> </div> <div> <button type="submit">로그인</button> </div> </form> <div> <a asp-controller="Auth" asp-action="Register" asp-route-returnURL="@Model.ReturnURL">등록하기</a> </div> |
▶ Views/Auth/Register.cshtml
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 |
@model RegisterViewModel <h1>신원 서버 인증 등록 페이지</h1> <hr /> <form asp-controller="Auth" asp-action="Register" method="post"> <input type="hidden" asp-for="ReturnURL" /> <div> <label>사용자명</label> <input asp-for="UserName" /> <span asp-validation-for="UserName"></span> </div> <div> <label>패스워드</label> <input asp-for="Password" /> <span asp-validation-for="Password"></span> </div> <div> <label>패스워드 확인</label> <input asp-for="ConfirmPassword" /> <span asp-validation-for="ConfirmPassword"></span> </div> <div> <button type="submit">등록</button> </div> </form> <div> <a asp-controller="Auth" asp-action="Login" asp-route-returnURL="@Model.ReturnURL">로그인 페이지로 돌아가기</a> </div> |
[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" : 44330 } }, "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" } } } } |
▶ 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 |
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; 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.DefaultScheme = "Cookie"; options.DefaultChallengeScheme = "oidc"; } ) .AddCookie("Cookie") .AddOpenIdConnect ( "oidc", options => { options.Authority = "https://localhost:44300/"; options.ClientId = "CLIENTID0003"; options.ClientSecret = "CLIENTSECRET0003"; options.SaveTokens = true; options.ResponseType = "code"; options.Scope.Add("profile"); options.Scope.Add("API1" ); } ); 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 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 /> |