.Net Core 商城微服務專案系列(一):使用IdentityServer4構建基礎登入驗證
這裡第一次搭建,所以IdentityServer端比較簡單,後期再進行完善。
1.新建API專案MI.Service.Identity,NuGet引用IdentityServer4,新增類InMemoryConfiguration用於配置api和客戶端資源:
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("MI.Service", "MI.Service"), }; } /// <summary> /// Define which Apps will use thie IdentityServer/// </summary> /// <returns></returns> public static IEnumerable<Client> GetClients() { return new[] { new Client { ClientId = "MI.Web", ClientSecrets = new [] { newSecret("miwebsecret".Sha256()) }, AllowedGrantTypes = GrantTypes.ClientCredentials, AllowedScopes = new [] { "MI.Service" } } }; } public static IEnumerable<IdentityResource> GetIdentityResources() { return new List<IdentityResource> { new IdentityResources.OpenId(), new IdentityResources.Profile(), }; } /// <summary> /// Define which uses will use this IdentityServer /// </summary> /// <returns></returns> public static IEnumerable<TestUser> GetUsers() { return new[] { new TestUser { SubjectId = "10001", Username = "admin", Password = "admin" }, new TestUser { SubjectId = "10002", Username = "wei", Password = "123" }, new TestUser { SubjectId = "10003", Username = "test", Password = "123" } }; } }
簡單介紹一下,既然是微服務專案,比如有需要的API,ApiResource即我們要使用的API資源,這裡我用“MI.Service”,後面的API專案也需要和這裡配置的相同。當前也可以每一個API專案都新建一個ApiResource的名稱。
Client是發起呼叫發,比如我們的Web系統會呼叫API,那Web系統就是一個Client,也可以理解為一個角色,Client Id是角色標識,這個也需要在發起呼叫方那邊配置,ClientSecrets是私鑰,這裡使用最簡單的自帶私鑰,AllowedScopes是當前這個Client可以訪問的ApiResource。
TestUser是IdentityServer自帶的測試使用者類,使用者使用使用者名稱和密碼的方式登入使用。
然後需要在Startup中新增IdentityServer配置:
在ConfigureServices方法中新增如下:
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddTestUsers(InMemoryConfiguration.GetUsers().ToList())
.AddInMemoryClients(InMemoryConfiguration.GetClients())
.AddInMemoryApiResources(InMemoryConfiguration.GetApiResources());
這裡我們使用的均是記憶體級別的配置,在實際專案裡建議改為資料庫中讀取。
然後在Configure方法中啟用IdentityServer:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseIdentityServer(); app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); }
到此IdentityServer驗證端配置完畢。
2.新建API專案MI.Service.Account,NuGet引用 IdentityServer4.AccessTokenValidation。
在Startup的ConfigureServices方法中進行IdentityServer4配置:
services.AddAuthentication(Configuration["Identity:Scheme"]) // .AddIdentityServerAuthentication(options => { options.RequireHttpsMetadata = false; // for dev env options.Authority = $"http://{Configuration["Identity:IP"]}:{Configuration["Identity:Port"]}"; //IdnetityServer專案IP和埠 options.ApiName = Configuration["Service:Name"]; // match with configuration in IdentityServer //當前API專案的ApiResource的名稱 即我們上個專案的“MI.Service” });
在Configure中啟用驗證:
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseHsts(); } app.UseAuthentication(); //啟用驗證 app.UseMvcWithDefaultRoute(); }
我們整理用的是appsettings.json的配置,配置如下:
{ "Service": { "Name": "MI.Service", "Port": "7001", "DocName": "Account Service", "Version": "v1", "Title": "Account Service API", "Description": "CAS Client Service API provide some API to help you get client information from CAS" //"XmlFile": "Manulife.DNC.MSAD.IdentityServer4Test.ApiService01.xml" }, "Identity": { "IP": "localhost", "Port": "7000", "Scheme": "Bearer" } }
我們的IdentityServer專案執行在7000埠,當前API專案執行在70001埠,大家可以根據需要自行配置。
在當前API專案新增控制器MiUserController,並新增一個測試方法和一個登陸方法:
[EnableCors("AllowCors")] [Authorize] //這裡新增驗證標籤 public class MiUserController : Controller {
//實體上下文類 public MIContext _context; public MiUserController(MIContext _context) { this._context = _context; } //這個方法用來進行測試 public IActionResult Index() { return Json("Successful"); } public async Task<SSOLoginResponse> SSOLogin(SSOLoginRequest request) { SSOLoginResponse response = new SSOLoginResponse(); try { if (!string.IsNullOrEmpty(request.UserName) && !string.IsNullOrEmpty(request.Password)) { var user = _context.UserEntities.FirstOrDefault(a => a.CustomerPhone.Equals(request.UserName)); if (user == null) { response.Successful = false; response.Message = "使用者名稱或密碼錯誤!"; return response; } if (user.CustomerPwd == request.Password) { //將使用者名稱儲存硬碟cookie 30分鐘 作用域為整個網站 HttpContext.Response.Cookies.Append("MIUserName", user.CustomerPhone, new Microsoft.AspNetCore.Http.CookieOptions { Expires = DateTime.Now.AddMinutes(30), Path = "/", }); return response; } } response.Successful = false; response.Message = "使用者名稱密碼不能為空!"; } catch (Exception ex) { response.Successful = false; response.Message = ex.Message; } return response; } }
現在配置完成,我們現在PostMan中測試一下請求IdentityServer專案獲取Token,下面請求引數分別是我們之前配置的:
不出意外我們能夠獲取到對應的Token。
拿到Token後我們可以使用它來請求API專案:MI.Service.Account:
Token前我們必須要有Bearer這個,我們之前在API專案的appsettings.json中也加過這個配置,如果一切正常我們能夠獲取當測試方法Index返回的“Successful”。
3.新建Web專案MI.Web,畢竟這些API專案需要有呼叫方,要麼是Web端,要麼是移動端,既然是商城就要有一個Web端介面。
通過Nuget新增 IdentityModel。
在Web專案的Startup.cs的ConfigureServices方法中註冊快取使用,我們獲取的Token需要儲存在快取中重複使用:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddMemoryCache(); //註冊快取 }
public void Configure(IApplicationBuilder app, IHostingEnvironment env) { if (env.IsDevelopment()) { app.UseBrowserLink(); app.UseDeveloperExceptionPage(); } app.UseStaticFiles(); app.UseMvcWithDefaultRoute(); //新增預設的MVC請求路由 }
在Web專案的appsettings.json中配置對應的API專案地址:
{ "Logging": { "IncludeScopes": false, "LogLevel": { "Default": "Warning" } }, "ServiceAddress": { "Service.Identity": "http://localhost:7000/", "Service.Account": "http://localhost:7001/" }, "MehtodName": { "Account.MiUser.SSOLogin": "MiUser/SSOLogin", //登入 "Identity.Connect.Token": "connect/token" //獲取token } }
接下來我們需要在Web中獲取Token就需要有一個公用的方法,我在ApiHelper中添加了一個方法如下,這裡使用了IdentityModel提供的方法來獲取Token:
//獲取Token public static async Task<string> GetToken() { string token = null; if (cache.TryGetValue<string>("Token", out token)) { return token; } try { //DiscoveryClient類:IdentityModel提供給我們通過基礎地址(如:http://localhost:5000)就可以訪問令牌服務端; //當然可以根據上面的restful api裡面的url自行構建;上面就是通過基礎地址,獲取一個TokenClient;(對應restful的url:token_endpoint "http://localhost:5000/connect/token") //RequestClientCredentialsAsync方法:請求令牌; //獲取令牌後,就可以通過構建http請求訪問API介面;這裡使用HttpClient構建請求,獲取內容; var dico = await DiscoveryClient.GetAsync("http://localhost:7000"); var tokenClient = new TokenClient(dico.TokenEndpoint, "MI.Web", "miwebsecret"); var tokenResponse = await tokenClient.RequestClientCredentialsAsync("MI.Service"); if (tokenResponse.IsError) { throw new Exception(tokenResponse.Error); } token = tokenResponse.AccessToken; cache.Set<string>("Token", token, TimeSpan.FromSeconds(tokenResponse.ExpiresIn)); } catch (Exception ex) { throw new Exception(ex.Message); } return token; }
有了獲取令牌的方法還需要有一個請求API的POST幫助方法,如下:(大家可以根據自己的習慣替換,重點是要加入Token)
private static MemoryCache cache = new MemoryCache(new MemoryCacheOptions()); /// <summary> /// HttpClient實現Post請求 /// </summary> public static async Task<T> PostAsync<T>(string url, Dictionary<string, string> dic) { //設定HttpClientHandler的AutomaticDecompression var handler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip }; //建立HttpClient(注意傳入HttpClientHandler) using (var http = new HttpClient(handler)) { //新增Token var token = await GetToken(); http.SetBearerToken(token); //使用FormUrlEncodedContent做HttpContent var content = new FormUrlEncodedContent(dic); //await非同步等待迴應 var response = await http.PostAsync(url, content); //確保HTTP成功狀態值 response.EnsureSuccessStatusCode(); //await非同步讀取最後的JSON(注意此時gzip已經被自動解壓縮了,因為上面的AutomaticDecompression = DecompressionMethods.GZip) string Result = await response.Content.ReadAsStringAsync(); var Item = JsonConvert.DeserializeObject<T>(Result); return Item; } }
有了這些之後我們新建一個登陸控制器 LoginController,新建登陸方法:
public async Task<JsonResult> UserLogin(string UserName, string UserPwd) { string url = $"{configuration["ServiceAddress:Service.Account"]}{configuration["MehtodName:Account.MiUser.SSOLogin"]}"; var dictionary = new Dictionary<string, string>(); dictionary.Add("UserName", UserName); dictionary.Add("Password", MD5Helper.Get_MD5(UserPwd)); SSOLoginResponse response = null; try { response = await ApiHelper.PostAsync<SSOLoginResponse>(url, dictionary); } catch(Exception ex) { return Json(ex.Message); } if(response.Successful) { return Json("ok"); } return Json(response.Message); }
然後將三個專案分別釋出在IIS中,訪問Web登陸頁面:
輸入使用者密碼登陸測試,這裡我們會請求MI.Service.Account這個API專案的登陸方法:
登陸成功即說明通過了驗證,下一步將加入Ocelot,結合IdentityServer4實現閘道器轉發請求並驗證。