.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實現網關轉發請求並驗證。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM