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; + } + } +}