首页 资讯 社群 我的社区 搜索

.Net Core 商城微服务项目系列(一):使用IdentityServer4构建基础登录验证

LM123
2019-06-18 17:30:13

这里第一次搭建,所以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实现网关转发请求并验证。

用户评论