IdentityServer4学习及简单使用

本文,主要用来记录IdentityServer4的简单使用。javascript

一. IdentityServer的预备知识

要学习IdentityServer,须要了解下基于Token的验证体系,其中涉及到Token, OAuth&OpenID,JWT,协议规范等。html

如图过程,java

 

二.  IdentityServer简单介绍

IdentityServer4 是一个基于OpenID ConnectOAuth 2.0的针对ASP.NET Core 2.0的框架,以中间件的形式存在。git

一般你能够构建(或从新使用)包含登陆和注销页面的应用程序,IdentityServer中间件会向其添加必要的协议头,以便客户端应用程序可使用这些标准协议与其对话。github

咱们能够用IdentityServer来作什么?web

  1. 身份验证服务:官方认证的OpenID Connect实现
  2. 单点登陆/注销(SSO)
  3. 访问受控的API : 为不一样的客户提供访问API的令牌,好比:MVC网站、SPAMobile APP
  4. ...等等

三.简单项目示例

先列出目录结构,以及建立顺序,来方便阅读json

IdentityServerDemo --> APIService1和APIService2 --> MVCClientapi

其中,处MVCClient是asp.net core web mvc项目外,其余都是asp.net core web api 项目服务器

建立名为IdentityServerDemo的认证服务

1. 建立一个asp.net core web api项目:IdentityServerDemocookie

注意,不要设置HTTPS,不然后面使用postman测试时,会no response

2. 添加InMemoryConfiguration

public class InMemoryConfiguration
    {
        public static IConfiguration Configuration { get; set; }
        /// <summary>
        /// Define which APIs will use this IdentityServer
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<ApiResource> GetApiResources()
        { 
            return new[]
            {
                new ApiResource("clientservice", "CAS Client Service"),
                new ApiResource("productservice", "CAS Product Service"),
                new ApiResource("agentservice", "CAS Agent Service")
            };
        }

        /// <summary>
        /// Define which Apps will use thie IdentityServer
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<Client> GetClients()
        {
            return new[]
            {
                new Client
                {
                    ClientId = "client.api.service",
                    ClientSecrets = new [] { new Secret("clientsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "clientservice" }
                },
                new Client
                {
                    ClientId = "product.api.service",
                    ClientSecrets = new [] { new Secret("productsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "clientservice", "productservice" }
                },
                new Client
                {
                    ClientId = "agent.api.service",
                    ClientSecrets = new [] { new Secret("agentsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "agentservice", "clientservice", "productservice" }
                }
            };
        }

        /// <summary>
        /// Define which uses will use this IdentityServer
        /// </summary>
        /// <returns></returns>
        public static IEnumerable<TestUser> GetUsers()
        {
            return new[]
            {
                new TestUser
                {
                    SubjectId = "10001",
                    Username = "test1@hotmail.com",
                    Password = "test1password"
                },
                new TestUser
                {
                    SubjectId = "10002",
                    Username = "test2@hotmail.com",
                    Password = "test2password"
                },
                new TestUser
                {
                    SubjectId = "10003",
                    Username = "test3@hotmail.com",
                    Password = "test3password"
                }
            };
        }
    }
View Code

3. 使用nuget管理器,添加IdentityServer4 ,而且修改StartUp.cs

修改StartUp.cs中的Configure方法

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //启用IdentityServer
            app.UseIdentityServer();
            app.UseMvc();
        }

修改StartUp.cs中的ConfigureServices方法

public void ConfigureServices(IServiceCollection services)
        {
            //添加IdentityServer
            services.AddIdentityServer()
                       .AddDeveloperSigningCredential()
                       .AddTestUsers(InMemoryConfiguration.GetUsers().ToList())
                       .AddInMemoryClients(InMemoryConfiguration.GetClients())
                       .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources());

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

这个主要是为了把IdentityServer注册到容器中,须要对其进行配置,而这个配置主要包含三个信息:

  1. 哪些api可使用这个AuthorizationServer
  2. 哪些client可使用这个AuthorizationServer
  3. 哪些User能够被这个AuthorizationServer识别并受权

这里的AuthorizationServer 指的就是这个项目的服务:用来认证及受权使用的.

这里是使用基于内存的方式。

对于Token签名须要一对公钥和私钥,IdentityServer为开发者提供了一个AddDeveloperSigningCredential()方法,它会帮咱们搞定这个事情而且存储到硬盘。当切换到正式环境,须要使用真正的证书,更换为

public void ConfigureServices(IServiceCollection services)
    {
        InMemoryConfiguration.Configuration = this.Configuration;

        services.AddIdentityServer()
            .AddDeveloperSigningCredential()
            .AddTestUsers(InMemoryConfiguration.GetUsers().ToList())
            .AddInMemoryClients(InMemoryConfiguration.GetClients())
            .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources());
    }
View Code

此项目,暂时不使用正式的证书了。

4.使用postman获取token

启动咱们的IdentityServerDemo 项目,

而后使用postman发送请求

5.引入QuickStartUI

IdentityServer为咱们提供了一套UI以使咱们能快速的开发具备基本功能的认证/受权界面,下载地址:QuickStartUI

QuickStartUI引入到咱们的项目中,目录结构以下:

5.修改StartUp.cs

修改Configure方法

添加静态文件中间件

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            //启用IdentityServer
            app.UseIdentityServer();
            //for QuickStart-UI 启用静态文件
            app.UseStaticFiles();
            //app.UseMvc();
            app.UseMvcWithDefaultRoute(); //这里带有默认的路由
        }

6.运行程序

登陆

点击here

登出

 

IdentityServer集成API Service

1.  添加asp.net core web api项目

注意,这里也是使用http方式;

2.nuget中安装IdentityServer4.AccessTokenValidation 

3.修改StartUp.cs文件

修改configureServices方法

public void ConfigureServices(IServiceCollection services)
        {
            //IdentityServer
            services.AddMvcCore().AddAuthorization().AddJsonFormatters();
            services.AddAuthentication(Configuration["Identity:Scheme"])
                        .AddIdentityServerAuthentication(options =>
                        {
                            options.RequireHttpsMetadata = false; //是否须要https
                            options.Authority = $"http://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}";  //IdentityServer受权路径
                            options.ApiName = Configuration["Service:Name"];  //须要受权的服务名称
                        });

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

修改Configure方法

UseMvc()以前启用Authentication中间件

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            //启用Authentication中间件
            app.UseAuthentication();

            app.UseMvc();
        }

修改appsettings.json文件

{
  "Service": {
    "Name": "clientservice", //本服务的名称
    "Port": "53064",  //本服务的端口号,根据本身服务启动时的端口号进行更改 "DocName": "clientservice",
    "Version": "v1",
    "Title": "CAS Client Service API",
    "Description": "CAS Client Service API provide some API to help you get client information from CAS",
    "Contact": {
      "Name": "CAS 2.0 Team",
      "Email": "EdisonZhou@manulife.com"
    },
    "XmlFile": "Manulife.DNC.MSAD.IdentityServer4Test.ApiService01.xml"
  },
  "Identity": { //去请求受权的Identity服务,这里即IdentityServerDemo的服务启动时的地址
    "IP": "localhost",
    "Port": "49363",  //IdentityServerDemo项目启动时的端口号,根据实际状况修改 "Scheme": "Bearer"
  }
}

 上面是APIService1的添加,对应的服务名称是clientservice;

 APIService2与之相似,只是把appsettings.json中的clientservice改成productservice.

4. APIService1APIService2Controller添加[Authorize]特性

 [Authorize]
    [Route("api/[controller]")]
    public class ValuesController : Controller
    {
        ......
    }

 

5. 测试

注意,这里模拟的是clientservice服务(APIService1)去认证服务器请求token的过程,因此请求到token,也应该在获取clientservice相关受权的时候携带这个token.

 

 

若是在请求productservice的受权服务中,使用clientservicetoken则会显示未受权

过程总结:

  1. 首先,在受权服务中,设置须要请求的ApiResource,client,user
  2. postman(至关于client)中,输入client的相关信息(client_id,client_serect)去请求token
  3. 而后就能够根据受权服务中相应client的AllowedScopes设置的范围来请求服务了。

受权服务中的client设置

IdentityServer集成MVC Web Application

1. 新建一个ASP.NET Core MVC项目:MVCClient

 2.为指定方法添加[Authorize]特性

咱们为HomeController下的Privacy方法上添加Authorize特性

     [Authorize]
        public IActionResult Privacy()
        {
            return View();
        }

这个时候,直接访问Privacy,会报错

而咱们但愿的效果是:当用户第一次点击Privacy,页面重定向到验证服务(IdentityServerDemo),当用户登陆验证受权后,再重定向到该网站。

此后必定时间范围内的第二次,第三次点击,都不须要再重定向到验证服务,而是直接读取保存的token.

3.  MVCClient项目添加OpenID Connect Authentication

而这部分主要集中于作Authentication(身份验证)而非Authorization(受权)

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_2);

            //这部分主要是作身份验证的(Authentication),而不是受权(Authorization)
            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
            services.AddAuthentication(options =>
            {
                options.DefaultScheme = "Cookies";
                options.DefaultChallengeScheme = "oidc"; //oidc => open id connect
            })
            .AddCookie("Cookies")
            .AddOpenIdConnect("oidc", options =>
            {
                options.SignInScheme = "Cookies";
                options.Authority = $"http://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}";
                options.RequireHttpsMetadata = false;
                options.ClientId = "cas.mvc.client.implicit";
                options.ResponseType = "id_token token";  //容许返回access token
                options.SaveTokens = true;
            });

        }

这里咱们使用的是implicit这个flow,它主要用于客户端应用程序(主要指基于javascript的应用),它容许客户端程序重定向到验证服务(IdentityServerDemo),然后带着token重定向回来。

另外,这里的ResponseType为”id_token token”,表示既获取id_token也获取access_token. 而SaveTokens设置为true,表示会将从验证服务返回的token持久化到cookie中,这样就不用每次请求token了。

另在configure方法中,设置Authentication中间件:

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

            app.UseAuthentication();

            app.UseStaticFiles();
            app.UseCookiePolicy();

            app.UseMvc(routes =>
            {
                routes.MapRoute(
                    name: "default",
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
        }

主要Authentication中间件,要再UseMvc以前。

4. 修改app.settings

{
  "Service": {
    "Name": "cas.mvc.client.implicit", //本服务的名称
    "Port": "56458",  //服务端口号,根据实际状况调整
    "DocName": "cas.mvc.client.implicit",
    "Version": "v1",
    "Title": "CAS Client Service API",
    "Description": "CAS Client Service API provide some API to help you get client information from CAS",
    "Contact": {
      "Name": "CAS 2.0 Team",
      "Email": "EdisonZhou@manulife.com"
    },
    "XmlFile": "Manulife.DNC.MSAD.IdentityServer4Test.ApiService01.xml"
  },
  "Identity": { //去请求受权的Identity服务
    "IP": "localhost",
    "Port": "49363"
  }
}

其中port根据本身此服务启动后的端口号修改

5.在验证服务(IdentityServerDemo)中添加MvcClient

修改 InMemoryConfiguration 中的GetClients方法:

public static IEnumerable<Client> GetClients()
        {
            return new[]
            {
                new Client
                {
                    ClientId = "client.api.service",
                    ClientSecrets = new [] { new Secret("clientsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "clientservice" }
                },
                new Client
                {
                    ClientId = "product.api.service",
                    ClientSecrets = new [] { new Secret("productsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "clientservice", "productservice" }
                },
                new Client
                {
                    ClientId = "agent.api.service",
                    ClientSecrets = new [] { new Secret("agentsecret".Sha256()) },
                    AllowedGrantTypes = GrantTypes.ResourceOwnerPasswordAndClientCredentials,
                    AllowedScopes = new [] { "agentservice", "clientservice", "productservice" }
                },
                new Client { ClientId = "cas.mvc.client.implicit", ClientName = "CAS MVC Web App Client", AllowedGrantTypes = GrantTypes.Implicit, RedirectUris = { $"http://localhost:56458/signin-oidc" }, PostLogoutRedirectUris = { $"http://localhost:56458/signout-callback-oidc" }, AllowedScopes = new [] { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "agentservice", "clientservice", "productservice" }, AllowAccessTokensViaBrowser = true // can return access_token to this client
 },
            };
        }

这里ClientId要和MvcClient中设置的同样。

RedirectUris是指登陆成功之后须要重定向的地址(即重定向到MvcClient中的地址)

PostLogoutRedirectUris是指登出以后须要重定向的地址。

API Service Client的设置不一样的就是AllowedScopes中给它增长了OpenIdProfile,由于咱们为MvcClient设定的是oidc而不是bearer模式。

最后为了使用这些OpenID Connect Scopes,须要设置这些Identity Resources。

 

InMemoryConfiguration 中增长GetIdentityResources方法:

public static IEnumerable<IdentityResource> GetIdentityResources()
        {
            return new List<IdentityResource>
            {
                new IdentityResources.OpenId(),
                new IdentityResources.Profile(),
            };
        }

ConfigureServices方法中修改:

 public void ConfigureServices(IServiceCollection services)
        {
            //添加IdentityServer
            services.AddIdentityServer()
                       .AddDeveloperSigningCredential()
                       .AddInMemoryIdentityResources(InMemoryConfiguration.GetIdentityResources())
                       .AddTestUsers(InMemoryConfiguration.GetUsers().ToList())
                       .AddInMemoryClients(InMemoryConfiguration.GetClients())
                       .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources());

            services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
        }

6. MvcClient项目的Privacy 页面中修改以下:

@{
    ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

<p>Use this page to detail your site's privacy policy.</p>


@using Microsoft.AspNetCore.Authentication
<div>
    <strong>id_token</strong>
    <span>@await ViewContext.HttpContext.GetTokenAsync("id_token")</span>
</div>
<div>
    <strong>access_token</strong>
    <span>@await ViewContext.HttpContext.GetTokenAsync("access_token")</span>
</div>

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

这里,咱们会把id_token和access_token显示出来

7. 为了退出方便,暂时在HomeController下增长Logout方法

 public async Task Logout()
        {
            await HttpContext.SignOutAsync("Cookies");
            await HttpContext.SignOutAsync("oidc");
        } 

8. 简单测试

启动IdentityServerDemo这个验证服务;

启动MvcClient这个Mvc Web Application服务;

 

 

 

 

这里没有添加可点击的按钮,可直接在url中修改路径来登出

 

 

参考网址:

https://www.cnblogs.com/edisonchou/p/identityserver4_foundation_and_quickstart_01.html

 另外推荐edisonchou微服务系列,感受很是棒

 https://github.com/Vincent-yuan/IdentityServerDemo