一、搭建一个ID4.IdServer(.NetCore API)认证服务器项目
1.1、在该项目中添加Nuget包(vs2.1版本安装IdentityServer4 2.5.3版本)
1.2、在ID4.IdServer项目中新建一个Config类
public class Config
{
/// <summary>
/// 返回应用列表
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{
List<ApiResource> resources = new List<ApiResource>();
//ApiResource第一个参数是应用的名字,第二个参数是描述
resources.Add(new ApiResource("MsgAPI", "消息服务API"));
resources.Add(new ApiResource("ProductAPI", "产品API"));
return resources;
}
/// <summary>
/// 返回账号列表
/// </summary>
/// <returns></returns>
public static IEnumerable<Client> GetClients()
{
List<Client> clients = new List<Client>();
clients.Add(new Client
{
ClientId = "clientPC1",//API账号、客户端Id
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets =
{
new Secret("123321".Sha256())//秘钥
},
AllowedScopes = { "MsgAPI", "ProductAPI" }//这个账号支持访问哪些应用
});
return clients;
}
}
1.3、在ID4.IdServer项目下Startup类中ConfigureServices方法进行注册
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//注册服务
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients());
}
1.4、在ID4.IdServer项目下Startup类中Configure方法进行注册中间件
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
//注册中间件
app.UseIdentityServer();
app.UseHttpsRedirection();
app.UseMvc();
}
1.5、然后在 5000 端口启动 、在 postman 里发出请求,获取 token
http://localhost:5000/connect/token,发 Post 请求,表单请求内容(注意不是报文头):
client_id=clientPC1 client_secret=123321 grant_type=client_credentials
![](https://img-blog.csdnimg.cn/20191111091613933.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTQwODg0MA==,size_16,color_FFFFFF,t_70)
二、 搭建 Ocelot 服务器项目
2.1、先在之前博客上OcelotTest API网关项目中添加Nuget包(vs2.1版本安装IdentityServer4 2.5.3版本)
2.2、在OcelotTest API网关项目configuration.json文件中进行修改
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/MsgService/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ],
"ServiceName": "MsgService",
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
"UseServiceDiscovery": true,
"AuthenticationOptions": {
"AuthenticationProviderKey": "MsgKey",
"AllowedScopes": []
}
},
{
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/ProductService/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ],
"ServiceName": "ProductService",
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
"UseServiceDiscovery": true,
"AuthenticationOptions": {
"AuthenticationProviderKey": "ProductKey",
"AllowedScopes": []
}
}
],
"GlobalConfiguration": {
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8500
}
}
}
2.3、在OcelotTest项目中修改 Startup类中ConfigureServices方法让 Ocelot 能够访问 Identity Server 进行 Token 的验证
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//指定Identity Server的信息
Action<IdentityServerAuthenticationOptions> isaOptMsg = o => {
o.Authority = "http://localhost:5003";
o.ApiName = "MsgAPI";//要连接的应用的名字
o.RequireHttpsMetadata = false;
o.SupportedTokens = SupportedTokens.Both;
o.ApiSecret = "123321";//秘钥
};
Action<IdentityServerAuthenticationOptions> isaOptProduct = o => {
o.Authority = "http://localhost:5003";
o.ApiName = "ProductAPI";//要连接的应用的名字
o.RequireHttpsMetadata = false;
o.SupportedTokens = SupportedTokens.Both;
o.ApiSecret = "123321";//秘钥
};
services.AddAuthentication()
//对配置文件中使用ChatKey配置了AuthenticationProviderKey=MsgKey
//的路由规则使用如下的验证方式
.AddIdentityServerAuthentication("MsgKey", isaOptMsg)
.AddIdentityServerAuthentication("ProductKey", isaOptProduct);
//注册ocelot服务
services.AddOcelot(Configuration).AddConsul();
}
2.4、在OcelotTest API网关项目Program类中进行修改
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseUrls("http://localhost:9500")
.ConfigureAppConfiguration((hostingContext, builder) =>
{
builder.AddJsonFile("configuration.json", false, true);
}).UseStartup<Startup>();
}
2.5、启动 Ocelot 服务器,然后向 ocelot 请求/MsgService/SMS/Send_MI(报文体还是要传 json 数据),在请求头(不是报文体)里加上: Authorization="Bearer "+上面 identityserver 返回的 accesstoken
![](https://img-blog.csdnimg.cn/20191111103756404.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTQwODg0MA==,size_16,color_FFFFFF,t_70)
如果返回 401,那就是认证错误。
Ocelot 会把 Authorization 值传递给后端服务器,这样在后端服务器可以用 IJwtDecoder
的这个不传递 key 的重载方法 IDictionary<string, object> DecodeToObject(string token),就可
以在不验证的情况下获取 client_id 等信息。
也可以把 Identity Server 通过 Consul 进行服务治理。
Ocelot+Identity Server 实现了接口的权限验证,各个业务系统不需要再去做验证。
三、用户名密码登录
3.1、在ID4.IdServer项目中新建一个ProfileService类
namespace ID4.IdServer
{
public class ProfileService : IProfileService
{
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var claims = context.Subject.Claims.ToList();
context.IssuedClaims = claims.ToList();
}
public async Task IsActiveAsync(IsActiveContext context)
{
context.IsActive = true;
}
}
}
3.2、在ID4.IdServer项目中再新建一个ResourceOwnerPasswordValidator验证类
public class ResourceOwnerPasswordValidator : IResourceOwnerPasswordValidator
{
public async Task ValidateAsync(ResourceOwnerPasswordValidationContext context)
{
//根据context.UserName和context.Password与数据库的数据做校验,判断是否合法
if (context.UserName == "yzk" && context.Password == "123")
{
context.Result = new GrantValidationResult(
subject: context.UserName,
authenticationMethod: "custom",
claims: new Claim[] { new Claim("Name",context.UserName), new Claim("UserId","111"), new Claim("RealName", "杨中科"), new Claim("Email", "yzk365@qq.com") });
}
else
{
//验证失败
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant, "invalid custom credential");
}
}
}
(注:当然这里的用户名密码是写死的,可以在项目中连接自己的用户数据库进行验证。claims 中可以 放入多组用户的信息,这些信息都可以在业务系统中获取到)
3.3、进行修改ID4.IdServer项目中Config类
public class Config
{
/// <summary>
/// 返回应用列表
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{
List<ApiResource> resources = new List<ApiResource>();
//ApiResource第一个参数是应用的名字,第二个参数是描述
resources.Add(new ApiResource("MsgAPI", "消息服务API"));
resources.Add(new ApiResource("ProductAPI", "产品API"));
return resources;
}
/// <summary>
/// 返回客户端账号列表
/// </summary>
/// <returns></returns>
public static IEnumerable<Client> GetClients()
{
List<Client> clients = new List<Client>();
clients.Add(new Client
{
ClientId = "clientPC1",//API账号、客户端Id
AllowedGrantTypes = GrantTypes.ResourceOwnerPassword,
ClientSecrets =
{
new Secret("123321".Sha256())//秘钥
},
AllowedScopes = { "MsgAPI","ProductAPI",IdentityServerConstants.StandardScopes.OpenId, //必须要添加,否则报forbidden错误
IdentityServerConstants.StandardScopes.Profile }//这个账号支持访问哪些应用
});
return clients;
}
}
3.4、进行修改ID4.IdServer项目中Startup类中ConfigureServices方法
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//注册服务
var idResources = new List<IdentityResource>
{
new IdentityResources.OpenId(), //必须要添加,否则报无效的 scope 错误
new IdentityResources.Profile()
};
services.AddIdentityServer()
.AddDeveloperSigningCredential()
.AddInMemoryIdentityResources(idResources)
.AddInMemoryApiResources(Config.GetApiResources())
.AddInMemoryClients(Config.GetClients())//
.AddResourceOwnerValidator<ResourceOwnerPasswordValidator>()
.AddProfileService<ProfileService>();
}
(注:主 要 是 增 加 了 AddInMemoryIdentityResources 、 AddResourceOwnerValidator 、AddProfileService)
3.5、进行修改ID4.IdServer项目中Program类
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>().UseUrls($"http://localhost:5003");
}
3.6 修改业务系统
3.6.1、在之前博客上MsgService信息服务项目中添加Nuget包(IdentityServer4.AccessTokenValidation)
3.6.2、在MsgService项目下Startup类中ConfigureServices方法进行修改
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
//注册服务
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = "http://localhost:5003";//identity server 地址
options.RequireHttpsMetadata = false;
});
}
3.6.3、在MsgService项目下Startup类中Configure方法添加注册中间件
//注册中间件
app.UseAuthentication();
3.6.4、在MsgService项目下Program类中进行修改
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args)
{
var config = new ConfigurationBuilder().AddCommandLine(args).Build();
String ip = config["ip"];
String port = config["port"];
return WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>()
.UseUrls($"http://{ip}:{port}");
}
}
3.7、请求 token 、把报文头中的 grant_type 值改为 password,报文头增加 username、password 为用户名、密码
![](https://img-blog.csdnimg.cn/20191111205837362.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTQwODg0MA==,size_16,color_FFFFFF,t_70)
四、创建一个LoginService(.NetCore Api) 独立登录服务器项目
4.1、在LoginService项目中新建一个RequestTokenParam类
public class RequestTokenParam
{
public string username { get; set; }
public string password { get; set; }
}
4.2、在LoginService项目中新建一个LoginController(Api)控制器
namespace LoginService.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class LoginController : ControllerBase
{
[HttpPost]
public async Task<ActionResult> RequestToken(RequestTokenParam model)
{
Dictionary<string, string> dict = new Dictionary<string, string>();
dict["client_id"] = "clientPC1";
dict["client_secret"] = "123321";
dict["grant_type"] = "password";
dict["username"] = model.username;
dict["password"] = model.password;
//由登录服务器向IdentityServer发请求获取Token
using (HttpClient http = new HttpClient())
using (var content = new FormUrlEncodedContent(dict))
{
var msg = await http.PostAsync("http://localhost:5003/connect/token", content);
string result = await msg.Content.ReadAsStringAsync();
return Content(result, "application/json");
}
}
}
}
(这 样 客 户 端 只 要 向 LoginService 的 /api/Login/ 发 请 求 带 上 json 报文体 {username:"yzk",password:"123"}即可。客户端就不知道 client_secret 这些机密信息了)
![](https://img-blog.csdnimg.cn/20191111211932415.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTQwODg0MA==,size_16,color_FFFFFF,t_70)
4.3、在LoginService项目中对Program类进行修改
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>().UseUrls($"http://localhost:6008");
}
五、创建一个LoginServer(WinForm窗体)项目
5.1、在LoginServer项目中添加Nuget包(Newtonsoft.Json)
5.2、在LoginServer项目Form1窗体中添加QQ登录按钮
![](https://img-blog.csdnimg.cn/20191111214107835.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTQwODg0MA==,size_16,color_FFFFFF,t_70)
5.3、Form1窗体按钮点击事件代码
public partial class Form1 : Form
{
public static string token;
public Form1()
{
InitializeComponent();
}
public class SendSMSRequest
{
public string PhoneNum { get; set; }
public string Msg { get; set; }
}
private void Form1_Load(object sender, EventArgs e)
{
FrmLogin login = new FrmLogin();
login.ShowDialog();
}
private async void BtnQQLogin_Click(object sender, EventArgs e)
{
SendSMSRequest req = new SendSMSRequest();
req.PhoneNum = "189189189";
req.Msg = "hello world!";
string jsonReq = JsonConvert.SerializeObject(req);
using (var http = new HttpClient())
using (var content = new StringContent(jsonReq, Encoding.UTF8, "application/json"))
{
http.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(token);
var resp = await http.PostAsync("http://localhost:9500/MsgService/SMS/Send_LX", content);
if (resp.StatusCode != System.Net.HttpStatusCode.OK)
{
MessageBox.Show("发送错误,状态码" + resp.StatusCode);
}
else
{
MessageBox.Show("发送成功");
}
}
}
}
5.4、在LoginServer项目新建一个FrmLogin窗体
![](https://img-blog.csdnimg.cn/2019111121453069.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80NTQwODg0MA==,size_16,color_FFFFFF,t_70)
5.5、FrmLogin窗体中代码
public partial class FrmLogin : Form
{
public FrmLogin()
{
InitializeComponent();
}
class LoginReq
{
public string username { get; set; }
public string password { get; set; }
}
private void FrmLogin_Load(object sender, EventArgs e)
{
}
private async void BtnLogin_Click(object sender, EventArgs e)
{
LoginReq req = new LoginReq();
req.username = txtUserName.Text;
req.password = txtPassword.Text;
//转换成json
string jsonReq = JsonConvert.SerializeObject(req);
//发送请求
using (HttpClient http = new HttpClient())
using (var content = new StringContent(jsonReq, Encoding.UTF8, "application/json"))
{
var resp = await http.PostAsync("http://localhost:9500/LoginService/Login", content);
if (resp.IsSuccessStatusCode == false)
{
MessageBox.Show("error" + resp.StatusCode);
return;
}
string jsonResp = await resp.Content.ReadAsStringAsync();
dynamic respObj = JsonConvert.DeserializeObject<dynamic>(jsonResp);
//获取token
string access_token = respObj.access_token;
string token_type = respObj.token_type;
// MessageBox.Show(token_type+" "+access_token);
Form1.token = token_type + " " + access_token;
this.Close();
}
}
}
六、操作OcelotTest(API 网关)项目
6.1、在OcelotTest项目下configuration.json文件再进一步修改
{
"ReRoutes": [
{
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/MsgService/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ],
"ServiceName": "MsgService",
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
"UseServiceDiscovery": true,
"AuthenticationOptions": {
"AuthenticationProviderKey": "MsgKey",
"AllowedScopes": []
}
},
{
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"UpstreamPathTemplate": "/ProductService/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ],
"ServiceName": "ProductService",
"LoadBalancerOptions": {
"Type": "RoundRobin"
},
"UseServiceDiscovery": true,
"AuthenticationOptions": {
"AuthenticationProviderKey": "ProductKey",
"AllowedScopes": []
}
},
{
"DownstreamPathTemplate": "/api/{url}",
"DownstreamScheme": "http",
"DownstreamHostAndPorts": [
{
"Host": "localhost",
"Port": 6008
}
],
"UpstreamPathTemplate": "/LoginService/{url}",
"UpstreamHttpMethod": [ "Get", "Post" ]
}
],
"GlobalConfiguration": {
"ServiceDiscoveryProvider": {
"Host": "localhost",
"Port": 8500
}
}
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)