寫在前面
先分享一首數搖:http://music.163.com/m/song?id=36089751&userid=52749763
其次是:對於identityServer理解並不是特別深刻,目前只是能簡單的應用,里面一些具體設置以后可能慢慢更新到本文中。
最后:一張大圖
IdentityServer4基於.net core的OAuth2.0和OpenId框架,主要應用於身份驗證,單點登錄,API訪問控制等。。。
IdentityServer4 文檔: https://identityserver4.readthedocs.io/en/release/
IdentityServer4 GitHub:https://github.com/IdentityServer/IdentityServer4/tree/dev/docs/topics
本文demo:https://github.com/aspros-luo/IdentityServer4Demo
api訪問控制
一.首先需要創建授權中心,
新建.net core Web Application 項目,模板可以選擇空或者web應用程序,偷懶的話直接選擇web就好了
1.在project.json里添加 "IdentityServer4": "1.0.0",nuget添加一樣可以。
2.我們需要新建一個配置文件configs.cs定義client和api作用域及賬號信息具體代碼如下:

public static IEnumerable<ApiResource> GeyApiResources() { return new List<ApiResource> { new ApiResource("UserApi","用戶API") }; } public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientId = "Client", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = {"UserApi"} }, new Client { ClientId = "ro.Client", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = {"UserApi"} }, // OpenID Connect implicit flow client (MVC) new Client { ClientId = "MVC", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5002" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "UserApi" }, AllowOfflineAccess = true }, // JavaScript Client new Client { ClientId = "js", ClientName = "JavaScript Client", AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, RedirectUris = { "http://localhost:5003/callback.html" }, PostLogoutRedirectUris = { "http://localhost:5003/index.html" }, AllowedCorsOrigins = { "http://localhost:5003" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "UserApi" }, } }; } public static List<TestUser> GeTestUsers() { return new List<TestUser> { new TestUser { SubjectId = "1", Username = "qwerty", Password = "a123" }, new TestUser { SubjectId = "2", Username = "aspros", Password = "b123" } }; }
3.在startup文件ConfigureServices里配置服務,Configure使用identityserver

public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddApplicationInsightsTelemetry(Configuration); services.AddIdentityServer() .AddTemporarySigningCredential() .AddInMemoryApiResources(Configs.GeyApiResources()) .AddInMemoryClients(Configs.GetClients()) .AddTestUsers(Configs.GeTestUsers()); services.AddMvc(); }

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); app.UseApplicationInsightsRequestTelemetry(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); app.UseBrowserLink(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseApplicationInsightsExceptionTelemetry(); app.UseStaticFiles(); //使用userIdentityServer app.UseIdentityServer(); app.UseMvc(routes => { routes.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
4.更改當前應用程序的端口為:8000 (可略過)
以上,基本授權中心配置完畢
二.添加測試使用的api
新建.net core Web Application,模板使用Api
1.在project.json里添加 "IdentityServer4.AccessTokenValidation": "1.0.1", "Microsoft.AspNetCore.Cors": "1.1.0"(為跨域訪問api做准備)
2.在Startup文件里添加跨域服務,配置授權中心地址及scope api作用域

public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddApplicationInsightsTelemetry(Configuration); #region 跨域 services.AddCors(options => { // this defines a CORS policy called "default" options.AddPolicy("default", policy => { policy.WithOrigins("http://localhost:5003") .AllowAnyHeader() .AllowAnyMethod(); }); }); #endregion services.AddMvcCore() .AddAuthorization() .AddJsonFormatters(); }

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); //配置identityServer授權 app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions { Authority = "http://localhost:8000", AllowedScopes = { "UserApi" }, RequireHttpsMetadata = false }); //跨域訪問 app.UseCors("default"); app.UseMvc(); }
注釋:cors可以暫時不配置,不影響后面調試
3.在controller上添加 [Authorize]

[Authorize] [Route("api/[controller]")] public class ValuesController : Controller { [HttpGet] public IActionResult Get() { //return Content("a"); return new JsonResult(from c in User.Claims select new { c.Type, c.Value }); } }
4.更改api端口號,5001
以上測試使用Api暫時配置完畢
三.單元測試
新建.net core 類庫
1.在project.json里添加
"dotnet-test-xunit": "2.2.0-preview2-build1029",
"IdentityModel": "2.1.1",
"xunit": "2.2.0-beta4-build3444",
"xunit.runner.console": "2.2.0-beta2-build3300"
具體如下:

{ "version": "1.0.0-*", "testRunner": "xunit", "dependencies": { "dotnet-test-xunit": "2.2.0-preview2-build1029", "IdentityModel": "2.1.1", "xunit": "2.2.0-beta4-build3444", "xunit.runner.console": "2.2.0-beta2-build3300" }, "frameworks": { "netcoreapp1.0.1": { "dependencies": { "Microsoft.NETCore.App": { "type": "platform", "version": "1.0.1" } } } } }
2.新建測試類,UserClientTest,代碼如下

public class UserClientTest { [Fact] public async Task ClientApiTest() { //get access_token var disco = await DiscoveryClient.GetAsync("http://localhost:8000"); var tokenClient = new TokenClient(disco.TokenEndpoint, "Client", "secret"); var tokenResponse = await tokenClient.RequestClientCredentialsAsync("UserApi"); var client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken);//add bearer with access_token var response = await client.GetAsync("http://localhost:5001/api/Values");//call API with access_token var apiResult = response.Content.ReadAsStringAsync().Result; Assert.NotEmpty(apiResult); } [Fact] public async Task PasswordApiTests() { var disco = await DiscoveryClient.GetAsync("http://localhost:8000"); var tokenClient = new TokenClient(disco.TokenEndpoint, "ro.Client", "secret"); var tokenResponse = await tokenClient.RequestResourceOwnerPasswordAsync("qwerty", "a123", "UserApi"); var client = new HttpClient(); client.SetBearerToken(tokenResponse.AccessToken);//add bearer with access_token var response = await client.GetAsync("http://localhost:5001/api/Values");//call API with access_token var apiResult = response.Content.ReadAsStringAsync().Result; Assert.NotEmpty(apiResult); } }
調試,
1請求授權中心,帶入clientId,secret,scope作用域(api) 得到access_token
2得到access_token后,在header里添加Authorization:Bearer+access_token 請求api
3返回結果
OpenId 連接的mvc用戶認證
一.授權中心更改的地方:
1.在授權中心里config里添加IdentityResource

public static IEnumerable<IdentityResource> GetyIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile() }; }
2.config=》client里添加mvc

new Client { ClientId = "MVC", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5002" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "UserApi" }, AllowOfflineAccess = true },
(AllowedScopes 設置授權范圍,加上“UserApi” 及AllowOfflineAccess = true 后,在授權中心登錄驗證通過后,顯示對應訪問權限)
3.在startup里添加 AddInMemoryIdentityResources方法

public void ConfigureServices(IServiceCollection services) { // Add framework services. services.AddApplicationInsightsTelemetry(Configuration); services.AddIdentityServer() .AddTemporarySigningCredential() .AddInMemoryApiResources(Configs.GeyApiResources()) .AddInMemoryClients(Configs.GetClients()) .AddInMemoryIdentityResources(Configs.GetyIdentityResources()) .AddTestUsers(Configs.GeTestUsers()); services.AddMvc(); }
(這里我出現過一個問題,在添加順序的時候,將IdentityResources方法寫在前面,單元測試請求api的時候會出現httpstatue 500錯誤)
4.添加UI
可以在github下載 https://github.com/IdentityServer/IdentityServer4.Quickstart.UI/tree/release
(涉及到的頁面及viewmodel較多。demo里我也重新整理過了)
以上,授權中心部分修改完畢
二.mvc客戶端
添加.net core web application 選擇web應用程序
1.在project.json 里添加
"Microsoft.AspNetCore.Authentication.Cookies": "1.0.*",
"Microsoft.AspNetCore.Authentication.OpenIdConnect": "1.0.*",
"IdentityModel": "2.1.1"
2.在startup里添加UseCookieAuthentication,UseOpenIdConnectAuthentication,代碼如下

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear(); loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Home/Error"); } app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationScheme = "Cookies" }); app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions { AuthenticationScheme = "oidc", SignInScheme = "Cookies", Authority = "http://localhost:8000", RequireHttpsMetadata = false, ClientId = "MVC", ClientSecret = "secret", ResponseType = "code id_token", Scope = { "UserApi", "offline_access" },//添加權限請求項 GetClaimsFromUserInfoEndpoint = true, SaveTokens = true }); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); }
注意的地方,Authority為授權中心地址,ClientId與授權中心config里client里保持一致。Scope為請求權限項
3.在action上加上[Authorize],啟用授權
4.更改端口為5002,可自行調節,需要與config保持一致
使用:
運行授權中心=》運行mvc客戶端=》點擊對應的授權的action鏈接=》跳轉到授權中心登錄頁面=》輸入賬號密碼后=》顯示授權對應的權限列表=》跳回當前頁面
以上。
js客戶端訪問
js端訪問配置稍微麻煩點,因為中間出了一些問題,主要是前端js功力不夠,看的的時候比較吃力
添加一個新項目。。先
在mvc端中,我們引用了一個庫處理openid連接,在javascript中也需要引用一個類似的庫
1.點擊添加=》新建項=》左側選擇client-side選擇NPM配置文件,默認為package.json
在package.json 里添加"oidc-client": "1.2.2",如下

{ "version": "1.0.0", "name": "asp.net", "private": true, "devDependencies": { "oidc-client": "1.2.2" } }
找到oidc-client.js文件,將文件復制到wwwroot下(注意html頁面引用就行)
2.添加兩個html
index.html

<!DOCTYPE html> <html> <head> <meta charset="utf-8"/> <title></title> </head> <body> <button id="login">Login</button> <button id="api">Call API</button> <button id="logout">Logout</button> <pre id="results"></pre> <script src="http://code.jquery.com/jquery-latest.js"></script> <script src="oidc-client.js"></script> <script src="app.js"></script> </body> </html>
callBack.html

<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title></title> </head> <body> <script src="oidc-client.js"></script> <script> new Oidc.UserManager().signinRedirectCallback().then(function () { window.location = "index.html"; }).catch(function (e) { console.error(e); }); </script> </body> </html>
3.添加app.js

/// <reference path="oidc-client.js" /> function log() { document.getElementById('results').innerText = ''; Array.prototype.forEach.call(arguments, function (msg) { if (msg instanceof Error) { msg = "Error: " + msg.message; } else if (typeof msg !== 'string') { msg = JSON.stringify(msg, null, 2); } document.getElementById('results').innerHTML += msg + '\r\n'; }); } document.getElementById("login").addEventListener("click", login, false); document.getElementById("api").addEventListener("click", api, false); document.getElementById("logout").addEventListener("click", logout, false); var config = { authority: "http://localhost:8000", client_id: "js", redirect_uri: "http://localhost:5003/callback.html", response_type: "id_token token", scope: "openid profile UserApi", post_logout_redirect_uri: "http://localhost:5003/index.html", }; var mgr = new Oidc.UserManager(config); mgr.getUser().then(function (user) { if (user) { log("User logged in", user.profile); } else { log("User not logged in"); } }); function login() { mgr.signinRedirect(); } function api() { mgr.getUser().then(function (user) { var url = "http://localhost:5001/api/Values"; var xhr = new XMLHttpRequest(); xhr.open("GET", url); xhr.onload = function () { log(xhr.status, JSON.parse(xhr.responseText)); } xhr.setRequestHeader("Authorization", "Bearer " + user.access_token); xhr.send(); }); } function logout() { mgr.signoutRedirect(); }
4.將端口設為5003
運行授權中心=》運行javascriptclient=》點擊login=》跳轉到授權中心登錄頁面=》登錄后,顯示權限列表=》返回5003
以上
更新
看了昨天的評論,又去官方文檔瞄了下,找到一些處理方法,如下
https://identityserver4.readthedocs.io/en/release/quickstarts/8_entity_framework.html
IdentityServer is designed for extensibility, and one of the extensibility points is the storage mechanism used for data that IdentityServer needs. This quickstart shows to how configure IdentityServer to use EntityFramework (EF) as the storage mechanism for this data (rather than using the in-memory implementations we had been using up until now).
大概意思是說,可以用數據庫存儲apiresour,client和identityserverresource 資源
新建或在原有的授權中心項目更改都是可以的,我這里是直接新建了一個
1.在project.json里 dependencies添加
"IdentityServer4.EntityFramework": "1.0.0",
"Microsoft.EntityFrameworkCore.SqlServer": "1.1.0",
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final",
"Microsoft.EntityFrameworkCore.SqlServer.Design": "1.1.*"
tools里添加(用於執行cmd的donet ef命令)
"Microsoft.EntityFrameworkCore.Tools": "1.0.0-preview2-final"
2.新建config文件

using System.Collections.Generic; using IdentityServer4; using IdentityServer4.Models; using IdentityServer4.Test; namespace EntityFrameworkDemo { public class Config { public static IEnumerable<IdentityResource> GetyIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile() }; } public static IEnumerable<ApiResource> GeyApiResources() { return new List<ApiResource> { new ApiResource("UserApi","用戶API"), new ApiResource("api1","測試api") }; } public static IEnumerable<Client> GetClients() { return new List<Client> { new Client { ClientId = "Client", AllowedGrantTypes = GrantTypes.ClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = {"UserApi"} }, new Client { ClientId = "ro.Client", AllowedGrantTypes = GrantTypes.ResourceOwnerPassword, ClientSecrets = { new Secret("secret".Sha256()) }, AllowedScopes = {"UserApi"} }, // OpenID Connect implicit flow client (MVC) new Client { ClientId = "MVC", ClientName = "MVC Client", AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, ClientSecrets = { new Secret("secret".Sha256()) }, RedirectUris = { "http://localhost:5002/signin-oidc" }, PostLogoutRedirectUris = { "http://localhost:5002" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "UserApi" }, AllowOfflineAccess = true }, // JavaScript Client new Client { ClientId = "js", ClientName = "JavaScript Client", AllowedGrantTypes = GrantTypes.Implicit, AllowAccessTokensViaBrowser = true, RedirectUris = { "http://localhost:5003/callback.html" }, PostLogoutRedirectUris = { "http://localhost:5003/index.html" }, AllowedCorsOrigins = { "http://localhost:5003" }, AllowedScopes = { IdentityServerConstants.StandardScopes.OpenId, IdentityServerConstants.StandardScopes.Profile, "UserApi" }, } }; } public static List<TestUser> GetTestUsers() { return new List<TestUser> { new TestUser { SubjectId = "1", Username = "qwerty", Password = "a123" }, new TestUser { SubjectId = "2", Username = "aspros", Password = "b123" } }; } } }
3.在startup里添加對應的服務
(1).修改ConfigureServices
舊:ConfigureServices里如下

services.AddIdentityServer()
.AddTemporarySigningCredential()
.AddInMemoryApiResources(Configs.GeyApiResources())
.AddInMemoryClients(Configs.GetClients())
.AddInMemoryIdentityResources(Configs.GetyIdentityResources())
.AddTestUsers(Configs.GeTestUsers());
新:ConfigureServices里如下

var connectionString = @"server=.;database=IdentityServer4;trusted_connection=yes"; var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; // configure identity server with in-memory users, but EF stores for clients and resources services.AddIdentityServer() .AddTemporarySigningCredential() .AddTestUsers(Config.GetTestUsers()) .AddConfigurationStore(builder => builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly))) .AddOperationalStore(builder => builder.UseSqlServer(connectionString, options => options.MigrationsAssembly(migrationsAssembly)));
(2).修改Configure

app.UseIdentityServer(); app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme, AutomaticAuthenticate = false, AutomaticChallenge = false });
(3).初始化數據庫

private void InitializeDatabase(IApplicationBuilder app) { using (var scope = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope()) { scope.ServiceProvider.GetRequiredService<PersistedGrantDbContext>().Database.Migrate(); var context = scope.ServiceProvider.GetRequiredService<ConfigurationDbContext>(); context.Database.Migrate(); if (!context.Clients.Any()) { foreach (var client in Config.GetClients().Where(client => !context.Clients.Any(c => c.ClientId == client.ClientId))) { context.Clients.Add(client.ToEntity()); } } //context.SaveChanges(); if (!context.IdentityResources.Any()) { foreach ( var identity in Config.GetyIdentityResources() .Where(identity => !context.IdentityResources.Any(i => i.Name == identity.Name))) { context.IdentityResources.Add(identity.ToEntity()); } } //context.SaveChanges(); if (!context.ApiResources.Any()) { foreach (var api in Config.GeyApiResources().Where(api => !context.ApiResources.Any(a => a.Name == api.Name))) { context.ApiResources.Add(api.ToEntity()); } } context.SaveChanges(); } }
在Configure添加初始化數據庫方法

public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { // this will do the initial DB population InitializeDatabase(app); ... }
4.在當前項目目錄,如 G:\MyProject\IdentityServerDemo\src\EntityFrameworkDemo下shift+右鍵,在此處打開命令窗口,運行下面兩行命令
dotnet ef migrations add InitialIdentityServerPersistedGrantDbMigration -c PersistedGrantDbContext -o Data/Migrations/IdentityServer/PersistedGrantDb
dotnet ef migrations add InitialIdentityServerConfigurationDbMigration -c ConfigurationDbContext -o Data/Migrations/IdentityServer/ConfigurationDb
5.重新運行授權中心前更改端口號為8001(如果是新建的項目),在sqlserver數據庫會生成對應的數據庫
6.將api的授權地址改成8001
7.運行單元測試
以上
如有問題,歡迎指正。