.Net Core 商城微服務項目系列(一):使用IdentityServer4構建基礎登陸驗證

這裏第一次搭建,因此IdentityServer端比較簡單,後期再進行完善。web

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的名稱。json

Client是發起調用發,好比咱們的Web系統會調用API,那Web系統就是一個Client,也能夠理解爲一個角色,Client Id是角色標識,這個也須要在發起調用方那邊配置,ClientSecrets是私鑰,這裏使用最簡單的自帶私鑰,AllowedScopes是當前這個Client能夠訪問的ApiResource。api

TestUser是IdentityServer自帶的測試用戶類,用戶使用用戶名和密碼的方式登陸使用。緩存

 

而後須要在Startup中添加IdentityServer配置:restful

在ConfigureServices方法中添加以下:cookie

services.AddIdentityServer()
           .AddDeveloperSigningCredential()
           .AddTestUsers(InMemoryConfiguration.GetUsers().ToList())
           .AddInMemoryClients(InMemoryConfiguration.GetClients())
           .AddInMemoryApiResources(InMemoryConfiguration.GetApiResources());

這裏咱們使用的均是內存級別的配置,在實際項目裏建議改成數據庫中讀取。app

 

而後在Configure方法中啓用IdentityServer:異步

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseIdentityServer();
            app.UseStaticFiles();
            app.UseMvcWithDefaultRoute();
        }

到此IdentityServer驗證端配置完畢。async

 

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

相關文章
相關標籤/搜索