1. 程式人生 > >.Net Core 商城微服務專案系列(一):使用IdentityServer4構建基礎登入驗證

.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 [] { new
Secret("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實現閘道器轉發請求並驗證。