一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第10天,点击查看活动详情。
1.secrets
在一些情况下,客户端需要使用身份服务器进行身份验证,例如
- 在令牌端点请求令牌的secrets应用程序(也称为客户端)
- API在内省端点验证引用令牌
可以将secrets列表分配给客户端或API资源。
secrets解析和验证是身份服务器中的可扩展点,它支持共享secrets以及通过基本身份验证头或POST主体传输共享secrets。
1. 创建共享密钥
设置散列共享密钥:
var secret = new Secret("secret".Sha256());
现在可以将此秘密分配给Client或ApiResource。请注意,它们不仅支持单个秘密,还支持多个秘密。这对于秘密翻转和轮换非常有用:
var client = new Client
{
ClientId = "client",
ClientSecrets = new List<Secret> { secret },
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = new List<string>
{
"api1", "api2"
}
};
可以为secrets分配说明和到期日期。描述将用于记录以及用于设置secrets生存期的到期日期:
var secret = new Secret(
"secret".Sha256(),
"2016 secret",
new DateTime(2016, 12, 31));
2. 使用共享密钥进行身份验证
可以将Client id/secret组合作为POST body的一部分发送,或作为基本认证头:
POST /connect/token
client_id=client1&
client_secret=secret&
...
POST /connect/token
Authorization: Basic xxxxx
...
可以使用以下C#代码手动创建基本身份验证标头:
var credentials = string.Format("{0}:{1}", clientId, clientSecret);
var headerValue = Convert.ToBase64String(Encoding.UTF8.GetBytes(credentials));
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", headerValue);
该IdentityModel库有一个叫做辅助类TokenClient和IntrospectionClient封装认证和协议消息。
3. 超越共享秘密
还有其他技术来验证客户端,例如基于公钥/私钥密码术。IdentityServer包括对私钥JWT客户端机密的支持秘密可扩展性通常由三件事组成:
- 一个secrets的定义
- 一个知道如何从传入请求中提取secrets的secrets解析器
- 一个secrets验证器,知道如何根据定义验证解析的secrets
secrets解析器和验证器是ISecretParser和ISecretValidator接口的实现。要使用于IdentityServer,需要将它们注册到DI容器,例如:
builder.AddSecretParser<JwtBearerClientAssertionSecretParser>()
builder.AddSecretValidator<PrivateKeyJwtSecretValidator>()
我们的默认私钥JWT secrets验证器期望完整的证书作为secrets定义的base64。然后此证书将用于验证自签名JWT上的签名,例如:
var client = new Client
{
ClientId = "client.jwt",
ClientSecrets =
{
new Secret
{
Type = IdentityServerConstants.SecretTypes.X509CertificateBase64,
Value = "MIIDATCCAe2gAwIBAgIQoHUYAquk9rBJcq8W+F0FAzAJBgUrDgMCHQUAMBIxEDAOBgNVBAMTB0RldlJvb3QwHhcNMTAwMTIwMjMwMDAwWhcNMjAwMTIwMjMwMDAwWjARMQ8wDQYDVQQDEwZDbGllbnQwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDSaY4x1eXqjHF1iXQcF3pbFrIbmNw19w/IdOQxbavmuPbhY7jX0IORu/GQiHjmhqWt8F4G7KGLhXLC1j7rXdDmxXRyVJBZBTEaSYukuX7zGeUXscdpgODLQVay/0hUGz54aDZPAhtBHaYbog+yH10sCXgV1Mxtzx3dGelA6pPwiAmXwFxjJ1HGsS/hdbt+vgXhdlzud3ZSfyI/TJAnFeKxsmbJUyqMfoBl1zFKG4MOvgHhBjekp+r8gYNGknMYu9JDFr1ue0wylaw9UwG8ZXAkYmYbn2wN/CpJl3gJgX42/9g87uLvtVAmz5L+rZQTlS1ibv54ScR2lcRpGQiQav/LAgMBAAGjXDBaMBMGA1UdJQQMMAoGCCsGAQUFBwMCMEMGA1UdAQQ8MDqAENIWANpX5DZ3bX3WvoDfy0GhFDASMRAwDgYDVQQDEwdEZXZSb290ghAsWTt7E82DjU1E1p427Qj2MAkGBSsOAwIdBQADggEBADLje0qbqGVPaZHINLn+WSM2czZk0b5NG80btp7arjgDYoWBIe2TSOkkApTRhLPfmZTsaiI3Ro/64q+Dk3z3Kt7w+grHqu5nYhsn7xQFAQUf3y2KcJnRdIEk0jrLM4vgIzYdXsoC6YO+9QnlkNqcN36Y8IpSVSTda6gRKvGXiAhu42e2Qey/WNMFOL+YzMXGt/nDHL/qRKsuXBOarIb++43DV3YnxGTx22llhOnPpuZ9/gnNY7KLjODaiEciKhaKqt/b57mTEz4jTF4kIg6BP03MUfDXeVlM1Qf1jB43G2QQ19n5lUiqTpmQkcfLfyci2uBZ8BkOhXr3Vk9HIk/xBXQ="
}
},
AllowedGrantTypes = GrantTypes.ClientCredentials,
AllowedScopes = { "api1", "api2" }
};
2.扩展授权
OAuth 2.0为令牌端点定义了标准授权类型,如password,authorization_code和refresh_token。扩展授权是一种添加对非标准令牌颁发方案(如令牌转换,委派或自定义凭据)的支持的方法。
可以通过实现IExtensionGrantValidator接口添加对其他授权类型的支持:
public interface IExtensionGrantValidator
{
Task ValidateAsync(ExtensionGrantValidationContext context);
string GrantType { get; }
}
该ExtensionGrantValidationContext对象使您可以访问:
- 传入令牌请求 -验证值,以及自定义值(通过
Raw集合) - 结果 - 错误或成功
- 自定义响应参数
要注册扩展授权将其添加到DI容器:
builder.AddExtensionGrantValidator<MyExtensionsGrantValidator>();
1.使用扩展授权的简单委派
想象一下以下场景 - 前端客户端使用通过交互流(例如混合流)获取的令牌调用中间层API。此中间层API(API 1)现在希望代表交互式用户调用后端API(API 2):
换句话说,中间层API(API 1)需要包含用户身份的访问令牌,但需要具有后端API(API 2)的范围。
前端的访问令牌只是转发到后端。这有一些缺点,例如API 2现在必须接受API 1范围,这将允许用户直接调用API 2。此外 - 您可能希望在令牌中添加一些特定于委托的声明,例如,调用路径是通过API。
2. 实现扩展授权
前端会将令牌发送到API 1,现在需要在IdentityServer上使用API 2的新令牌交换此令牌。
在线上,对交换的令牌服务的调用可能如下所示:
POST /connect/token
grant_type=delegation&
scope=api2&
token=...&
client_id=api1.client
client_secret=secret
扩展授权验证程序的工作是通过验证传入令牌来处理该请求,并返回表示新令牌的结果:
public class DelegationGrantValidator : IExtensionGrantValidator
{
private readonly ITokenValidator _validator;
public DelegationGrantValidator(ITokenValidator validator)
{
_validator = validator;
}
public string GrantType => "delegation";
public async Task ValidateAsync(ExtensionGrantValidationContext context)
{
var userToken = context.Request.Raw.Get("token");
if (string.IsNullOrEmpty(userToken))
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
var result = await _validator.ValidateAccessTokenAsync(userToken);
if (result.IsError)
{
context.Result = new GrantValidationResult(TokenRequestErrors.InvalidGrant);
return;
}
// get user's identity
var sub = result.Claims.FirstOrDefault(c => c.Type == "sub").Value;
context.Result = new GrantValidationResult(sub, GrantType);
return;
}
}
不要忘记在DI上注册验证器。
3. 注册委托客户端
需要在IdentityServer中进行客户端注册,以允许客户端使用此新的扩展授权,例如:
var client = new client
{
ClientId = "api1.client",
ClientSecrets = new List<Secret>
{
new Secret("secret".Sha256())
},
AllowedGrantTypes = { "delegation" },
AllowedScopes = new List<string>
{
"api2"
}
}
在API 1中,现在可以自己构建HTTP有效内容,或使用IdentityModel帮助程序库:
public async Task<TokenResponse> DelegateAsync(string userToken)
{
var payload = new
{
token = userToken
};
// create token client
var client = new TokenClient(disco.TokenEndpoint, "api1.client", "secret");
// send custom grant to token endpoint, return response
return await client.RequestCustomGrantAsync("delegation", "api2", payload);
}
现在TokenResponse.AccessToken将包含委托访问令牌。
4.资源密码验证
如果要使用OAuth 2.0资源所有者密码凭据授权(aka password),则需要实现并注册IResourceOwnerPasswordValidator接口:
public interface IResourceOwnerPasswordValidator
{
/// <summary>
/// Validates the resource owner password credential
/// </summary>
/// <param name="context">The context.</param>
Task ValidateAsync(ResourceOwnerPasswordValidationContext context);
}
在上下文中,您将找到已解析的协议参数,如UserName和Password,以及原始请求,如果您想查看其他输入数据,然后,您的工作是实施密码验证并相应地设置Result上下文。