■ IdentityServer4 커스텀 클레임을 ID 토큰에서 제외하는 방법을 보여준다.
[TestIdentityServer 프로젝트]
▶ 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 90 91 92 93 94 95 96 97 |
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(), new IdentityResource { Name = "user.scope", UserClaims = { "user.claim" } } }; } #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, "user.scope" }, RequireConsent = false, // ID 토큰에 사용자 클레임을 포함하지 않는다. AlwaysIncludeUserClaimsInIdToken = false } }; } #endregion } } |
▶ 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 62 63 64 |
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using System.Security.Claims; 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(); userManager.AddClaimAsync(user, new Claim("user.claim", "VALUE0001")).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 } } |
[TestClient 프로젝트]
▶ 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; 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.ClaimActions.MapUniqueJsonKey("user.claim", "user.claim"); // 사용자 정의 엔드포인트에서 클레임을 구한다. // ID 토큰의 크기가 적어지나 쿠키에 클레임을 로드하기 위해 추가로 통신한다. options.GetClaimsFromUserInfoEndpoint = true; options.Scope.Add("openid" ); options.Scope.Add("profile" ); options.Scope.Add("API1" ); options.Scope.Add("API2" ); options.Scope.Add("user.scope"); } ); services.AddHttpClient(); 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 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 |
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Net.Http; using System.Threading.Tasks; using TestClient.Models; 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() { string idToken = await HttpContext.GetTokenAsync("id_token" ); string accessToken = await HttpContext.GetTokenAsync("access_token"); JwtSecurityToken idJwtSecurityToken = new JwtSecurityTokenHandler().ReadJwtToken(idToken); JwtSecurityToken accessJwtSecurityToken = new JwtSecurityTokenHandler().ReadJwtToken(accessToken); SecretViewModel vm = new SecretViewModel { IDTokenClaimList = idJwtSecurityToken.Claims.ToList(), UserClaimList = User.Claims.ToList(), AccessTokenClaimList = accessJwtSecurityToken.Claims.ToList() }; return View(vm); } #endregion } } |
▶ Views/Home/Secret.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 |
@using System.Security.Claims @using TestClient.Models @model SecretViewModel <h1>클라이언트 홈 비밀 페이지</h1> <hr /> <h2>ID 토큰 클레임 리스트 (@Model.IDTokenClaimList.Count 건)</h2> @foreach(Claim claim in Model.IDTokenClaimList) { <p>@claim.Type : @claim.Value</p> } <hr /> <h2>사용자 클레임 리스트 (@Model.UserClaimList.Count 건)</h2> @foreach(Claim claim in Model.UserClaimList) { <p>@claim.Type : @claim.Value</p> } <hr /> <h2>액세스 토큰 클레임 리스트 (@Model.AccessTokenClaimList.Count 건)</h2> @foreach(Claim claim in Model.AccessTokenClaimList) { <p>@claim.Type : @claim.Value</p> } |