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