ClaimKeyValue 模块初步 1
This commit is contained in:
parent
110db553fd
commit
e06576fa46
17
Falcon.SugarApi/ClaimTicket/ClaimKeyValue.cs
Normal file
17
Falcon.SugarApi/ClaimTicket/ClaimKeyValue.cs
Normal file
|
@ -0,0 +1,17 @@
|
|||
namespace Falcon.SugarApi.ClaimTicket
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储claim的结构
|
||||
/// </summary>
|
||||
public class ClaimKeyValue
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储Type
|
||||
/// </summary>
|
||||
public string Key { get; set; }
|
||||
/// <summary>
|
||||
/// 存储值
|
||||
/// </summary>
|
||||
public string Value { get; set; }
|
||||
}
|
||||
}
|
54
Falcon.SugarApi/ClaimTicket/ClaimTicketModelBinding.cs
Normal file
54
Falcon.SugarApi/ClaimTicket/ClaimTicketModelBinding.cs
Normal file
|
@ -0,0 +1,54 @@
|
|||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Falcon.SugarApi.ClaimTicket
|
||||
{
|
||||
/// <summary>
|
||||
/// 模型绑定
|
||||
/// </summary>
|
||||
public class ClaimTicketModelBinding:IModelBinder
|
||||
{
|
||||
/// <summary>
|
||||
/// 票据创建器
|
||||
/// </summary>
|
||||
public ITicketBuilder Builter { get; set; }
|
||||
/// <summary>
|
||||
/// 配置
|
||||
/// </summary>
|
||||
public ClaimTicketOptions Options { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 通过票据创建器创建模型绑定
|
||||
/// </summary>
|
||||
/// <param name="builter"></param>
|
||||
/// <param name="options"></param>
|
||||
public ClaimTicketModelBinding(ITicketBuilder builter,ClaimTicketOptions options) {
|
||||
Builter = builter;
|
||||
this.Options = options;
|
||||
}
|
||||
|
||||
Task IModelBinder.BindModelAsync(ModelBindingContext bindingContext) {
|
||||
if(bindingContext.ModelType != typeof(UserTicket)) {
|
||||
return FailBind(bindingContext);
|
||||
}
|
||||
var token = bindingContext.ValueProvider.GetValue(this.Options.HttpHeaderKey).ToString();
|
||||
if(token.IsNullOrEmpty()) {
|
||||
token = bindingContext.HttpContext.Request.Headers[this.Options.HttpHeaderKey].ToString();
|
||||
}
|
||||
var model = this.Builter.GetUser(token);
|
||||
if(model == null) {
|
||||
return FailBind(bindingContext);
|
||||
}
|
||||
|
||||
bindingContext.Model = model;
|
||||
bindingContext.Result = ModelBindingResult.Success(bindingContext.Model);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
private static Task FailBind(ModelBindingContext bindingContext) {
|
||||
bindingContext.Result = ModelBindingResult.Failed();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
||||
using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
|
||||
using System;
|
||||
|
||||
namespace Falcon.SugarApi.ClaimTicket
|
||||
{
|
||||
/// <summary>
|
||||
/// ClaimTicket模型绑定提供器
|
||||
/// </summary>
|
||||
public class ClaimTicketModelBindingProvider:IModelBinderProvider
|
||||
{
|
||||
IModelBinder? IModelBinderProvider.GetBinder(ModelBinderProviderContext context) {
|
||||
if(context == null) {
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
if(context.Metadata.ModelType == typeof(UserTicket)) {
|
||||
return new BinderTypeModelBinder(typeof(ClaimTicketModelBinding));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
27
Falcon.SugarApi/ClaimTicket/ClaimTicketOptions.cs
Normal file
27
Falcon.SugarApi/ClaimTicket/ClaimTicketOptions.cs
Normal file
|
@ -0,0 +1,27 @@
|
|||
using Falcon.SugarApi.Encryption;
|
||||
|
||||
namespace Falcon.SugarApi.ClaimTicket
|
||||
{
|
||||
/// <summary>
|
||||
/// 票据系统配置
|
||||
/// </summary>
|
||||
public class ClaimTicketOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 加密键
|
||||
/// </summary>
|
||||
public string EncryptionKey { get; set; } = "E9319792CB7249AD8E432000E9F2FE7A";
|
||||
/// <summary>
|
||||
/// http头票据key
|
||||
/// </summary>
|
||||
public string HttpHeaderKey { get; set; } = "_authUserTicket";
|
||||
/// <summary>
|
||||
/// 安全加密组件
|
||||
/// </summary>
|
||||
public IEncryption Encryption { get; set; }
|
||||
/// <summary>
|
||||
/// 序列化组件
|
||||
/// </summary>
|
||||
public ISerialize JsonSerialize { get; set; }
|
||||
}
|
||||
}
|
25
Falcon.SugarApi/ClaimTicket/ITicketBuilder.cs
Normal file
25
Falcon.SugarApi/ClaimTicket/ITicketBuilder.cs
Normal file
|
@ -0,0 +1,25 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Falcon.SugarApi.ClaimTicket
|
||||
{
|
||||
/// <summary>
|
||||
/// 票据生成接口
|
||||
/// </summary>
|
||||
public interface ITicketBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// 根据用户生成票据
|
||||
/// </summary>
|
||||
/// <param name="userTicket">用户票据</param>
|
||||
/// <returns>ticket</returns>
|
||||
string? GetTicket(UserTicket userTicket);
|
||||
/// <summary>
|
||||
/// 通过ticket获取用户
|
||||
/// </summary>
|
||||
/// <param name="ticket">token</param>
|
||||
/// <returns>声明组</returns>
|
||||
UserTicket? GetUser(string ticket);
|
||||
|
||||
}
|
||||
}
|
180
Falcon.SugarApi/ClaimTicket/Readme.md
Normal file
180
Falcon.SugarApi/ClaimTicket/Readme.md
Normal file
|
@ -0,0 +1,180 @@
|
|||
## 客户凭据 Falcon.SugarApi.ClaimTicket.ClaimTicket
|
||||
|
||||
### 1、添加ClaimTicket支持。
|
||||
是否使用ClaimTicket由插件自行决定。
|
||||
如果插件需要使用ClaimTicket,按如下方式在插件服务配置中添加。
|
||||
~~~C#
|
||||
public class ServicePlugin:IServicePlugin
|
||||
{
|
||||
IServiceCollection IServicePlugin.AddServices(IServiceCollection services,IConfiguration configuration) {
|
||||
//注入使用ClaimTicket的控制器
|
||||
services.AddPluginsController(this.GetType().Assembly);
|
||||
//添加ClaimTicket服务支持
|
||||
services.AddClaimTicket();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
~~~
|
||||
|
||||
### 2、控制器注入
|
||||
要完成用户的登录,认证和绑定工作需要注入ITicketBuilder接口,这个接口在上面的services.AddClaimTicket()中已经完成注入。
|
||||
下面是在HomeController控制器中注入ITicketBuilder的示例。
|
||||
~~~C#
|
||||
|
||||
public ITicketBuilder TicketBuilder { get; set; }
|
||||
|
||||
public HomeController(IServiceProvider service) : base(service) {
|
||||
this.TicketBuilder = service.GetRequiredService<ITicketBuilder>();
|
||||
}
|
||||
~~~
|
||||
### 3、用户登录。
|
||||
插件需要自行完成用户认证工作,示例代码如下。
|
||||
~~~c#
|
||||
[HttpGet]
|
||||
public string Login(string username) {
|
||||
//验证用户有效性,自行调用数据库进行验证。
|
||||
|
||||
//如果通过验证,生成用户票据
|
||||
var user = new UserTicket(
|
||||
//添加用户名
|
||||
new Claim("name",username),
|
||||
//添加其他必要的声明
|
||||
new Claim("role","admin")
|
||||
);
|
||||
var ticket = this.TicketBuilder.GetTicket(user);
|
||||
return ticket ?? "";
|
||||
}
|
||||
|
||||
~~~
|
||||
客户端在收到返回的票据后自行保管票据。
|
||||
|
||||
### 4、验证用户
|
||||
实例代码如下:
|
||||
~~~c#
|
||||
[HttpGet]
|
||||
public string ViewUser(string ticket) {
|
||||
//通过票据获取用户
|
||||
var cs = this.TicketBuilder.GetUser(ticket);
|
||||
if(cs == null) {
|
||||
return "TicketBuilder.GetUser 返回空";
|
||||
}
|
||||
StringBuilder sb = new();
|
||||
//轮训票据生成获取登录信息
|
||||
foreach(var i in cs.Claims) {
|
||||
sb.AppendLine($"type:{i.Type},Val:{i.Value}");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
~~~
|
||||
### 5、绑定用户
|
||||
示例代码如下:
|
||||
~~~c#
|
||||
[HttpGet]
|
||||
public string Viewbind(string _authUserTicket,[FromHeader] UserTicket user) {
|
||||
StringBuilder sb = new();
|
||||
foreach(var i in user.Claims) {
|
||||
sb.AppendLine($"type:{i.Type},Val:{i.Value}");
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
~~~
|
||||
一般客户端通过HTTP头参数_authUserTicket提交用户凭据,这个例子中为了方便通过get参数提交票据,控制器可以自动将其绑定在UserTicket对象中。
|
||||
因为action还通过httpbody获取了一个_authUserTicket参数,所以UserTicket需要加[FromHeader]特性。
|
||||
|
||||
### 6、自定义ClaimTicket配置
|
||||
通过ClaimTicketOptions对象完成配置。完成配置的途径有两个。
|
||||
> 1、在插件配置中优先注入自己的ClaimTicketOptions对象。
|
||||
> ~~~c#
|
||||
> public class ServicePlugin:IServicePlugin
|
||||
> {
|
||||
> IServiceCollection IServicePlugin.AddServices(IServiceCollection services,IConfiguration configuration) {
|
||||
> //注入使用ClaimTicket的控制器
|
||||
> services.AddPluginsController(this.GetType().Assembly);
|
||||
>
|
||||
> //优先添加自己的服务配置
|
||||
> services.AddSingleton(new ClaimTicketOptions());
|
||||
>
|
||||
> //添加ClaimTicket服务支持
|
||||
> services.AddClaimTicket();
|
||||
> return services;
|
||||
> }
|
||||
> }
|
||||
> ~~~
|
||||
> 2、在插件配置中优先注入自己的ClaimTicketOptions对象。
|
||||
> ~~~c#
|
||||
> public class ServicePlugin:IServicePlugin
|
||||
> {
|
||||
> IServiceCollection IServicePlugin.AddServices(IServiceCollection services,IConfiguration configuration) {
|
||||
> //注入使用ClaimTicket的控制器
|
||||
> services.AddPluginsController(this.GetType().Assembly);
|
||||
>
|
||||
> //优先添加自己的服务配置
|
||||
> //services.AddSingleton(new ClaimTicketOptions());
|
||||
>
|
||||
> //添加ClaimTicket服务支持
|
||||
> services.AddClaimTicket(optionsBuilder => {
|
||||
> //客户端通过_headCalimTick属性提交凭据
|
||||
> optionsBuilder.HttpHeaderKey = "_headCalimTick";
|
||||
> });
|
||||
> return services;
|
||||
> }
|
||||
> }
|
||||
> ~~~
|
||||
### 7、扩展ITicketBuilder
|
||||
插件可以自行扩展票据的生成方式,方法有两个,一个是继承TicketBuilder对象并重写方法,另一个就是自己实现ITicketBuilder接口。
|
||||
|
||||
~~~c#
|
||||
/// <summary>
|
||||
/// 自定义票据生成方式
|
||||
/// </summary>
|
||||
public class MyTicketBuilder:TicketBuilder, ITicketBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// 使用票据参数构造生成器
|
||||
/// </summary>
|
||||
/// <param name="options">生成参数</param>
|
||||
public MyTicketBuilder(ClaimTicketOptions options) : base(options) { }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override string? GetTicket(UserTicket userTicket) {
|
||||
//实现自己的票据生成方法
|
||||
|
||||
//或使用基类提供的方法
|
||||
return base.GetTicket(userTicket);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override UserTicket? GetUser(string ticket) {
|
||||
//根据票据获取用户声明返回用户信息
|
||||
|
||||
//或使用基类提供的方法
|
||||
return base.GetUser(ticket);
|
||||
}
|
||||
}
|
||||
~~~
|
||||
然后在调用AddClaimTicket之前注入自己的ITicketBuilder
|
||||
~~~c#
|
||||
public class ServicePlugin:IServicePlugin
|
||||
{
|
||||
IServiceCollection IServicePlugin.AddServices(IServiceCollection services,IConfiguration configuration) {
|
||||
//注入使用ClaimTicket的控制器
|
||||
services.AddPluginsController(this.GetType().Assembly);
|
||||
|
||||
//优先添加自己的服务配置
|
||||
//services.AddSingleton(new ClaimTicketOptions());
|
||||
|
||||
//注入扩展MyTicketBuilder
|
||||
services.TryAddSingleton<ITicketBuilder,MyTicketBuilder>();
|
||||
|
||||
//添加ClaimTicket服务支持
|
||||
services.AddClaimTicket(optionsBuilder => {
|
||||
//客户端通过_headCalimTick属性提交凭据
|
||||
optionsBuilder.HttpHeaderKey = "_headCalimTick";
|
||||
});
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
~~~
|
43
Falcon.SugarApi/ClaimTicket/ServiceCollectionExtend.cs
Normal file
43
Falcon.SugarApi/ClaimTicket/ServiceCollectionExtend.cs
Normal file
|
@ -0,0 +1,43 @@
|
|||
using Falcon.SugarApi.Encryption;
|
||||
using Falcon.SugarApi.JsonSerialize;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.DependencyInjection.Extensions;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Falcon.SugarApi.ClaimTicket
|
||||
{
|
||||
/// <summary>
|
||||
/// 服务扩展
|
||||
/// </summary>
|
||||
public static class ServiceCollectionExtend
|
||||
{
|
||||
/// <summary>
|
||||
/// 增加客户凭据支持
|
||||
/// </summary>
|
||||
/// <param name="services">服务集合</param>
|
||||
/// <param name="OptionBuilder">配置创建</param>
|
||||
/// <returns>服务集合</returns>
|
||||
public static IServiceCollection AddClaimTicket(this IServiceCollection services,Action<ClaimTicketOptions>? OptionBuilder = null) {
|
||||
services.TryAddSingleton<ClaimTicketOptions>(p => {
|
||||
var en = p.GetRequiredService<IEncryption>();
|
||||
var ser = p.GetRequiredService<IJsonSerialize>();
|
||||
var option = new ClaimTicketOptions {
|
||||
Encryption = en,JsonSerialize = ser,
|
||||
};
|
||||
OptionBuilder?.Invoke(option);
|
||||
return option;
|
||||
});
|
||||
services.TryAddSingleton<IEncryption,AESProvider>();
|
||||
services.TryAddSingleton<IJsonSerialize,JsonSerialize.JsonSerialize>();
|
||||
services.TryAddSingleton<ITicketBuilder,TicketBuilder>();
|
||||
services.AddControllers(op => {
|
||||
op.ModelBinderProviders.Insert(0,new ClaimTicketModelBindingProvider());
|
||||
});
|
||||
return services;
|
||||
}
|
||||
}
|
||||
}
|
63
Falcon.SugarApi/ClaimTicket/TicketBuilder.cs
Normal file
63
Falcon.SugarApi/ClaimTicket/TicketBuilder.cs
Normal file
|
@ -0,0 +1,63 @@
|
|||
using Falcon.SugarApi.Encryption;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Falcon.SugarApi.ClaimTicket
|
||||
{
|
||||
/// <summary>
|
||||
/// 票据生成器
|
||||
/// </summary>
|
||||
public class TicketBuilder:ITicketBuilder
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造票据生成器
|
||||
/// </summary>
|
||||
/// <param name="options">加密配置</param>
|
||||
public TicketBuilder(ClaimTicketOptions options) {
|
||||
this.Encryption = options.Encryption;
|
||||
this.Serialize = options.JsonSerialize;
|
||||
this.Options = options;
|
||||
}
|
||||
/// <summary>
|
||||
/// 加密模块
|
||||
/// </summary>
|
||||
public IEncryption Encryption { get; }
|
||||
/// <summary>
|
||||
/// 序列化模块
|
||||
/// </summary>
|
||||
public ISerialize Serialize { get; }
|
||||
/// <summary>
|
||||
/// 票据生成配置
|
||||
/// </summary>
|
||||
public ClaimTicketOptions Options { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual UserTicket? GetUser(string ticket) {
|
||||
var str = this.Encryption.Decrypt(this.Options.EncryptionKey,ticket);
|
||||
if(str.IsNullOrEmpty()) {
|
||||
return new UserTicket();
|
||||
}
|
||||
var list = this.Serialize.Deserialize<List<ClaimKeyValue>>(str);
|
||||
if(list == null) {
|
||||
return new UserTicket();
|
||||
}
|
||||
return new UserTicket(list.Select(a => new Claim(a.Key,a.Value)));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public virtual string? GetTicket(UserTicket userTicket) {
|
||||
if(userTicket == null) {
|
||||
return string.Empty;
|
||||
}
|
||||
var claims = userTicket.Claims;
|
||||
if(claims == null || claims.Count == 0) {
|
||||
return string.Empty;
|
||||
}
|
||||
var list = claims.Select(a => new ClaimKeyValue { Key = a.Type,Value = a.Value });
|
||||
var str = this.Serialize.Serialize(list);
|
||||
var sstr = this.Encryption.Encrypt(this.Options.EncryptionKey,str);
|
||||
return sstr;
|
||||
}
|
||||
}
|
||||
}
|
77
Falcon.SugarApi/ClaimTicket/UserTicket.cs
Normal file
77
Falcon.SugarApi/ClaimTicket/UserTicket.cs
Normal file
|
@ -0,0 +1,77 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace Falcon.SugarApi.ClaimTicket
|
||||
{
|
||||
/// <summary>
|
||||
/// 用户票据
|
||||
/// </summary>
|
||||
public class UserTicket
|
||||
{
|
||||
/// <summary>
|
||||
/// 存储用户声明
|
||||
/// </summary>
|
||||
public List<Claim> Claims { get; set; } = new List<Claim>();
|
||||
/// <summary>
|
||||
/// 构造空白票据
|
||||
/// </summary>
|
||||
public UserTicket() { }
|
||||
/// <summary>
|
||||
/// 通过声明列表构造票据
|
||||
/// </summary>
|
||||
/// <param name="claims">声称</param>
|
||||
public UserTicket(IEnumerable<Claim> claims) {
|
||||
this.Claims = claims.ToList();
|
||||
}
|
||||
/// <summary>
|
||||
/// 通过声明列表构造票据
|
||||
/// </summary>
|
||||
/// <param name="claims">声称</param>
|
||||
public UserTicket(params Claim[] claims) : this(claims.ToList()) { }
|
||||
|
||||
/// <summary>
|
||||
/// 增加用户声明
|
||||
/// </summary>
|
||||
/// <param name="claims"></param>
|
||||
public void AddClaims(params Claim[] claims) {
|
||||
if(claims.Length == 0) {
|
||||
return;
|
||||
}
|
||||
foreach(var c in claims) {
|
||||
var f = this.Claims.Find(a => a.Type == c.Type);
|
||||
if(f != null) {
|
||||
this.Claims.Remove(f);
|
||||
}
|
||||
this.Claims.Add(c);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为用户添加键和值
|
||||
/// </summary>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="val">值</param>
|
||||
public void AddClaim(string key,string val) {
|
||||
if(key.IsNullOrEmpty()) {
|
||||
return;
|
||||
}
|
||||
if(val.IsNullOrEmpty()) {
|
||||
var c = this.Claims.Find(a => a.Type == key);
|
||||
if(c != null) {
|
||||
this.Claims.Remove(c);
|
||||
}
|
||||
}
|
||||
this.AddClaims(new Claim(key,val));
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取用户声明的值
|
||||
/// </summary>
|
||||
/// <param name="key">声明键</param>
|
||||
/// <returns>值</returns>
|
||||
public string? GetValue(string key) {
|
||||
var c = this.Claims.Find(a => a.Type == key);
|
||||
return c == null ? string.Empty : c.Value;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user