04 建立 IdentityServer4 项目,Client Credentials.mp4
大纲:
- 介绍 ldentityServer4
- 搭建 IdentityServer4 项目
- 保护客户端:使用 OAuth 2.0 Client Credentials
介绍 IdentityServer4
参考官方文档 Terminology。
(官方 Terminology 配图)
IdentityServer4 是一个 OpenID Connect provider,它的主要功能包括:
- protect your resources
- authenticate users using a local account store or via an external identity provider
- provide session management and single sign-on(单点登录)
- manage and authenticate clients
- issue identity and access tokens to clients
- validate tokens
Packaging and Builds 里面列出了 IdentityServer 4 相关的包的位置。
搭建 IdentityServer4 项目
- 参考官方文档 QuickStarts/Overview
- 项目模板源码:IdentityServer4.Templates
- 示例源码:IdentityServer4.Samples
通过 dotnet new is4inmem --name Idp
命令创建一个 IdentityServer4 with In-Memory Stores and Test Users 项目。
查看项目 Startup ConfigureServices 的代码,依次匹配官方示意图中的每个部分。
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
})
.AddTestUsers(TestUsers.Users);
// in-memory, code config
builder.AddInMemoryIdentityResources(Config.GetIdentityResources());
builder.AddInMemoryApiResources(Config.GetApis());
builder.AddInMemoryClients(Config.GetClients());
使用 OAuth 2.0 Client Credentials
参考官方文档“创建 Console 客户端,通过 Client Credentials 向 IdentityServer 请求 Access Token,并访问受保护资源”。
Client Credentials: The Client Credentials grant is used when applications request an access token to access their own resources, not on behalf of a user.
C# 7.1 异步的 Main 方法
创建 console client 时通过在 csproj 里面指定 Language Version 使项目支持异步的 Main 方法:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
...
</Project>
老写法:
static void Main(string[] args)
{
var client = new HttpClient();
Task.Run(async () =>
{
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000/");
}).GetAwaiter().GetResult();
}
7.1:
static async Task Main(string[] args)
{
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000/");
}
尝试访问身份认证资源
在 Idp 中给 console client 添加 OpenID Scope:
public static IEnumerable<Client> GetClients()
{
return new[]
{
// client credentials flow client
new Client
{
ClientId = "console client",
ClientName = "Client Credentials Client",
AllowedGrantTypes = GrantTypes.ClientCredentials,
ClientSecrets = { new Secret("511536EF-F270-4058-80CA-1C89C192F69A".Sha256()) },
AllowedScopes = { "api1" ,IdentityServerConstants.StandardScopes.OpenId}
}
};
}
在 console client 中访问 OpenID 资源:
static async Task Main(string[] args)
{
// Discoverty endpoint
var client = new HttpClient();
var disco = await client.GetDiscoveryDocumentAsync("http://localhost:5000/");
if (disco.IsError)
{
Console.WriteLine(disco.Error);
return;
}
// Request access token
var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "console client",
ClientSecret = "511536EF-F270-4058-80CA-1C89C192F69A",
Scope = "api1 openid"
});
if (tokenResponse.IsError)
{
Console.WriteLine(tokenResponse.Error);
// invalid_scope
return;
}
...
}
tokenResponse 报错 invalid_scope。这是因为 Client Credentials 授权方式不代表任何用户,所以无法通过它访问 IdentityServer 上身份认证的资源。
创建并访问 Api1Resource
由于无法通过 Client Credentials 访问 OpenID,也就不方便演示。所以又参考官方教程创建了 Api1Resource。
Api1Resource 中返回了 User.Claims 的一些信息:
[Route("identity")]
[Authorize]
public class IdentityController : ControllerBase
{
[HttpGet]
public IActionResult Get()
{
return new JsonResult(from c in User.Claims select new { c.Type, c.Value });
}
}
修改 Startup,添加 Authentication 服务,并将 Authentication 中间件添加到管道中:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore()
.AddAuthorization()
.AddJsonFormatters();
services.AddAuthentication("Bearer")
.AddJwtBearer("Bearer", options =>
{
options.Authority = "http://localhost:5000";
options.RequireHttpsMetadata = false;
options.Audience = "api1";
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseMvc();
}
AddAuthentication
adds the authentication services to DI and configures"Bearer"
as the default scheme.UseAuthentication
adds the authentication middleware to the pipeline so authentication will be performed automatically on every call into the host.Navigating to the controller
http://localhost:5001/identity
on a browser should return a 401 status code. This means your API requires a credential and is now protected by IdentityServer.
修改 console client 中的代码,以访问 Api1Resource,并打印 Claim 信息:
static async Task Main(string[] args)
{
// Discoverty endpoint
...
// Request access token
...
// Call Api1Resource
var apiClient = new HttpClient();
apiClient.SetBearerToken(tokenResponse.AccessToken);
var response = await apiClient.GetAsync("http://localhost:5001/identity");
if (!response.IsSuccessStatusCode)
{
Console.WriteLine(response.StatusCode);
}
else
{
var content = await response.Content.ReadAsStringAsync();
Console.WriteLine(JArray.Parse(content));
}
Console.ReadKey();
}
效果: