commit
fd676201ec
34 changed files with 1384 additions and 0 deletions
@ -0,0 +1,3 @@ |
|||
.vs |
|||
bin |
|||
obj |
@ -0,0 +1,49 @@ |
|||
using Flurl; |
|||
using Flurl.Http; |
|||
using HuiXin.Gateway.Ocelot.Configurations; |
|||
using Microsoft.Extensions.Options; |
|||
using Ocelot.Errors; |
|||
using Ocelot.Infrastructure.Claims.Parser; |
|||
using Ocelot.Responses; |
|||
using Serilog; |
|||
|
|||
namespace HuiXin.Gateway.Ocelot.Authorizers |
|||
{ |
|||
public class HttpRolesAuthorizer : RolesAuthorizerBase, IRolesAuthorizer |
|||
{ |
|||
private readonly string _url; |
|||
private readonly FlurlClient _client; |
|||
|
|||
public HttpRolesAuthorizer(IClaimsParser claimsParser, IOptions<RolesAuthorizerConfiguration> configuration) : base(claimsParser, configuration) |
|||
{ |
|||
_url = _configs.Url ?? throw new Exception("未配置角色验证的Url地址"); |
|||
_client = new FlurlClient(_url); |
|||
_client.Settings.Timeout = TimeSpan.FromMilliseconds(_configs.Timeout); |
|||
_client.Settings.Redirects.Enabled = false; |
|||
} |
|||
|
|||
public async Task<Response<bool>> Authorize(List<string> roles, string path) |
|||
{ |
|||
try |
|||
{ |
|||
bool pass = await _client.Request().AppendQueryParam("roles", roles).AppendQueryParam("path", path).GetJsonAsync<bool>(); |
|||
if (pass) |
|||
{ |
|||
return await ReturnAsync(new OkResponse<bool>(true)); |
|||
} |
|||
else |
|||
{ |
|||
return await ReturnAsync(new ErrorResponse<bool>(new HttpRolesAuthorizerFail("用户没有访问权限"))); |
|||
} |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Log.Error(ex.Message, "验证用户角色权限出错"); |
|||
return await ReturnAsync(new ErrorResponse<bool>(new HttpRolesAuthorizerError("验证用户角色权限出错"))); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public class HttpRolesAuthorizerError(string message) : Error(message, OcelotErrorCode.UnableToCompleteRequestError, 500){} |
|||
public class HttpRolesAuthorizerFail(string message) : Error(message, OcelotErrorCode.UnauthorizedError, 403){} |
|||
} |
@ -0,0 +1,9 @@ |
|||
using Ocelot.Responses; |
|||
|
|||
namespace HuiXin.Gateway.Ocelot.Authorizers |
|||
{ |
|||
public interface IRolesAuthorizer |
|||
{ |
|||
Task<Response<bool>> Authorize(List<string> roles, string path); |
|||
} |
|||
} |
@ -0,0 +1,24 @@ |
|||
using HuiXin.Gateway.Ocelot.Configurations; |
|||
using Microsoft.Extensions.Options; |
|||
using Ocelot.Infrastructure.Claims.Parser; |
|||
using Ocelot.Responses; |
|||
|
|||
namespace HuiXin.Gateway.Ocelot.Authorizers |
|||
{ |
|||
public class RolesAuthorizerBase |
|||
{ |
|||
protected readonly IClaimsParser _claimsParser; |
|||
protected RolesAuthorizerConfiguration _configs; |
|||
|
|||
public RolesAuthorizerBase(IClaimsParser claimsParser, IOptions<RolesAuthorizerConfiguration> cfg) |
|||
{ |
|||
_claimsParser = claimsParser; |
|||
_configs = cfg.Value ?? throw new Exception("未配置角色验证参数"); |
|||
} |
|||
|
|||
protected async Task<Response<T>> ReturnAsync<T>(Response<T> response) |
|||
{ |
|||
return await Task.FromResult(response); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,6 @@ |
|||
{ |
|||
"AuthenticationScheme": "MyKey", |
|||
"Authority": "http://localhost:5001", |
|||
"Audience": "huixin", |
|||
"IssuerSigningKeyBase64": "GcTdqSZdpRxBtdtgwvDHBzS427VGTQzbM+JD1CBbUZY=" |
|||
} |
@ -0,0 +1,54 @@ |
|||
{ |
|||
"Routes": [ |
|||
{ |
|||
"UpstreamHttpMethod": [ "Get" ], |
|||
"UpstreamPathTemplate": "/user/{postId}", |
|||
"DownstreamPathTemplate": "/search?q=/{postId}", |
|||
"DownstreamScheme": "https", |
|||
"DownstreamHostAndPorts": [ |
|||
{ |
|||
"Host": "localhost", |
|||
"Port": 443 |
|||
} |
|||
], |
|||
"QoSOptions": { |
|||
"TimeoutValue": 5000 |
|||
}, |
|||
//"ServiceName": "auth", |
|||
"LoadBalancerOptions": { |
|||
"Type": "LeastConnection" |
|||
}, |
|||
"AuthenticationOptions": { |
|||
"AuthenticationProviderKey": "MyKey", |
|||
"AllowedScopes": [ "all" ] |
|||
} |
|||
}, |
|||
{ |
|||
"UpstreamHttpMethod": [ "Get" ], |
|||
"UpstreamPathTemplate": "/{url}", |
|||
"DownstreamPathTemplate": "/search?q={url}", |
|||
"DownstreamScheme": "https", |
|||
"DownstreamHostAndPorts": [ |
|||
{ |
|||
"Host": "cn.bing.com", |
|||
"Port": 443 |
|||
} |
|||
] |
|||
} |
|||
], |
|||
"GlobalConfiguration": { |
|||
"BaseUrl": "https://api.mybusiness.com", |
|||
"RequestIdKey": "TraceId" |
|||
//"ServiceDiscoveryProvider": { |
|||
// "Scheme": "https", |
|||
// "Host": "localhost", |
|||
// "Port": 8500, |
|||
// "Type": "Consul" |
|||
//} |
|||
}, |
|||
"RolesAuthorizer": { |
|||
"Type": "http", |
|||
"Url": "http://auth.huixin.com/api/authorizer", |
|||
"Timeout": 2000 |
|||
} |
|||
} |
@ -0,0 +1,15 @@ |
|||
{ |
|||
"Serilog": { |
|||
"WriteTo": [ |
|||
{ |
|||
"Name": "File", |
|||
"Args": { |
|||
"path": "./Logs/gateway.txt", |
|||
"rollingInterval": "Day", |
|||
"fileSizeLimitBytes": 20971520 |
|||
} |
|||
}, |
|||
{ "Name": "Console" } |
|||
] |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
namespace HuiXin.Gateway.Ocelot.Configurations |
|||
{ |
|||
public class RolesAuthorizerConfiguration |
|||
{ |
|||
public string Type { get; set; } |
|||
public string Url { get; set; } |
|||
public int Timeout { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,46 @@ |
|||
using Microsoft.AspNetCore.Authentication.JwtBearer; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
|
|||
namespace HuiXin.Gateway.Ocelot.Extensions |
|||
{ |
|||
public static class JWTExtensions |
|||
{ |
|||
public static IServiceCollection AddJWT(this IServiceCollection services, IConfiguration configuration) |
|||
{ |
|||
services.AddAuthentication(options => |
|||
{ |
|||
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; |
|||
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; |
|||
}).AddJwtBearer(configuration.GetValue<string>("AuthenticationScheme") ?? throw new Exception("jwt的参数AuthenticationScheme未配置,请在jwt.json文件中配置"), options => |
|||
{ |
|||
//options.Authority = cfgJwt.GetValue<string>("Authority"); // OpenIddict服务端地址
|
|||
//options.BackchannelTimeout = TimeSpan.FromMilliseconds(300);
|
|||
options.RequireHttpsMetadata = false; |
|||
options.Audience = configuration.GetValue<string>("Audience"); // 与OpenIddict中定义的Audience匹配
|
|||
options.TokenValidationParameters = new TokenValidationParameters |
|||
{ |
|||
ValidateIssuerSigningKey = false, |
|||
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(configuration.GetValue<string>("IssuerSigningKeyBase64") ?? throw new Exception("jwt的参数IssuerSigningKeyBase64未配置,请在jwt.json文件中配置"))), |
|||
ValidateIssuer = false, |
|||
//ValidIssuer = "YOUR_ISSUER",
|
|||
ValidateAudience = false, |
|||
//ValidAudience = "YOUR_AUDIENCE",
|
|||
ValidateLifetime = true, |
|||
// 忽略 kid 参数
|
|||
ValidateTokenReplay = false, |
|||
}; |
|||
}); |
|||
services.AddAuthorization(); |
|||
|
|||
return services; |
|||
} |
|||
|
|||
public static IApplicationBuilder UseJWT(this WebApplication app) |
|||
{ |
|||
app.UseAuthentication(); |
|||
app.UseAuthorization(); |
|||
|
|||
return app; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,64 @@ |
|||
using HuiXin.Gateway.Ocelot.Authorizers; |
|||
using HuiXin.Gateway.Ocelot.Configurations; |
|||
using HuiXin.Gateway.Ocelot.Middlewares; |
|||
using Ocelot.Authorization; |
|||
using Ocelot.DependencyInjection; |
|||
using Ocelot.Infrastructure.Claims.Parser; |
|||
using Ocelot.Middleware; |
|||
using Ocelot.Provider.Consul; |
|||
using System.Security.Claims; |
|||
|
|||
namespace HuiXin.Gateway.Ocelot.Extensions |
|||
{ |
|||
public static class OcelotExtensions |
|||
{ |
|||
public static IServiceCollection AddMyOcelot(this IServiceCollection services, IConfiguration configuration) |
|||
{ |
|||
services.AddOcelot().AddConsul(); |
|||
|
|||
services.AddSingleton<IRolesAuthorizer, HttpRolesAuthorizer>(); |
|||
services.Configure<RolesAuthorizerConfiguration>(configuration.GetSection("RolesAuthorizer")); |
|||
|
|||
return services; |
|||
} |
|||
|
|||
public static IApplicationBuilder UseMyOcelot(this WebApplication app) |
|||
{ |
|||
app.UseMiddleware<AccessLoggingMiddleware>(); |
|||
|
|||
app.UseOcelot(new OcelotPipelineConfiguration |
|||
{ |
|||
PreQueryStringBuilderMiddleware = async (context, next) => |
|||
{ |
|||
var claimsParser = app.Services.GetRequiredService<IClaimsParser>(); |
|||
var values = claimsParser.GetValuesByClaimType(context.User.Claims, ClaimsIdentity.DefaultRoleClaimType); |
|||
if (values.IsError) |
|||
{ |
|||
context.Items.UpsertErrors(values.Errors); |
|||
return; |
|||
} |
|||
if (values.Data == null || values.Data.Count == 0) |
|||
{ |
|||
context.Items.SetError(new UserDoesNotHaveClaimError("token中未包含角色信息")); |
|||
return; |
|||
} |
|||
var downstreamRoute = context.Items.DownstreamRoute(); |
|||
var url = downstreamRoute.DownstreamPathTemplate.Value; |
|||
context.Items.TemplatePlaceholderNameAndValues().ForEach(nv => |
|||
{ |
|||
url = url.Replace(nv.Name, nv.Value); |
|||
}); |
|||
var rolesAuthorizer = app.Services.GetRequiredService<IRolesAuthorizer>(); |
|||
var result = await rolesAuthorizer.Authorize(values.Data, url); |
|||
if (result.IsError) |
|||
{ |
|||
context.Items.UpsertErrors(result.Errors); |
|||
return; |
|||
} |
|||
await next.Invoke(); |
|||
} |
|||
}).Wait(); |
|||
return app; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,23 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net8.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Flurl.Http" Version="4.0.2" /> |
|||
<PackageReference Include="JWT" Version="10.1.1" /> |
|||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.3" /> |
|||
<PackageReference Include="Ocelot" Version="23.1.0" /> |
|||
<PackageReference Include="Ocelot.Provider.Consul" Version="23.1.0" /> |
|||
<PackageReference Include="Ocelot.Provider.Polly" Version="23.1.0" /> |
|||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" /> |
|||
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.0" /> |
|||
<PackageReference Include="Serilog.Sinks.Console" Version="5.0.1" /> |
|||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" /> |
|||
<PackageReference Include="Yitter.IdGenerator" Version="1.0.14" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,64 @@ |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using System.IdentityModel.Tokens.Jwt; |
|||
using System.Security.Claims; |
|||
|
|||
namespace HuiXin.Gateway.Ocelot |
|||
{ |
|||
public class JWTUtil |
|||
{ |
|||
|
|||
/// <summary>
|
|||
/// 创建token
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
public static string CreateJwtToken(IDictionary<string, object> payload, string secret, IDictionary<string, string> extraHeaders = null) |
|||
{ |
|||
//IJwtEncoder encoder = new JwtEncoder(new HMACSHA256Algorithm(), new JsonNetSerializer(), new JwtBase64UrlEncoder());
|
|||
//var token = encoder.Encode(extraHeaders,payload, secret);
|
|||
//return token;
|
|||
|
|||
Claim[] claims = new Claim[] |
|||
{ |
|||
new Claim("Id", "aaa"), |
|||
new Claim("Name", "bbb"), |
|||
new Claim("Email", "ccc"), |
|||
}; |
|||
|
|||
var securityKey = new SymmetricSecurityKey(Convert.FromBase64String("GcTdqSZdpRxBtdtgwvDHBzS427VGTQzbM+JD1CBbUZY=")); |
|||
|
|||
var signingCredentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); |
|||
|
|||
|
|||
var header = new JwtHeader(signingCredentials); |
|||
//header.Add("typ", "JWT"); // 默认情况下,typ通常是"JWT",但你可以明确设置它
|
|||
//header.Add("kid", "MyKey");
|
|||
//header.Add("key", "MyKey");
|
|||
//header.Add("keyid", "MyKey");
|
|||
|
|||
// 设置JWT载荷(payload)
|
|||
var payload1 = new JwtPayload |
|||
{ |
|||
//{ "kid", "MyKey" },
|
|||
//{ "key", "MyKey" },
|
|||
//{ "keyid", "MyKey" },
|
|||
{ "issuer",""}, |
|||
{ "sub", "1234567890" }, |
|||
{ "name", "测试用户" }, |
|||
{ "role", new []{"test","admin" } }, |
|||
{ "scope",new []{"all" } }, |
|||
{ "iat", ToUnixTime(DateTime.Now)}, |
|||
{ "exp", ToUnixTime(DateTime.Now.AddDays(1)) } |
|||
}; |
|||
|
|||
// 创建JwtSecurityToken实例并结合header和payload
|
|||
var jwt = new JwtSecurityToken(header, payload1); |
|||
return new JwtSecurityTokenHandler().WriteToken(jwt); |
|||
} |
|||
|
|||
private static long ToUnixTime(DateTime dt) |
|||
{ |
|||
DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); |
|||
return (long)(dt.ToUniversalTime() - epoch).TotalMilliseconds; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,132 @@ |
|||
using Microsoft.AspNetCore.Http.Extensions; |
|||
using Ocelot.RequestId; |
|||
using Serilog; |
|||
using System.Diagnostics; |
|||
using System.Text; |
|||
using Yitter.IdGenerator; |
|||
|
|||
namespace HuiXin.Gateway.Ocelot.Middlewares |
|||
{ |
|||
public class AccessLoggingMiddleware |
|||
{ |
|||
private readonly RequestDelegate _next; |
|||
|
|||
public AccessLoggingMiddleware(RequestDelegate next) |
|||
{ |
|||
_next = next; |
|||
} |
|||
|
|||
public async Task InvokeAsync(HttpContext context) |
|||
{ |
|||
var traceIdKey = DefaultRequestIdKey.Value; |
|||
var traceId = YitIdHelper.NextId().ToString(); |
|||
context.TraceIdentifier = traceId; |
|||
context.Request.Headers[traceIdKey] = traceId; |
|||
|
|||
var sw = Stopwatch.StartNew(); |
|||
sw.Start(); |
|||
|
|||
var sb = new StringBuilder(); |
|||
|
|||
sb.AppendLine(); |
|||
sb.AppendLine("-------------------Request Start-------------------"); |
|||
sb.AppendLine(DateTime.Now.ToString()); |
|||
sb.AppendLine($"追踪id:{traceId}"); |
|||
sb.AppendLine($"地址:{context.Request.GetDisplayUrl()}"); |
|||
sb.AppendLine("请求头:"); |
|||
foreach (var header in context.Request.Headers) |
|||
{ |
|||
sb.AppendLine($"{header.Key}:{header.Value}"); |
|||
} |
|||
sb.AppendLine($"数据:{await GetRequestBody(context.Request)}"); |
|||
sb.AppendLine("===================Request End==================="); |
|||
|
|||
Log.Information(sb.ToString()); |
|||
sb.Clear(); |
|||
|
|||
//Copy a pointer to the original response body stream
|
|||
var originalBodyStream = context.Response.Body; |
|||
|
|||
//Create a new memory stream...
|
|||
using var responseBody = new MemoryStream(); |
|||
//...and use that for the temporary response body
|
|||
context.Response.Body = responseBody; |
|||
|
|||
//Continue down the Middleware pipeline, eventually returning to this class
|
|||
await _next(context); |
|||
|
|||
//Format the response from the server
|
|||
//var response = await FormatResponse(context.Response);
|
|||
sb.AppendLine(); |
|||
sb.AppendLine("-------------------Response Start-------------------"); |
|||
sb.AppendLine(DateTime.Now.ToString()); |
|||
sb.AppendLine($"追踪id:{traceId}"); |
|||
sb.AppendLine($"耗时:{sw.ElapsedMilliseconds}毫秒"); |
|||
sb.AppendLine($"状态码:{context.Response.StatusCode}"); |
|||
//sb.AppendLine("Headers: ");
|
|||
//foreach (var header in context.Response.Headers)
|
|||
//{
|
|||
// sb.AppendLine($"{header.Key}:{header.Value}");
|
|||
//}
|
|||
sb.AppendLine($"数据:{await GetResponseBody(context.Response)}"); |
|||
sb.AppendLine("===================Response End==================="); |
|||
|
|||
//Save log to chosen datastore
|
|||
Log.Information(sb.ToString()); |
|||
|
|||
sb.Clear(); |
|||
|
|||
//Copy the contents of the new memory stream (which contains the response) to the original stream, which is then returned to the client.
|
|||
await responseBody.CopyToAsync(originalBodyStream); |
|||
} |
|||
|
|||
private static async Task<string> GetRequestBody(HttpRequest request) |
|||
{ |
|||
request.EnableBuffering(); |
|||
// Leave the body open so the next middleware can read it.
|
|||
using var reader = new StreamReader( |
|||
request.Body, |
|||
encoding: Encoding.UTF8, |
|||
detectEncodingFromByteOrderMarks: false, |
|||
leaveOpen: true); |
|||
var body = await reader.ReadToEndAsync(); |
|||
// Do some processing with body…
|
|||
|
|||
//var formattedRequest = $"{request.Scheme} {request.Host}{request.Path} {request.QueryString} {body}";
|
|||
|
|||
// Reset the request body stream position so the next middleware can read it
|
|||
request.Body.Position = 0; |
|||
|
|||
return body; |
|||
} |
|||
|
|||
private static async Task<string> GetResponseBody(HttpResponse response) |
|||
{ |
|||
//We need to read the response stream from the beginning...
|
|||
response.Body.Seek(0, SeekOrigin.Begin); |
|||
|
|||
//...and copy it into a string
|
|||
string text = await new StreamReader(response.Body).ReadToEndAsync(); |
|||
|
|||
//We need to reset the reader for the response so that the client can read it.
|
|||
response.Body.Seek(0, SeekOrigin.Begin); |
|||
|
|||
//Return the string for the response, including the status code (e.g. 200, 404, 401, etc.)
|
|||
//return $"{response.StatusCode}: {text}";
|
|||
return text; |
|||
} |
|||
|
|||
static AccessLoggingMiddleware() |
|||
{ |
|||
// 创建 IdGeneratorOptions 对象,可在构造函数中输入 WorkerId:
|
|||
var options = new IdGeneratorOptions(); |
|||
// options.WorkerIdBitLength = 10; // 默认值6,限定 WorkerId 最大值为2^6-1,即默认最多支持64个节点。
|
|||
// options.SeqBitLength = 6; // 默认值6,限制每毫秒生成的ID个数。若生成速度超过5万个/秒,建议加大 SeqBitLength 到 10。
|
|||
// options.BaseTime = Your_Base_Time; // 如果要兼容老系统的雪花算法,此处应设置为老系统的BaseTime。
|
|||
// ...... 其它参数参考 IdGeneratorOptions 定义。
|
|||
|
|||
// 保存参数(务必调用,否则参数设置不生效):
|
|||
YitIdHelper.SetIdGenerator(options); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,34 @@ |
|||
using Ocelot.Infrastructure.Claims.Parser; |
|||
using Ocelot.Logging; |
|||
using Ocelot.Middleware; |
|||
using Ocelot.Responses; |
|||
|
|||
namespace HuiXin.Gateway.Ocelot.Middlewares |
|||
{ |
|||
public class MyAuthorizationMiddleware : OcelotMiddleware |
|||
{ |
|||
private readonly RequestDelegate _next; |
|||
private readonly IClaimsParser _claimsParser; |
|||
|
|||
public MyAuthorizationMiddleware(RequestDelegate next, |
|||
IClaimsParser claimsParser, |
|||
IOcelotLoggerFactory loggerFactory) |
|||
: base(loggerFactory.CreateLogger<MyAuthorizationMiddleware>()) |
|||
{ |
|||
_next = next; |
|||
_claimsParser = claimsParser; |
|||
} |
|||
|
|||
public async Task Invoke(HttpContext httpContext) |
|||
{ |
|||
Authorize(httpContext); |
|||
await _next.Invoke(httpContext); |
|||
} |
|||
|
|||
public Response<bool> Authorize(HttpContext httpContext) |
|||
{ |
|||
var values = _claimsParser.GetValuesByClaimType(httpContext.User.Claims, "role"); |
|||
return new OkResponse<bool>(true); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,78 @@ |
|||
using HuiXin.Gateway.Ocelot.Extensions; |
|||
using Serilog; |
|||
using System.Security.Cryptography; |
|||
|
|||
namespace HuiXin.Gateway.Ocelot |
|||
{ |
|||
public class Program |
|||
{ |
|||
public static void Main(string[] args) |
|||
{ |
|||
//BuildJwt();
|
|||
|
|||
var configuration = new ConfigurationBuilder() |
|||
.AddJsonFile("Configs/ocelot.json", false, true) |
|||
.AddJsonFile("Configs/jwt.json", false, false) |
|||
.AddJsonFile("Configs/serilog.json", false, true) |
|||
.Build(); |
|||
|
|||
var logger = new LoggerConfiguration() |
|||
.ReadFrom.Configuration(configuration) |
|||
.CreateLogger(); |
|||
Log.Logger = logger; |
|||
|
|||
try |
|||
{ |
|||
var builder = WebApplication.CreateBuilder(args); |
|||
|
|||
builder.Configuration.AddConfiguration(configuration); |
|||
|
|||
var services = builder.Services; |
|||
|
|||
services.AddCors(); |
|||
services.AddLogging(loggingBuilder => loggingBuilder.ClearProviders().AddSerilog()); |
|||
|
|||
services.AddJWT(configuration); |
|||
|
|||
services.AddMyOcelot(configuration); |
|||
|
|||
var app = builder.Build(); |
|||
|
|||
app.UseCors(); |
|||
app.UseJWT(); |
|||
|
|||
app.UseMyOcelot(); |
|||
|
|||
app.Run(); |
|||
|
|||
Log.Information("系统已启动"); |
|||
} |
|||
catch (Exception ex) |
|||
{ |
|||
Log.Fatal(ex, "系统异常"); |
|||
} |
|||
finally |
|||
{ |
|||
Log.CloseAndFlush(); |
|||
} |
|||
} |
|||
|
|||
static void BuildJwt() |
|||
{ |
|||
using var aes = Aes.Create(); |
|||
aes.KeySize = 256; |
|||
aes.GenerateKey(); |
|||
var sign = Convert.ToBase64String(aes.Key); |
|||
var extraHeaders = new Dictionary<string, string> { { "kid", "MyKey" } }; |
|||
//过期时间(可以不设置,下面表示签名后 10秒过期)
|
|||
double exp = (DateTime.UtcNow.AddDays(10) - new DateTime(1970, 1, 1)).TotalSeconds; |
|||
var payload = new Dictionary<string, object> |
|||
{ |
|||
{ "userId", "001" }, |
|||
{ "userAccount", "fan" }, |
|||
{ "exp",exp } |
|||
}; |
|||
Console.WriteLine("Token:" + JWTUtil.CreateJwtToken(payload, sign, extraHeaders)); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,30 @@ |
|||
{ |
|||
"$schema": "http://json.schemastore.org/launchsettings.json", |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:21406", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"http": { |
|||
"commandName": "Project", |
|||
"dotnetRunMessages": true, |
|||
"launchBrowser": true, |
|||
"applicationUrl": "http://localhost:5000", |
|||
"launchUrl": "user/getById", |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"Logging": { |
|||
"LogLevel": { |
|||
"Default": "Information", |
|||
"Microsoft.AspNetCore": "Warning", |
|||
"Ocelot": "Error" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"Logging": { |
|||
"LogLevel": { |
|||
"Default": "Information", |
|||
"Microsoft.AspNetCore": "Warning" |
|||
} |
|||
}, |
|||
"AllowedHosts": "*" |
|||
} |
@ -0,0 +1,13 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net8.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Yarp.ReverseProxy" Version="2.1.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,15 @@ |
|||
namespace HuiXin.Gateway.Yarp |
|||
{ |
|||
public class Program |
|||
{ |
|||
public static void Main(string[] args) |
|||
{ |
|||
var builder = WebApplication.CreateBuilder(args); |
|||
var app = builder.Build(); |
|||
|
|||
app.MapGet("/", () => "Hello World!"); |
|||
|
|||
app.Run(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
{ |
|||
"$schema": "http://json.schemastore.org/launchsettings.json", |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:17782", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"http": { |
|||
"commandName": "Project", |
|||
"dotnetRunMessages": true, |
|||
"launchBrowser": true, |
|||
"applicationUrl": "http://localhost:5167", |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
{ |
|||
"Logging": { |
|||
"LogLevel": { |
|||
"Default": "Information", |
|||
"Microsoft.AspNetCore": "Warning" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"Logging": { |
|||
"LogLevel": { |
|||
"Default": "Information", |
|||
"Microsoft.AspNetCore": "Warning" |
|||
} |
|||
}, |
|||
"AllowedHosts": "*" |
|||
} |
@ -0,0 +1,37 @@ |
|||
|
|||
Microsoft Visual Studio Solution File, Format Version 12.00 |
|||
# Visual Studio Version 17 |
|||
VisualStudioVersion = 17.9.34622.214 |
|||
MinimumVisualStudioVersion = 10.0.40219.1 |
|||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuiXin.Gateway.Ocelot", "HuiXin.Gateway.Ocelot\HuiXin.Gateway.Ocelot.csproj", "{2E2162C5-FC45-478B-83D4-14148477D627}" |
|||
EndProject |
|||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HuiXin.Identity.OpenIddict", "HuiXin.Identity.OpenIddict\HuiXin.Identity.OpenIddict.csproj", "{819D01DC-9EFE-427E-B73E-9022DC7843EC}" |
|||
EndProject |
|||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HuiXin.Gateway.Yarp", "HuiXin.Gateway.Yarp\HuiXin.Gateway.Yarp.csproj", "{55A12844-A4CE-407A-BD7C-94469AA407F0}" |
|||
EndProject |
|||
Global |
|||
GlobalSection(SolutionConfigurationPlatforms) = preSolution |
|||
Debug|Any CPU = Debug|Any CPU |
|||
Release|Any CPU = Release|Any CPU |
|||
EndGlobalSection |
|||
GlobalSection(ProjectConfigurationPlatforms) = postSolution |
|||
{2E2162C5-FC45-478B-83D4-14148477D627}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{2E2162C5-FC45-478B-83D4-14148477D627}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{2E2162C5-FC45-478B-83D4-14148477D627}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{2E2162C5-FC45-478B-83D4-14148477D627}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{819D01DC-9EFE-427E-B73E-9022DC7843EC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{819D01DC-9EFE-427E-B73E-9022DC7843EC}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{819D01DC-9EFE-427E-B73E-9022DC7843EC}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{819D01DC-9EFE-427E-B73E-9022DC7843EC}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
{55A12844-A4CE-407A-BD7C-94469AA407F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU |
|||
{55A12844-A4CE-407A-BD7C-94469AA407F0}.Debug|Any CPU.Build.0 = Debug|Any CPU |
|||
{55A12844-A4CE-407A-BD7C-94469AA407F0}.Release|Any CPU.ActiveCfg = Release|Any CPU |
|||
{55A12844-A4CE-407A-BD7C-94469AA407F0}.Release|Any CPU.Build.0 = Release|Any CPU |
|||
EndGlobalSection |
|||
GlobalSection(SolutionProperties) = preSolution |
|||
HideSolutionNode = FALSE |
|||
EndGlobalSection |
|||
GlobalSection(ExtensibilityGlobals) = postSolution |
|||
SolutionGuid = {8F9B214D-FFA3-4E2C-8103-6A92331507EA} |
|||
EndGlobalSection |
|||
EndGlobal |
@ -0,0 +1,12 @@ |
|||
using Microsoft.EntityFrameworkCore; |
|||
|
|||
namespace HuiXin.Identity.OpenIddict |
|||
{ |
|||
public class ApplicationDbContext : DbContext |
|||
{ |
|||
public ApplicationDbContext(DbContextOptions options) |
|||
: base(options) |
|||
{ |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,357 @@ |
|||
using Microsoft.AspNetCore; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.Extensions.Primitives; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using Newtonsoft.Json; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Server.AspNetCore; |
|||
using System.Collections.Immutable; |
|||
using System.Security.Claims; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace HuiXin.Identity.OpenIddict.Controllers |
|||
{ |
|||
[ApiController] |
|||
[Route("auth")] |
|||
public class AuthorizationController : Controller |
|||
{ |
|||
private readonly IOpenIddictApplicationManager _applicationManager; |
|||
private readonly IOpenIddictAuthorizationManager _authorizationManager; |
|||
private readonly IOpenIddictScopeManager _scopeManager; |
|||
|
|||
public AuthorizationController(IOpenIddictApplicationManager applicationManager, IOpenIddictAuthorizationManager authorizationManager, IOpenIddictScopeManager scopeManager) |
|||
{ |
|||
_applicationManager = applicationManager; |
|||
_scopeManager = scopeManager; |
|||
_authorizationManager = authorizationManager; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// 登录权限校验
|
|||
/// </summary>
|
|||
/// <returns></returns>
|
|||
/// <exception cref="InvalidOperationException"></exception>
|
|||
/// <exception cref="Exception"></exception>
|
|||
[HttpGet("connect/authorize")] |
|||
[IgnoreAntiforgeryToken] |
|||
public async Task<IActionResult> Authorize() |
|||
{ |
|||
// var s=await _schemeProvider.GetAllSchemesAsync();
|
|||
|
|||
//通过扩展的获取自定义的参数校验
|
|||
var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("未获取到相关认证情况"); |
|||
|
|||
#region 存在登录凭证且明确了登录请求的行为
|
|||
// 存在登录凭证且明确了登录请求的行为
|
|||
if (request.HasPrompt(Prompts.Login)) |
|||
{ |
|||
//这里有个小坑,在Challenge之前必须把这个行为去掉 不然 Challenge 进入 /connect/authorize 路由陷入死循环
|
|||
var prompt = string.Join(" ", request.GetPrompts().Remove(Prompts.Login)); |
|||
var parameters = Request.HasFormContentType ? |
|||
Request.Form.Where(parameter => parameter.Key != Parameters.Prompt).ToList() : |
|||
Request.Query.Where(parameter => parameter.Key != Parameters.Prompt).ToList(); |
|||
|
|||
parameters.Add(KeyValuePair.Create(Parameters.Prompt, new StringValues(prompt))); |
|||
|
|||
return Challenge( |
|||
authenticationSchemes: new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme }, // IdentityConstants.ApplicationScheme,
|
|||
properties: new AuthenticationProperties |
|||
{ |
|||
RedirectUri = Request.PathBase + Request.Path + QueryString.Create(parameters) |
|||
}); |
|||
} |
|||
#endregion
|
|||
//检索本地的Cookies信息 确定重定向页面 这里都是UTC时间来设置的过期情况 这里没有用Identity 所以这里可以指定自己的应用名称
|
|||
var result = await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); //IdentityConstants.ApplicationScheme
|
|||
#region 未获取本地Cookies信息或者 cookie过期的情况
|
|||
if (request == null || !result.Succeeded || (request.MaxAge != null && result.Properties?.IssuedUtc != null && |
|||
DateTimeOffset.UtcNow - result.Properties.IssuedUtc > TimeSpan.FromSeconds(request.MaxAge.Value))) |
|||
{ |
|||
//是否是无效授权
|
|||
if (request.HasPrompt(Prompts.None)) |
|||
{ |
|||
return Forbid( |
|||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, |
|||
properties: new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "用户未登录." |
|||
})); |
|||
} |
|||
return Challenge( |
|||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, |
|||
properties: new AuthenticationProperties |
|||
{ |
|||
RedirectUri = Request.PathBase + Request.Path + QueryString.Create( |
|||
Request.HasFormContentType ? Request.Form.ToList() : Request.Query.ToList()) |
|||
}); |
|||
} |
|||
#endregion
|
|||
|
|||
|
|||
//var resultdata = _userService.GetLoginInfo(new Guid(result.Principal.GetClaim(Claims.Subject) ?? throw new Exception("用户标识存在"))) ?? throw new Exception("用户详细信息不存在");
|
|||
var resultdata = new UserInfo() |
|||
{ |
|||
Id = "user1", |
|||
UserName = "测试用户名", |
|||
NickName = "测试昵称", |
|||
}; |
|||
|
|||
// 获取客户端详细信息 验证其他数据
|
|||
var application = await _applicationManager.FindByClientIdAsync(request.ClientId) ?? throw new InvalidOperationException("未查找到该客户端的应用详细信息"); |
|||
|
|||
//查找当前情况客户端下请求用户的持久化授权数据信息
|
|||
var authorizations = _authorizationManager.FindAsync( |
|||
subject: resultdata.Id.ToString(), |
|||
client: await _applicationManager.GetIdAsync(application) ?? throw new Exception("没有找到客户端的应用信息"), //这里区分下 是application的Id而不是ClientId
|
|||
status: Statuses.Valid, |
|||
type: AuthorizationTypes.Permanent, |
|||
scopes: request.GetScopes() |
|||
).ToBlockingEnumerable();//.ToListAsync();
|
|||
|
|||
var consenttype = await _applicationManager.GetConsentTypeAsync(application); |
|||
//获取授权同意确认页面
|
|||
switch (consenttype) |
|||
{ |
|||
//判断授权同意的类型
|
|||
|
|||
//1 外部允许的且没有任何授权项
|
|||
case ConsentTypes.External when !authorizations.Any(): |
|||
return Forbid( |
|||
authenticationSchemes: new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme }, |
|||
properties: new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = |
|||
"登录用户没用访问该客户端应用的权限" |
|||
})); |
|||
|
|||
// 隐式、外部授权、显示模式模式
|
|||
case ConsentTypes.Implicit: |
|||
case ConsentTypes.External when authorizations.Any(): |
|||
case ConsentTypes.Explicit when authorizations.Any() && !request.HasPrompt(Prompts.Consent): |
|||
|
|||
ClaimsPrincipal principal = CreateUserPrincpal(resultdata); |
|||
//设置请求的范围
|
|||
principal.SetScopes(request.GetScopes()); |
|||
|
|||
//查找scope允许访问的资源
|
|||
var resources = _scopeManager.ListResourcesAsync(principal.GetScopes()).ToBlockingEnumerable(); |
|||
//通过扩展设置不同的资源访问 其实本质都是设置Claims 只是 key 在 scope以及Resource上不同
|
|||
//Resource = "oi_rsrc";
|
|||
// Scope = "oi_scp";
|
|||
|
|||
principal.SetResources(resources); |
|||
|
|||
// 自动创建一个永久授权,以避免需要明确的同意 用于包含相同范围的未来授权或令牌请求
|
|||
var authorization = authorizations.LastOrDefault(); |
|||
if (authorization is null) |
|||
{ |
|||
authorization = await _authorizationManager.CreateAsync( |
|||
principal: principal, |
|||
subject: resultdata.Id.ToString(), |
|||
client: await _applicationManager.GetIdAsync(application), |
|||
type: AuthorizationTypes.Permanent, |
|||
scopes: principal.GetScopes()); |
|||
} |
|||
|
|||
principal.SetAuthorizationId(await _authorizationManager.GetIdAsync(authorization)); |
|||
|
|||
foreach (var claim in principal.Claims) |
|||
{ |
|||
claim.SetDestinations(GetDestinations(claim, principal)); |
|||
} |
|||
//登录 OpenIddict签发令牌
|
|||
return SignIn(principal, null, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
|
|||
// At this point, no authorization was found in the database and an error must be returned
|
|||
// if the client application specified prompt=none in the authorization request.
|
|||
case ConsentTypes.Explicit when request.HasPrompt(Prompts.None): |
|||
case ConsentTypes.Systematic when request.HasPrompt(Prompts.None): |
|||
return Forbid( |
|||
authenticationSchemes: new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme }, |
|||
properties: new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.ConsentRequired, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = |
|||
"Interactive user consent is required." |
|||
})); |
|||
|
|||
// In every other case, render the consent form.
|
|||
//default:
|
|||
// return View(new AuthorizeViewModel
|
|||
// {
|
|||
// ApplicationName = await _applicationManager.GetLocalizedDisplayNameAsync(application),
|
|||
// Scope = request.Scope
|
|||
// });
|
|||
default: |
|||
return Challenge( |
|||
authenticationSchemes: new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme }, |
|||
properties: new AuthenticationProperties { RedirectUri = "/" } |
|||
); |
|||
} |
|||
} |
|||
|
|||
|
|||
[HttpPost("connect/token"), Produces("application/json")] |
|||
public async Task<IActionResult> ConnectToken() |
|||
{ |
|||
var request = HttpContext.GetOpenIddictServerRequest() ?? throw new InvalidOperationException("OIDC请求不存在"); |
|||
|
|||
if (request.IsClientCredentialsGrantType()) |
|||
{ |
|||
// Note: the client credentials are automatically validated by OpenIddict:
|
|||
// if client_id or client_secret are invalid, this action won't be invoked.
|
|||
|
|||
var application = await _applicationManager.FindByClientIdAsync(request.ClientId); |
|||
if (application is null) |
|||
{ |
|||
Console.WriteLine($"client_id未注册:{request.ClientId}"); |
|||
throw new InvalidOperationException("The application cannot be found."); |
|||
} |
|||
Console.WriteLine($"获取到客户信息:{JsonConvert.SerializeObject(application)}"); |
|||
|
|||
// Create a new ClaimsIdentity containing the claims that
|
|||
// will be used to create an id_token, a token or a code.
|
|||
var identity = new ClaimsIdentity(TokenValidationParameters.DefaultAuthenticationType, Claims.Name, Claims.Role); |
|||
|
|||
// Use the client_id as the subject identifier.
|
|||
identity.SetClaim(Claims.Subject, await _applicationManager.GetClientIdAsync(application)); |
|||
identity.SetClaim(Claims.Name, await _applicationManager.GetDisplayNameAsync(application)); |
|||
|
|||
identity.SetDestinations(static claim => claim.Type switch |
|||
{ |
|||
// Allow the "name" claim to be stored in both the access and identity tokens
|
|||
// when the "profile" scope was granted (by calling principal.SetScopes(...)).
|
|||
Claims.Name when claim.Subject.HasScope(Scopes.Profile) |
|||
=> new[] { Destinations.AccessToken, Destinations.IdentityToken }, |
|||
|
|||
// Otherwise, only store the claim in the access tokens.
|
|||
_ => new[] { Destinations.AccessToken } |
|||
}); |
|||
//Console.WriteLine($"identity信息:{JsonConvert.SerializeObject(identity)}");
|
|||
|
|||
var result = SignIn(new ClaimsPrincipal(identity), OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
//Console.WriteLine($"signIn信息:{JsonConvert.SerializeObject(result)}");
|
|||
return result; |
|||
} |
|||
else if (request.IsPasswordGrantType()) |
|||
{ |
|||
//数据库获取用户信息
|
|||
var princpal = CreateUserPrincpal(new UserInfo() |
|||
{ |
|||
Id = Guid.NewGuid().ToString(), |
|||
UserName = "测试用户名", |
|||
NickName = "测试昵称", |
|||
}); |
|||
//princpal.SetResources(_scopeManager.ListResourcesAsync(princpal.GetScopes()));
|
|||
|
|||
foreach (var claim in princpal.Claims) |
|||
{ |
|||
claim.SetDestinations(GetDestinations(claim, princpal)); |
|||
} |
|||
return SignIn(princpal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
else if (request.IsAuthorizationCodeGrantType() || request.IsDeviceCodeGrantType() || request.IsRefreshTokenGrantType()) |
|||
{ |
|||
var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal; |
|||
//var user = _userService.GetLoginInfo(new Guid(principal?.GetClaim(Claims.Subject) ?? throw new Exception("用户标识存在")));
|
|||
//UserInfo user = null;
|
|||
//if (user == null)
|
|||
//{
|
|||
// return Forbid(
|
|||
// authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
|
|||
// properties: new AuthenticationProperties(new Dictionary<string, string?>
|
|||
// {
|
|||
// [OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant,
|
|||
// [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "令牌已失效"
|
|||
// })
|
|||
// );
|
|||
//}
|
|||
foreach (var claim in principal.Claims) |
|||
{ |
|||
claim.SetDestinations(GetDestinations(claim, principal)); |
|||
} |
|||
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
throw new InvalidOperationException($"不支持的grant_type:{request.GrantType}"); |
|||
} |
|||
|
|||
[HttpPost("connect/logout")] |
|||
public async Task<IActionResult> Logout() |
|||
{ |
|||
await HttpContext.SignOutAsync(); |
|||
return SignOut( |
|||
authenticationSchemes: OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, |
|||
properties: new AuthenticationProperties |
|||
{ |
|||
RedirectUri = "/" |
|||
}); |
|||
} |
|||
|
|||
public static ClaimsPrincipal CreateUserPrincpal(UserInfo user, string claimsIdentityName = "USERINFO") |
|||
{ |
|||
//登录成功流程
|
|||
ClaimsIdentity identity = new ClaimsIdentity(claimsIdentityName); |
|||
identity.AddClaim(new Claim(Claims.Subject, user.Id)); |
|||
identity.AddClaim(new Claim(Claims.Name, user.UserName)); |
|||
identity.AddClaim(new Claim(Claims.Nickname, user.NickName)); |
|||
return new ClaimsPrincipal(identity); |
|||
} |
|||
|
|||
private IEnumerable<string> GetDestinations(Claim claim) |
|||
{ |
|||
// Note: by default, claims are NOT automatically included in the access and identity tokens.
|
|||
// To allow OpenIddict to serialize them, you must attach them a destination, that specifies
|
|||
// whether they should be included in access tokens, in identity tokens or in both.
|
|||
|
|||
return claim.Type switch |
|||
{ |
|||
Claims.Name or |
|||
Claims.Subject |
|||
=> ImmutableArray.Create(Destinations.AccessToken, Destinations.IdentityToken), |
|||
|
|||
_ => ImmutableArray.Create(Destinations.AccessToken), |
|||
}; |
|||
} |
|||
|
|||
private IEnumerable<string> GetDestinations(Claim claim, ClaimsPrincipal principal) |
|||
{ |
|||
|
|||
switch (claim.Type) |
|||
{ |
|||
case Claims.Name: |
|||
yield return Destinations.AccessToken; |
|||
|
|||
if (principal.HasScope(Scopes.Profile)) |
|||
yield return Destinations.IdentityToken; |
|||
|
|||
yield break; |
|||
|
|||
case Claims.Email: |
|||
yield return Destinations.AccessToken; |
|||
|
|||
if (principal.HasScope(Scopes.Email)) |
|||
yield return Destinations.IdentityToken; |
|||
|
|||
yield break; |
|||
|
|||
case Claims.Role: |
|||
yield return Destinations.AccessToken; |
|||
|
|||
if (principal.HasScope(Scopes.Roles)) |
|||
yield return Destinations.IdentityToken; |
|||
|
|||
yield break; |
|||
|
|||
|
|||
case "AspNet.Identity.SecurityStamp": yield break; |
|||
|
|||
default: |
|||
yield return Destinations.AccessToken; |
|||
yield break; |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,21 @@ |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using OpenIddict.Abstractions; |
|||
|
|||
namespace HuiXin.Identity.OpenIddict.Controllers |
|||
{ |
|||
[ApiController] |
|||
[Route("user")] |
|||
public class UserController : Controller |
|||
{ |
|||
private readonly IOpenIddictApplicationManager _applicationManager; |
|||
private readonly IOpenIddictAuthorizationManager _authorizationManager; |
|||
private readonly IOpenIddictScopeManager _scopeManager; |
|||
|
|||
public UserController(IOpenIddictApplicationManager applicationManager, IOpenIddictAuthorizationManager authorizationManager, IOpenIddictScopeManager scopeManager) |
|||
{ |
|||
_applicationManager = applicationManager; |
|||
_scopeManager = scopeManager; |
|||
_authorizationManager = authorizationManager; |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,19 @@ |
|||
<Project Sdk="Microsoft.NET.Sdk.Web"> |
|||
|
|||
<PropertyGroup> |
|||
<TargetFramework>net8.0</TargetFramework> |
|||
<Nullable>enable</Nullable> |
|||
<ImplicitUsings>enable</ImplicitUsings> |
|||
</PropertyGroup> |
|||
|
|||
<ItemGroup> |
|||
<PackageReference Include="Microsoft.AspNetCore.Identity.EntityFrameworkCore" Version="8.0.3" /> |
|||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.3" /> |
|||
<PackageReference Include="MySql.EntityFrameworkCore" Version="8.0.0" /> |
|||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> |
|||
<PackageReference Include="OpenIddict.AspNetCore" Version="5.3.0" /> |
|||
<PackageReference Include="OpenIddict.EntityFrameworkCore" Version="5.3.0" /> |
|||
<PackageReference Include="OpenIddict.Validation.AspNetCore" Version="5.3.0" /> |
|||
</ItemGroup> |
|||
|
|||
</Project> |
@ -0,0 +1,108 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using OpenIddict.Validation.AspNetCore; |
|||
using System.Text; |
|||
|
|||
namespace HuiXin.Identity.OpenIddict |
|||
{ |
|||
public class Program |
|||
{ |
|||
public static void Main(string[] args) |
|||
{ |
|||
var builder = WebApplication.CreateBuilder(args); |
|||
|
|||
// Add services to the container.
|
|||
builder.Services.AddCors(); |
|||
builder.Services.AddControllers(); |
|||
|
|||
builder.Services |
|||
.AddAuthentication(OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme) |
|||
.AddCookie(); |
|||
|
|||
builder.Services.AddDbContext<ApplicationDbContext>(options => |
|||
{ |
|||
//options.UseSqlite("DataSource=:memory:");
|
|||
options.UseMySQL("server=8.134.236.110;port=13306;database=openiddict;user=test;password=test") |
|||
.UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); |
|||
|
|||
// Register the entity sets needed by OpenIddict.
|
|||
// Note: use the generic overload if you need to replace the default OpenIddict entities.
|
|||
options.UseOpenIddict(); |
|||
}); |
|||
|
|||
builder.Services.AddIdentity<UserInfo, IdentityRole>() |
|||
.AddEntityFrameworkStores<ApplicationDbContext>() |
|||
.AddDefaultTokenProviders(); |
|||
|
|||
builder.Services |
|||
.AddOpenIddict() |
|||
// Register the OpenIddict core components.
|
|||
.AddCore(options => |
|||
{ |
|||
// Configure OpenIddict to use the Entity Framework Core stores and models.
|
|||
// Note: call ReplaceDefaultEntities() to replace the default entities.
|
|||
options.UseEntityFrameworkCore() |
|||
.UseDbContext<ApplicationDbContext>(); |
|||
}) |
|||
// Register the OpenIddict server components.
|
|||
.AddServer(options => |
|||
{ |
|||
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(5)); |
|||
options.SetIdentityTokenLifetime(TimeSpan.FromMinutes(5)); |
|||
options.SetRefreshTokenLifetime(TimeSpan.FromDays(365 * 100)); |
|||
|
|||
// Enable the token endpoint.
|
|||
options.SetTokenEndpointUris("auth/connect/token"); |
|||
options.SetAuthorizationEndpointUris("auth/connect/authorize"); |
|||
options.SetUserinfoEndpointUris("auth/connect/userinfo"); |
|||
options.SetLogoutEndpointUris("auth/connect/logout"); |
|||
|
|||
// Enable the client credentials flow.
|
|||
options.AllowClientCredentialsFlow(); |
|||
options.AllowAuthorizationCodeFlow(); |
|||
options.AllowPasswordFlow(); |
|||
options.AllowRefreshTokenFlow(); |
|||
|
|||
// Register the signing and encryption credentials.
|
|||
options.AddEncryptionKey(new SymmetricSecurityKey(Convert.FromBase64String("GcTdqSZdpRxBtdtgwvDHBzS427VGTQzbM+JD1CBbUZY="))); |
|||
options.AddDevelopmentSigningCertificate(); |
|||
|
|||
// Register the ASP.NET Core host and configure the ASP.NET Core options.
|
|||
options.UseAspNetCore() |
|||
.EnableTokenEndpointPassthrough() |
|||
.EnableAuthorizationEndpointPassthrough() |
|||
.EnableUserinfoEndpointPassthrough() |
|||
.EnableLogoutEndpointPassthrough() |
|||
.DisableTransportSecurityRequirement(); |
|||
|
|||
options.IgnoreEndpointPermissions(); |
|||
options.IgnoreGrantTypePermissions(); |
|||
options.IgnoreScopePermissions(); |
|||
options.IgnoreResponseTypePermissions(); |
|||
}) |
|||
// Register the OpenIddict validation components.
|
|||
.AddValidation(options => |
|||
{ |
|||
// Import the configuration from the local OpenIddict server instance.
|
|||
options.UseLocalServer(); |
|||
|
|||
// Register the ASP.NET Core host.
|
|||
options.UseAspNetCore(); |
|||
}); |
|||
builder.Services.AddHostedService<Worker>(); |
|||
|
|||
var app = builder.Build(); |
|||
|
|||
// Configure the HTTP request pipeline.
|
|||
|
|||
//app.UseHttpsRedirection();
|
|||
|
|||
app.UseAuthorization(); |
|||
|
|||
app.MapControllers(); |
|||
|
|||
app.Run(); |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,29 @@ |
|||
{ |
|||
"$schema": "http://json.schemastore.org/launchsettings.json", |
|||
"iisSettings": { |
|||
"windowsAuthentication": false, |
|||
"anonymousAuthentication": true, |
|||
"iisExpress": { |
|||
"applicationUrl": "http://localhost:61533", |
|||
"sslPort": 0 |
|||
} |
|||
}, |
|||
"profiles": { |
|||
"http": { |
|||
"commandName": "Project", |
|||
"dotnetRunMessages": true, |
|||
"launchBrowser": true, |
|||
"applicationUrl": "http://localhost:5001", |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
}, |
|||
"IIS Express": { |
|||
"commandName": "IISExpress", |
|||
"launchBrowser": true, |
|||
"environmentVariables": { |
|||
"ASPNETCORE_ENVIRONMENT": "Development" |
|||
} |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,11 @@ |
|||
using Microsoft.AspNetCore.Identity; |
|||
|
|||
namespace HuiXin.Identity.OpenIddict |
|||
{ |
|||
public class UserInfo : IdentityUser |
|||
{ |
|||
public string Id { get; set; } |
|||
public string UserName { get; set; } |
|||
public string NickName { get; set; } |
|||
} |
|||
} |
@ -0,0 +1,41 @@ |
|||
using OpenIddict.Abstractions; |
|||
|
|||
namespace HuiXin.Identity.OpenIddict |
|||
{ |
|||
public class Worker : IHostedService |
|||
{ |
|||
private readonly IServiceProvider _serviceProvider; |
|||
|
|||
public Worker(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider; |
|||
|
|||
public async Task StartAsync(CancellationToken cancellationToken) |
|||
{ |
|||
using var scope = _serviceProvider.CreateScope(); |
|||
|
|||
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>(); |
|||
await context.Database.EnsureCreatedAsync(cancellationToken); |
|||
|
|||
var manager = scope.ServiceProvider.GetRequiredService<IOpenIddictApplicationManager>(); |
|||
//var authManager = scope.ServiceProvider.GetRequiredService<IOpenIddictAuthorizationManager>();
|
|||
|
|||
var data = await manager.FindByClientIdAsync("apisix", cancellationToken) ?? await manager.CreateAsync( |
|||
new OpenIddictApplicationDescriptor |
|||
{ |
|||
ClientId = "apisix", |
|||
ClientSecret = "388D45FA-B36B-4988-BA59-B187D329C207", |
|||
DisplayName = "APISIX的测试客户", |
|||
Permissions = |
|||
{ |
|||
OpenIddictConstants.Permissions.Endpoints.Authorization, |
|||
OpenIddictConstants.Permissions.Endpoints.Logout, |
|||
OpenIddictConstants.Permissions.Endpoints.Token |
|||
}, |
|||
RedirectUris = { new Uri("http://8.134.191.93:9080/test/callback")}, |
|||
}, cancellationToken); |
|||
//await manager.DeleteAsync(data);
|
|||
Console.WriteLine($"APISIX的测试客户:{System.Text.Json.JsonSerializer.Serialize(data)}"); |
|||
} |
|||
|
|||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask; |
|||
} |
|||
} |
@ -0,0 +1,8 @@ |
|||
{ |
|||
"Logging": { |
|||
"LogLevel": { |
|||
"Default": "Information", |
|||
"Microsoft.AspNetCore": "Warning" |
|||
} |
|||
} |
|||
} |
@ -0,0 +1,9 @@ |
|||
{ |
|||
"Logging": { |
|||
"LogLevel": { |
|||
"Default": "Information", |
|||
"Microsoft.AspNetCore": "Warning" |
|||
} |
|||
}, |
|||
"AllowedHosts": "*" |
|||
} |
Loading…
Reference in new issue