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