Identity Server 4

准备

  1. 建立Identity Provider项目
  • IdentityServer4.Templates
  • https://github.com/IdentityServer/IdentityServer4.Templates
  • 安装工具:
    • dotnet new -i identityserver4.templates
    • 重置 “dotnet new” 功能列表: dotnet new --debug:reinit
  • 模板:
    • dotnet new is4empty
    • dotnet new is4ui
    • dotnet new is4inmem
    • dotnet new is4aspid
    • dotnet new is4ef
    • dotnet new is4admin (收费)

创建项目

  • dotnet new -i identityserver4.templates
  • dotnet new is4aspid --name BlogIdp
  • 升级为.NET Core 2.1, 更新Nuget包
  • 配置Hsts|HttpsRedirection
   //注册Hsts
            services.AddHsts(options =>
            {
                options.Preload = true;
                options.IncludeSubDomains = true;
                options.MaxAge = TimeSpan.FromDays(60);
            });

            //配置HTTP重定向
            services.AddHttpsRedirection(options =>
            {
                options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
                options.HttpsPort = 6001;
            });

MVC 测试

  • 安全/机密客户端(Confidential Client), 它是传统的服务器端Web应用.
  • 它需要长时间访问(long-lived access), 所以需要refresh token. 那么它可以使用Authorization Code Flow或Hybrid Flow.
  • Hybrid Flow是相对高级一些的, 它可以让客户端首先从授权端点获得一个ID Token并通过浏览器(front-channel)传递过来, 这样我们就可以验证这个ID Token. 如果验证成功然后, 客户端再打开一个后端通道(back-channel), 从Token端点获取Access Token.
  1. 身份认证请求


  • 第一行的URI: "/authorize" 就是授权端点(Authorization Endpoint), 它位于身份提供商(Identity provider, IDP)那里. 这个URI可以从前面介绍的discovery document里面找到.
  • 第二行 response_type=code id_token, 它决定了采取了哪一种Hybrid流程(参考上面那三个图).
  • 第三行 client_id=xxxx, 这是客户端的身份标识.
  • 第四行 redirect_uri=https...., 这是客户端那里的重定向端点(Redirection Endpoint).
  • 第五行 scope=openid profile email, 这就是客户端所请求的scopes.
  1. Hybrid Flow


    Hybrid Flow
  • 为什么要返回两次ID Token呢? 这是因为第(4)步里面请求Token的时候要求客户端身份认证, 这时请求Token的时候需要提供Authorization Code, Client ID和 Client Secret, 这些secret并不暴露给外界, 这些东西是由客户端服务器通过后端通道传递给Token端点的. 而第一次获得的ID Token是从前端通道(浏览器)返回的. 当这个ID Token被验证通过之后, 也就证明了当前用户到底是谁.

code demo

  1. 新建MVC
  2. 修改端口为7000,7001
  3. 修改Idp项目config.cs
 new Client
                {
                    ClientId = "mvcclient",
                    ClientName = "MVC客户端",

                    AllowedGrantTypes = GrantTypes.HybridAndClientCredentials,
                    ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },

                    RedirectUris = { "http://localhost:7001/signin-oidc" },
                    FrontChannelLogoutUri = "http://localhost:7001/signout-oidc",
                    PostLogoutRedirectUris = { "http://localhost:7001/signout-callback-oidc" },

                    AllowOfflineAccess = true, //offline_access(refresh token)
                    AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId }// { "openid", "profile", "api1" }
                },
  1. 修改mvc项目Startup
  public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.Configure<CookiePolicyOptions>(options =>
            {
                // This lambda determines whether user consent for non-essential cookies is needed for a given request.
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
            });


            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);


            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc";
            })
                .AddCookie("Cookies", options =>
                {
                    options.AccessDeniedPath = "/Authorization/AccessDenied";
                })
                .AddOpenIdConnect("oidc", options =>
                {
                    options.SignInScheme = "Cookies";
                    options.Authority = "https://localhost:6001";
                    options.RequireHttpsMetadata = true;
                    options.ClientId = "mvcclient";
                    options.ResponseType = "code id_token";
                    options.Scope.Clear();
                    options.Scope.Add("openid"); //与Idp中config对应
                    //options.Scope.Add("profile");
                    //options.Scope.Add("email");
                    //options.Scope.Add("restapi");

                    options.SaveTokens = true;
                    options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";
                    options.GetClaimsFromUserInfoEndpoint = true;
                });
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
                app.UseHsts();
            }

            app.UseHttpsRedirection();
            app.UseAuthentication();
            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
  1. HomeController 测试
 [Authorize]
    public class HomeController : Controller
    {
        public IActionResult Index()
        {
            return View();
        }
  ......
跳转验证
  1. 添加更多获取资源
 public async Task<IActionResult> About()
        {
            var idToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.IdToken);

            ViewData["idToken"] = idToken;

            return View();
        }
  1. Config.cs添加
  public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new IdentityResource[]
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
                new IdentityResources.Email(),
            };
        }
 public static IEnumerable<ApiResource> GetApis()
        {
            return new ApiResource[]
            {
                new ApiResource("restapi", "My RESTful API")
            };
        }
   AllowOfflineAccess = true, //offline_access(开启refresh token)
   AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        IdentityServerConstants.StandardScopes.Email,
                        "restapi"
                    }// { "openid", "profile", "api1" }
  1. 修改html
@{
    ViewData["Title"] = "About";
}
<h2>@ViewData["Title"]</h2>
<h3>IdToken:@ViewData["idToken"]</h3>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

保护API资源

  1. 安装包 `IdentityServer4.AccessTokenValidation
  2. 注册
 // 注册IdentityServer
            services.AddAuthentication(IdentityServerAuthenticationDefaults.AuthenticationScheme)
                .AddIdentityServerAuthentication(options =>
                {
                    options.Authority = "https://localhost:6001";
                    options.ApiName = "restapi";
                });

  1. config
    app.UseAuthentication();
  2. api中加验证
    • 属性标签 [Authorize]
    • 全局filter
  //设置全局filter保护api需要认真用户才可访问
            services.Configure<MvcOptions>(options =>
            {
                var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
                options.Filters.Add(new AuthorizeFilter(policy));
            });
  1. NoAuth


    401 error
  2. MVC客户端
  • 安装IdentityModel
  • 修改HomeController
  public async Task<IActionResult> Contact()
        {
            var httpClient = new HttpClient
            {
                BaseAddress = new Uri("https://localhost:5001")
            };
            httpClient.DefaultRequestHeaders.Clear();
            httpClient.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/vnd.enfi.hateoas+json")
                );

            var accessToken = await HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
            ViewData["accessToken"] = accessToken;
            httpClient.SetBearerToken(accessToken);

            var res = await httpClient.GetAsync("api/posts").ConfigureAwait(false);
            if (res.IsSuccessStatusCode)
            {
                var json = await res.Content.ReadAsStringAsync().ConfigureAwait(false);
                var objects = JsonConvert.DeserializeObject<dynamic>(json);
                ViewData["json"] = objects;
                return View();
            }
            if (res.StatusCode == HttpStatusCode.Unauthorized)
            {
                return RedirectToAction("AccessDenied", "Authorization");
            }
            throw new Exception($"Error Occurred:${res.ReasonPhrase}");
        }
  • 添加 Authorization/AccessDenied
 public class AuthorizationController : Controller
    {
        public IActionResult AccessDenied()
        {
            return View();
        }
    }
@{ViewData["Title"] = "AccessDenied";}
<div class="container">
    <h2>Access Denied</h2>
</div>
  1. https://jwt.io 网站解析token
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容