✨
This commit is contained in:
56
StopShopping.Api/Extensions/AuthExtensions.cs
Normal file
56
StopShopping.Api/Extensions/AuthExtensions.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using StopShopping.Services;
|
||||
|
||||
namespace StopShopping.Api.Extensions;
|
||||
|
||||
public static class JwtExtensions
|
||||
{
|
||||
public static IServiceCollection AddAuthServices(this IServiceCollection services, IConfiguration jwtOptions)
|
||||
{
|
||||
services.Configure<JwtOptions>(jwtOptions);
|
||||
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
|
||||
.AddJwtBearer(jwtBearerOptions =>
|
||||
{
|
||||
var jwtConfiguration = jwtOptions.Get<JwtOptions>()!;
|
||||
|
||||
var signingKey = new SymmetricSecurityKey(
|
||||
Encoding.UTF8.GetBytes(jwtConfiguration.SigningKey!)
|
||||
);
|
||||
|
||||
jwtBearerOptions.MapInboundClaims = false;
|
||||
jwtBearerOptions.TokenValidationParameters = new TokenValidationParameters
|
||||
{
|
||||
ValidAudience = jwtConfiguration.ValidAudience,
|
||||
ValidIssuer = jwtConfiguration.ValidIssuer,
|
||||
IssuerSigningKey = signingKey,
|
||||
ClockSkew = TimeSpan.FromSeconds(30) //宽容时间,30秒后才失效
|
||||
};
|
||||
jwtBearerOptions.Events = new JwtBearerEvents
|
||||
{
|
||||
OnMessageReceived = async (context) =>
|
||||
{
|
||||
var accessTokenService = context.HttpContext.RequestServices.GetRequiredService<IAccessTokenService>();
|
||||
var authorizationHeader = context.Request.Headers[HeaderNames.Authorization];
|
||||
if (authorizationHeader.Count == 0)
|
||||
{
|
||||
context.Fail($"未找到{HeaderNames.Authorization}请求头");
|
||||
}
|
||||
else
|
||||
{
|
||||
var token = authorizationHeader.First()!.Split(" ").Last();
|
||||
if (string.IsNullOrWhiteSpace(token))
|
||||
context.Fail("未找到token");
|
||||
if (await accessTokenService.IsAccessTokenBlacklistAsync(token))
|
||||
context.Fail("token已失效");
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
services.AddAuthorization();
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||
using Microsoft.AspNetCore.OpenApi;
|
||||
using Microsoft.OpenApi;
|
||||
|
||||
namespace StopShopping.Api.Extensions;
|
||||
|
||||
// 来自网络。。。
|
||||
public class BearerOpenApiDocumentTransformer : IOpenApiDocumentTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
var bearerOpenApiSecurityScheme = new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Scheme = JwtBearerDefaults.AuthenticationScheme,
|
||||
Type = SecuritySchemeType.Http,
|
||||
Description = "jwt"
|
||||
};
|
||||
|
||||
document.Components ??= new OpenApiComponents();
|
||||
document.Components.SecuritySchemes ??= new Dictionary<string, IOpenApiSecurityScheme>();
|
||||
document.Components.SecuritySchemes[JwtBearerDefaults.AuthenticationScheme] = bearerOpenApiSecurityScheme;
|
||||
|
||||
var securityRequirement = new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecuritySchemeReference(JwtBearerDefaults.AuthenticationScheme),
|
||||
new List<string>()
|
||||
}
|
||||
};
|
||||
|
||||
document.Security ??= [];
|
||||
document.Security.Add(securityRequirement);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
79
StopShopping.Api/Extensions/CommonServiceCollections.cs
Normal file
79
StopShopping.Api/Extensions/CommonServiceCollections.cs
Normal file
@@ -0,0 +1,79 @@
|
||||
using System.Text.Json.Serialization;
|
||||
using StopShopping.Api.Middlewares;
|
||||
using StopShopping.Services;
|
||||
using StopShopping.Services.Extensions;
|
||||
|
||||
namespace StopShopping.Api.Extensions;
|
||||
|
||||
public static class CommonServiceCollections
|
||||
{
|
||||
public static IServiceCollection AddCommonServices(
|
||||
this IServiceCollection services,
|
||||
string corsPolicy,
|
||||
IConfigurationSection jwtConfiguration,
|
||||
IConfigurationSection appConfiguration,
|
||||
bool isDevelopment)
|
||||
{
|
||||
var appOptions = appConfiguration.Get<AppOptions>();
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy(corsPolicy, policy =>
|
||||
{
|
||||
policy.AllowAnyHeader();
|
||||
policy.AllowAnyMethod();
|
||||
policy.WithOrigins(appOptions!.CorsAllowedOrigins);
|
||||
policy.AllowCredentials();
|
||||
});
|
||||
});
|
||||
services.ConfigureHttpJsonOptions(options =>
|
||||
{
|
||||
options.SerializerOptions.Converters.Add(
|
||||
new JsonStringEnumConverter(namingPolicy: null, allowIntegerValues: true));
|
||||
});
|
||||
services.AddHttpContextAccessor();
|
||||
services.AddOpenApi(options =>
|
||||
{
|
||||
options.AddDocumentTransformer<BearerOpenApiDocumentTransformer>();
|
||||
options.AddSchemaTransformer<EnumOpenApiSchemaTransformer>();
|
||||
});
|
||||
services.AddProblemDetails(options =>
|
||||
{
|
||||
options.CustomizeProblemDetails = (context) =>
|
||||
{
|
||||
if (context.ProblemDetails is HttpValidationProblemDetails problemDetails)
|
||||
{
|
||||
problemDetails.AddErrorCode(ProblemDetailsCodes.ParametersValidationFailed);
|
||||
var errors = problemDetails.Errors.Select(e => string.Join(',', e.Value));
|
||||
if (null != errors)
|
||||
problemDetails.Detail = string.Join(',', errors);
|
||||
}
|
||||
};
|
||||
});
|
||||
services.AddValidation();
|
||||
services.AddDistributedMemoryCache();
|
||||
|
||||
services.AddAuthServices(jwtConfiguration);
|
||||
|
||||
services.AddAntiforgery(options =>
|
||||
{
|
||||
var jwtOptions = jwtConfiguration.Get<JwtOptions>();
|
||||
|
||||
options.HeaderName = appOptions!.CSRFHeaderName;
|
||||
options.Cookie.MaxAge = TimeSpan.FromSeconds(jwtOptions!.RefreshTokenExpiresIn);
|
||||
options.Cookie.HttpOnly = true;
|
||||
options.Cookie.Name = appOptions.CSRFCookieName;
|
||||
if (isDevelopment)
|
||||
{
|
||||
options.Cookie.SameSite = SameSiteMode.Lax;
|
||||
}
|
||||
else
|
||||
{
|
||||
options.Cookie.SameSite = SameSiteMode.None;
|
||||
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
|
||||
options.Cookie.Domain = appOptions.CookieDomain;
|
||||
}
|
||||
});
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
49
StopShopping.Api/Extensions/EnumOpenApiSchemaTransformer.cs
Normal file
49
StopShopping.Api/Extensions/EnumOpenApiSchemaTransformer.cs
Normal file
@@ -0,0 +1,49 @@
|
||||
using System.ComponentModel;
|
||||
using System.Text.Json.Nodes;
|
||||
using Microsoft.AspNetCore.OpenApi;
|
||||
using Microsoft.OpenApi;
|
||||
|
||||
namespace StopShopping.Api.Extensions;
|
||||
|
||||
/// <summary>
|
||||
/// 处理enum类型openapi显示
|
||||
/// </summary>
|
||||
public class EnumOpenApiSchemaTransformer : IOpenApiSchemaTransformer
|
||||
{
|
||||
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
if (context.JsonTypeInfo.Type.IsEnum)
|
||||
{
|
||||
schema.Type = JsonSchemaType.Integer;
|
||||
|
||||
var enumValues = Enum.GetValues(context.JsonTypeInfo.Type)
|
||||
.Cast<object>()
|
||||
.Select(v => JsonNode.Parse(Convert.ToInt32(v).ToString())!)
|
||||
.ToList();
|
||||
|
||||
schema.Enum = enumValues;
|
||||
|
||||
var enumNames = Enum.GetNames(context.JsonTypeInfo.Type);
|
||||
schema.Extensions ??= new Dictionary<string, IOpenApiExtension>();
|
||||
var namesExtension = new JsonNodeExtension(new JsonArray(
|
||||
enumNames
|
||||
.Select(n => (JsonNode)n)
|
||||
.ToArray()));
|
||||
schema.Extensions.Add("x-enumNames", namesExtension);
|
||||
|
||||
var descMap = new JsonObject();
|
||||
foreach (var name in enumNames)
|
||||
{
|
||||
if (context.JsonTypeInfo.Type.GetField(name)
|
||||
?.GetCustomAttributes(typeof(DescriptionAttribute), false)
|
||||
.FirstOrDefault() is DescriptionAttribute attr)
|
||||
{
|
||||
descMap[name] = attr.Description;
|
||||
}
|
||||
}
|
||||
schema.Extensions.Add("x-enumDescriptions", new JsonNodeExtension(descMap));
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
36
StopShopping.Api/Extensions/HttpExtensions.cs
Normal file
36
StopShopping.Api/Extensions/HttpExtensions.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using StopShopping.Services.Extensions;
|
||||
|
||||
namespace Microsoft.AspNetCore.Http;
|
||||
|
||||
public static class HttpExtensions
|
||||
{
|
||||
public const string REFRESH_TOKEN_COOKIE_KEY = "refresh_token";
|
||||
|
||||
public static IResponseCookies AppendRefreshToken(
|
||||
this IResponseCookies cookies,
|
||||
IWebHostEnvironment env,
|
||||
AppOptions appOptions,
|
||||
TimeSpan maxAge,
|
||||
string token)
|
||||
{
|
||||
CookieOptions options = new()
|
||||
{
|
||||
MaxAge = maxAge,
|
||||
HttpOnly = true,
|
||||
SameSite = SameSiteMode.Lax,
|
||||
};
|
||||
if (!env.IsDevelopment())
|
||||
{
|
||||
options.SameSite = SameSiteMode.None;
|
||||
options.Secure = true;
|
||||
options.Domain = appOptions.CookieDomain;
|
||||
}
|
||||
|
||||
cookies.Append(
|
||||
REFRESH_TOKEN_COOKIE_KEY,
|
||||
token,
|
||||
options);
|
||||
|
||||
return cookies;
|
||||
}
|
||||
}
|
||||
25
StopShopping.Api/Extensions/MiddlewareExtensions.cs
Normal file
25
StopShopping.Api/Extensions/MiddlewareExtensions.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using StopShopping.Api.Middlewares;
|
||||
|
||||
namespace Microsoft.AspNetCore.Builder;
|
||||
|
||||
public static class MiddlewareExtensions
|
||||
{
|
||||
public static IApplicationBuilder UseGlobalExceptionHandler(this IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
applicationBuilder.UseMiddleware<GlobalExceptionHandlerMiddleware>();
|
||||
|
||||
return applicationBuilder;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 解决开发时多客户端localhost端口串cookie的问题
|
||||
/// </summary>
|
||||
/// <param name="applicationBuilder"></param>
|
||||
/// <returns></returns>
|
||||
public static IApplicationBuilder UseDevelopmentCookie(this IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
applicationBuilder.UseMiddleware<DevelopmentCookieMiddleware>();
|
||||
|
||||
return applicationBuilder;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user