本文,主要用来记录IdentityServer4的简单使用。
一. IdentityServer的预备知识
要学习IdentityServer,需要了解下基于Token的验证体系,其中涉及到Token, OAuth&OpenID,JWT,协议规范等。
如图过程,
二. IdentityServer简单介绍
IdentityServer4 是一个基于OpenID Connect和OAuth 2.0的针对ASP.NET Core 2.0的框架,以中间件的形式存在。
通常你可以构建(或重新使用)包含登录和注销页面的应用程序,IdentityServer中间件会向其添加必要的协议头,以便客户端应用程序可以使用这些标准协议与其对话。
我们可以用IdentityServer来做什么?
- 身份验证服务:官方认证的OpenID Connect实现
- 单点登录/注销(SSO)
- 访问受控的API : 为不同的客户提供访问API的令牌,比如:MVC网站、SPA、Mobile APP等
- ...等等
三.简单项目示例
先列出目录结构,以及创建顺序,来方便阅读
IdentityServerDemo --> APIService1和APIService2 --> MVCClient
其中,处MVCClient是asp.net core web mvc项目外,其他都是asp.net core web api 项目
创建名为IdentityServerDemo的认证服务
1. 创建一个asp.net core web api项目:IdentityServerDemo。
注意,不要设置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注册到容器中,需要对其进行配置,而这个配置主要包含三个信息:
- 哪些api可以使用这个AuthorizationServer
- 哪些client可以使用这个AuthorizationServer
- 哪些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. 在APIService1和APIService2的Controller添加[Authorize]特性
[Authorize] [Route(\"api/[controller]\")] public class ValuesController : Controller { ...... }
5. 测试
注意,这里模拟的是clientservice服务(即APIService1)去认证服务器请求token的过程,所以请求到token,也应该在获取clientservice相关授权的时候携带这个token.
如果在请求productservice的授权服务中,使用clientservice的token则会显示未授权
过程总结:
- 首先,在授权服务中,设置需要请求的ApiResource,client,user
- 在postman(相当于client)中,输入client的相关信息(client_id,client_serect)去请求token
- 然后就可以根据授权服务中相应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中给它增加了OpenId和Profile,因为我们为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