diff --git a/Falcon.SugarApi/ClaimTicket/ClaimKeyValue.cs b/Falcon.SugarApi/ClaimTicket/ClaimKeyValue.cs
new file mode 100644
index 0000000..d147aa6
--- /dev/null
+++ b/Falcon.SugarApi/ClaimTicket/ClaimKeyValue.cs
@@ -0,0 +1,17 @@
+namespace Falcon.SugarApi.ClaimTicket
+{
+ ///
+ /// 存储claim的结构
+ ///
+ public class ClaimKeyValue
+ {
+ ///
+ /// 存储Type
+ ///
+ public string Key { get; set; }
+ ///
+ /// 存储值
+ ///
+ public string Value { get; set; }
+ }
+}
diff --git a/Falcon.SugarApi/ClaimTicket/ClaimTicketModelBinding.cs b/Falcon.SugarApi/ClaimTicket/ClaimTicketModelBinding.cs
new file mode 100644
index 0000000..127c7bd
--- /dev/null
+++ b/Falcon.SugarApi/ClaimTicket/ClaimTicketModelBinding.cs
@@ -0,0 +1,54 @@
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using System.Threading.Tasks;
+
+namespace Falcon.SugarApi.ClaimTicket
+{
+ ///
+ /// 模型绑定
+ ///
+ public class ClaimTicketModelBinding:IModelBinder
+ {
+ ///
+ /// 票据创建器
+ ///
+ public ITicketBuilder Builter { get; set; }
+ ///
+ /// 配置
+ ///
+ public ClaimTicketOptions Options { get; set; }
+
+ ///
+ /// 通过票据创建器创建模型绑定
+ ///
+ ///
+ ///
+ 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;
+ }
+ }
+}
diff --git a/Falcon.SugarApi/ClaimTicket/ClaimTicketModelBindingProvider.cs b/Falcon.SugarApi/ClaimTicket/ClaimTicketModelBindingProvider.cs
new file mode 100644
index 0000000..6c355a0
--- /dev/null
+++ b/Falcon.SugarApi/ClaimTicket/ClaimTicketModelBindingProvider.cs
@@ -0,0 +1,24 @@
+using Microsoft.AspNetCore.Mvc.ModelBinding;
+using Microsoft.AspNetCore.Mvc.ModelBinding.Binders;
+using System;
+
+namespace Falcon.SugarApi.ClaimTicket
+{
+ ///
+ /// ClaimTicket模型绑定提供器
+ ///
+ 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;
+ }
+ }
+}
diff --git a/Falcon.SugarApi/ClaimTicket/ClaimTicketOptions.cs b/Falcon.SugarApi/ClaimTicket/ClaimTicketOptions.cs
new file mode 100644
index 0000000..38ae730
--- /dev/null
+++ b/Falcon.SugarApi/ClaimTicket/ClaimTicketOptions.cs
@@ -0,0 +1,27 @@
+using Falcon.SugarApi.Encryption;
+
+namespace Falcon.SugarApi.ClaimTicket
+{
+ ///
+ /// 票据系统配置
+ ///
+ public class ClaimTicketOptions
+ {
+ ///
+ /// 加密键
+ ///
+ public string EncryptionKey { get; set; } = "E9319792CB7249AD8E432000E9F2FE7A";
+ ///
+ /// http头票据key
+ ///
+ public string HttpHeaderKey { get; set; } = "_authUserTicket";
+ ///
+ /// 安全加密组件
+ ///
+ public IEncryption Encryption { get; set; }
+ ///
+ /// 序列化组件
+ ///
+ public ISerialize JsonSerialize { get; set; }
+ }
+}
diff --git a/Falcon.SugarApi/ClaimTicket/ITicketBuilder.cs b/Falcon.SugarApi/ClaimTicket/ITicketBuilder.cs
new file mode 100644
index 0000000..a6726ea
--- /dev/null
+++ b/Falcon.SugarApi/ClaimTicket/ITicketBuilder.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.Security.Claims;
+
+namespace Falcon.SugarApi.ClaimTicket
+{
+ ///
+ /// 票据生成接口
+ ///
+ public interface ITicketBuilder
+ {
+ ///
+ /// 根据用户生成票据
+ ///
+ /// 用户票据
+ /// ticket
+ string? GetTicket(UserTicket userTicket);
+ ///
+ /// 通过ticket获取用户
+ ///
+ /// token
+ /// 声明组
+ UserTicket? GetUser(string ticket);
+
+ }
+}
diff --git a/Falcon.SugarApi/ClaimTicket/Readme.md b/Falcon.SugarApi/ClaimTicket/Readme.md
new file mode 100644
index 0000000..586dfc7
--- /dev/null
+++ b/Falcon.SugarApi/ClaimTicket/Readme.md
@@ -0,0 +1,180 @@
+## ͻƾ Falcon.SugarApi.ClaimTicket.ClaimTicket
+
+### 1ClaimTicket֧֡
+ Ƿʹ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();
+}
+~~~
+### 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ȡһ_authUserTicketUserTicketҪ[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#
+///
+/// ԶƱɷʽ
+///
+public class MyTicketBuilder:TicketBuilder, ITicketBuilder
+{
+ ///
+ /// ʹƱݲ
+ ///
+ /// ɲ
+ public MyTicketBuilder(ClaimTicketOptions options) : base(options) { }
+
+ ///
+ public override string? GetTicket(UserTicket userTicket) {
+ //ʵԼƱɷ
+
+ //ʹûṩķ
+ return base.GetTicket(userTicket);
+ }
+
+ ///
+ 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();
+
+ //ClaimTicket֧
+ services.AddClaimTicket(optionsBuilder => {
+ //ͻͨ_headCalimTickύƾ
+ optionsBuilder.HttpHeaderKey = "_headCalimTick";
+ });
+ return services;
+ }
+}
+
+~~~
diff --git a/Falcon.SugarApi/ClaimTicket/ServiceCollectionExtend.cs b/Falcon.SugarApi/ClaimTicket/ServiceCollectionExtend.cs
new file mode 100644
index 0000000..c6e4be9
--- /dev/null
+++ b/Falcon.SugarApi/ClaimTicket/ServiceCollectionExtend.cs
@@ -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
+{
+ ///
+ /// 服务扩展
+ ///
+ public static class ServiceCollectionExtend
+ {
+ ///
+ /// 增加客户凭据支持
+ ///
+ /// 服务集合
+ /// 配置创建
+ /// 服务集合
+ public static IServiceCollection AddClaimTicket(this IServiceCollection services,Action? OptionBuilder = null) {
+ services.TryAddSingleton(p => {
+ var en = p.GetRequiredService();
+ var ser = p.GetRequiredService();
+ var option = new ClaimTicketOptions {
+ Encryption = en,JsonSerialize = ser,
+ };
+ OptionBuilder?.Invoke(option);
+ return option;
+ });
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.TryAddSingleton();
+ services.AddControllers(op => {
+ op.ModelBinderProviders.Insert(0,new ClaimTicketModelBindingProvider());
+ });
+ return services;
+ }
+ }
+}
diff --git a/Falcon.SugarApi/ClaimTicket/TicketBuilder.cs b/Falcon.SugarApi/ClaimTicket/TicketBuilder.cs
new file mode 100644
index 0000000..72a52e8
--- /dev/null
+++ b/Falcon.SugarApi/ClaimTicket/TicketBuilder.cs
@@ -0,0 +1,63 @@
+using Falcon.SugarApi.Encryption;
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+
+namespace Falcon.SugarApi.ClaimTicket
+{
+ ///
+ /// 票据生成器
+ ///
+ public class TicketBuilder:ITicketBuilder
+ {
+ ///
+ /// 构造票据生成器
+ ///
+ /// 加密配置
+ public TicketBuilder(ClaimTicketOptions options) {
+ this.Encryption = options.Encryption;
+ this.Serialize = options.JsonSerialize;
+ this.Options = options;
+ }
+ ///
+ /// 加密模块
+ ///
+ public IEncryption Encryption { get; }
+ ///
+ /// 序列化模块
+ ///
+ public ISerialize Serialize { get; }
+ ///
+ /// 票据生成配置
+ ///
+ public ClaimTicketOptions Options { get; }
+
+ ///
+ 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>(str);
+ if(list == null) {
+ return new UserTicket();
+ }
+ return new UserTicket(list.Select(a => new Claim(a.Key,a.Value)));
+ }
+
+ ///
+ 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;
+ }
+ }
+}
diff --git a/Falcon.SugarApi/ClaimTicket/UserTicket.cs b/Falcon.SugarApi/ClaimTicket/UserTicket.cs
new file mode 100644
index 0000000..5ae17e9
--- /dev/null
+++ b/Falcon.SugarApi/ClaimTicket/UserTicket.cs
@@ -0,0 +1,77 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Security.Claims;
+
+namespace Falcon.SugarApi.ClaimTicket
+{
+ ///
+ /// 用户票据
+ ///
+ public class UserTicket
+ {
+ ///
+ /// 存储用户声明
+ ///
+ public List Claims { get; set; } = new List();
+ ///
+ /// 构造空白票据
+ ///
+ public UserTicket() { }
+ ///
+ /// 通过声明列表构造票据
+ ///
+ /// 声称
+ public UserTicket(IEnumerable claims) {
+ this.Claims = claims.ToList();
+ }
+ ///
+ /// 通过声明列表构造票据
+ ///
+ /// 声称
+ public UserTicket(params Claim[] claims) : this(claims.ToList()) { }
+
+ ///
+ /// 增加用户声明
+ ///
+ ///
+ 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);
+ }
+ }
+
+ ///
+ /// 为用户添加键和值
+ ///
+ /// 键
+ /// 值
+ 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));
+ }
+ ///
+ /// 获取用户声明的值
+ ///
+ /// 声明键
+ /// 值
+ public string? GetValue(string key) {
+ var c = this.Claims.Find(a => a.Type == key);
+ return c == null ? string.Empty : c.Value;
+ }
+ }
+}