直接上代码,该说明的我都把注释写在代码中了
逻辑图如下:
一、创建WebApi(用于IdentityServer4发送token和验证token)(IdentityServerSolution)
1.nuget安装identityServer4(最新版必须是.net core3.0才可以,我是2.0,所以装不了最新版的):
Install-Package IdentityServer4 -version 2.5.4
2.创建Config类:配置资源和客户端(家里哪些东西可以被访问,以及客户端得满足什么条件才可以访问)
using IdentityServer4.Models;
using System.Collections.Generic;
namespace IdentityServerSolution
{
public class Config
{
/// <summary>
/// 允许访问哪些Api(就像我允许我家里的哪些东西可以让顾客访问使用,如桌子,椅子等等) CreateDate:2019-12-26 14:08:29;Author:Ling_bug
/// </summary>
/// <returns></returns>
public static IEnumerable<ApiResource> GetApiResources()
{
return new[]
{
new ApiResource("api1", "Lingbug Api1"),
new ApiResource("api2", "Lingbug Api2")
};
}
/// <summary>
/// 允许哪些客户端访问(就像我要求顾客必须具备哪些条件才可以拿到进入我家的钥匙) CreateDate:2019-12-26 14:09:51;Author:Ling_bug
/// </summary>
/// <returns></returns>
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
//对应请求参数的client_id(假设身高)
ClientId = "Client",
//对应请求参数的grant_type(GrantTypes.ClientCredentials是client_credentials)(假设体重)
AllowedGrantTypes = GrantTypes.ClientCredentials,
//对应请求参数的client_secret(假设口令)
ClientSecrets =
{
new Secret(Encrypt("secret")),
},
//对应请求参数的Scope,这里写的类似于一个id,对应的是上面的ApiResource的key
/*
* (这里假设为是VIP卡片,一个资源一张卡片,每个卡片对应的我上面的资源的key,
* 所以,
* 1.哪怕你有卡片,但是我这个资源不开放或者没有对应的资源,你也是访问不到的,即401
* 2.哪怕我有资源,你前面的身高体重都符合,口令也正确,但是你没有VIP卡片,你也是无法访问的)
*/
AllowedScopes = {"api1", "api2"}
}
};
}
/// <summary>
/// 加密 CreateDate:2019-12-26 11:19:04;Author:Ling_bug
/// </summary>
/// <param name="valueString"></param>
/// <returns></returns>
private static string Encrypt(string valueString)
{
return string.IsNullOrWhiteSpace(valueString) ? null : valueString.Sha256();
}
}
}
3.startup配置:
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace IdentityServerSolution
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentityServer()//配置id4(跟物业说我现在需要给顾客分配钥匙)
.AddInMemoryApiResources(Config.GetApiResources())//配置允许请求的api(告诉物业我家里的哪些东西可以让顾客使用)
.AddInMemoryClients(Config.GetClients())//配置允许哪些client请求(告诉物业满足什么条件的顾客才可以拿到我家的钥匙)
.AddDeveloperSigningCredential();
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//启用id4,将id4中间件添加到管道中(跟物业说一下,身份符合要求的才可以发送钥匙)
app.UseIdentityServer();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
}
4.此时已经identityServer4服务端创建完毕了,我们可以运行起来
a.访问http://localhost:3065/.well-known/openid-configuration,可以看到identityServer4的配置,可以不用管,知道就可以
b.在项目根目录会多一个tempkey.rsa文件,这个也不用管
c.使用postman,获取到你心心念念的token
二、创建WebApi(项目中客户端真实需要请求的服务)(IdentityApi)
1.startup配置:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace IdentityApi
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
//配置身份认证(告诉物业任何进来的顾客都需要身份验证,看看钥匙对不对)
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
{
//身份认证平台地址(身份认证时会去这个地址验证token是否有效)(告诉物业去哪里验证客户的钥匙是否正确)
options.Authority = "http://localhost:3065";
//一般默认(true),开发环境时可以设置为false
options.RequireHttpsMetadata = false;
//apiResource(这里目前还还不知道如何配置多个)
//(钥匙正确,进入要使用啥的顾客才可以放行,其他的不允许,这里就是进去要使用api1的放行,其他的钥匙正确也不让你进)
options.Audience = "api1";
});
services.AddMvc();
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
//启用身份认证中间件(告诉物业只认钥匙不认人)
app.UseAuthentication();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc();
}
}
}
2.创建服务服务器,获取当前用户信息:
using System.Linq;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace IdentityApi.Controllers
{
[Authorize]//Authorize特性启用身份认证
[Route("Identity")]
public class IdentityController : ControllerBase
{
/// <summary>
/// 获取到当前用户的身份信息 CreateDate:2019-12-26 11:25:05;Author:Ling_bug
/// </summary>
/// <returns></returns>
[HttpGet]
public IActionResult Get()
{
var data = User.Claims.Select(r => new { r.Type, r.Value });
return new JsonResult(data);
}
}
}
三、控制台应用程序(用来模拟客户端发送请求)(IdentityModelClient)
using IdentityModel.Client;
using Newtonsoft.Json.Linq;
using System;
using System.Net.Http;
namespace IdentityModelClient
{
class Program
{
static void Main(string[] args)
{
try
{
TestIdentity4Async();
}
catch (Exception e)
{
Console.WriteLine("异常:" + e.Message);
}
Console.ReadKey();
}
private static async void TestIdentity4Async()
{
//获取到获取token的url
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:3065");
if (disco.IsError)
{
Console.WriteLine("获取到获取token的url失败:" + disco.Error);
return;
}
Console.WriteLine("获取token的url为:" + disco.TokenEndpoint);
Console.WriteLine();
//获取token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,//就是我们postman请求token的地址
ClientId = "Client",//客户端
ClientSecret = "secret",//秘钥
Scope = "api1"//请求的api
});
if (tokenResponse.IsError)
{
Console.WriteLine("获取token失败:" + tokenResponse.Error);
return;
}
Console.WriteLine("获取token的response:");
int index = 0;
foreach (var proc in tokenResponse.GetType().GetProperties())
{
Console.WriteLine($"{++index}.{proc.Name}:{proc.GetValue(tokenResponse)}");
}
Console.WriteLine();
//模拟客户端调用需要身份认证的api
var apiClient = new HttpClient();
//赋值token(携带token访问)
apiClient.SetBearerToken(tokenResponse.AccessToken);
//发起请求
var response = await apiClient.GetAsync("http://localhost:10469/Identity");
if (response.IsSuccessStatusCode)
{
//请求成功
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine("请求成功,返回结果是:" + JArray.Parse(content));
}
else
{
//请求失败
Console.WriteLine($"请求失败,状态码为:{(int)response.StatusCode},描述:{response.StatusCode.ToString()}");
}
}
}
}
运行起来,结果为:
Ending~