这里第一次搭建,所以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实现网关转发请求并验证。
.Net Core 商城微服务项目系列(一):使用IdentityServer4构建基础登录验证
原文地址:https://www.cnblogs.com/weiBlog/p/9822416.html