修复 token无法校验问题

This commit is contained in:
小肥羊 2025-08-04 10:45:53 +08:00
parent 7075ec039d
commit 658b75e3ad
17 changed files with 279 additions and 48 deletions

View File

@ -17,7 +17,7 @@ namespace Learn.Archives.API.Controllers
this.baseService = baseService; this.baseService = baseService;
} }
/// <summary> /// <summary>
/// 后台管理员登录 /// 管理员登录
/// </summary> /// </summary>
/// <param name="model"></param> /// <param name="model"></param>
/// <returns></returns> /// <returns></returns>
@ -36,7 +36,7 @@ namespace Learn.Archives.API.Controllers
Oh.Error("登录失败,用户不存在!"); Oh.Error("登录失败,用户不存在!");
if (!admin!.Enable) if (!admin!.Enable)
Oh.Error("登录失败,用户已锁定!"); Oh.Error("登录失败,用户已锁定!");
if (admin.Password != model.Password) if (admin.Password != model.Password.GetMD5())
Oh.Error("登录失败,密码错误"); Oh.Error("登录失败,密码错误");
// 获取租户信息 // 获取租户信息
@ -48,5 +48,7 @@ namespace Learn.Archives.API.Controllers
new Claim(ClaimEnum.Name, admin.Name), new Claim(ClaimEnum.Name, admin.Name),
]); ]);
} }
} }
} }

View File

@ -0,0 +1,15 @@
using Learn.Archives.Core.Model;
namespace Learn.Archives.API.Controllers.Dto
{
/// <summary>
/// 菜单树
/// </summary>
public class MenuTree : Menu
{
/// <summary>
/// 子菜单列表
/// </summary>
public MenuTree[] Children { get; set; } = Array.Empty<MenuTree>();
}
}

View File

@ -0,0 +1,71 @@
using Learn.Archives.API.Controllers.Dto;
using Learn.Archives.API.Expand;
using Learn.Archives.Core.Common;
using Learn.Archives.Core.Model;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using System.Security.Claims;
using UserCenter.Model;
namespace Learn.Archives.API.Controllers
{
/// <summary>
/// 路由菜单
/// </summary>
public class MenuController : BackController<Menu>
{
readonly Repository<Menu> baseService;
readonly Repository<MenuRelation> menuRelationDB;
readonly LiveUserInfo userInfo;
public MenuController(Repository<Menu> baseService, LiveUserInfo userInfo, Repository<MenuRelation> menuRelationDB) : base(baseService)
{
this.baseService = baseService;
this.userInfo = userInfo;
this.menuRelationDB = menuRelationDB;
}
/// <summary>
/// 管理员菜单
/// </summary>
/// <returns></returns>
[HttpGet]
public async Task<MenuTree[]> AdminMenu()
{
var rId = userInfo.RoleId;
if (rId == 0) Oh.Error("登录了无效的用户");
var menuArr = await menuRelationDB.AsQueryable()
.LeftJoin<Menu>((mr, m) => mr.MenuId == m.Id)
.Where((mr,m)=> mr.RoleId == userInfo.RoleId)
.Select((mr, m) => m)
.ToArrayAsync();
return GetChildren(menuArr, menuArr.First().Id);
}
/// <summary>
/// 递归获取子菜单
/// </summary>
[NonAction]
private static MenuTree[] GetChildren(IEnumerable<Menu> menus, long parentId)
{
return menus
.Where(m => m.ParentId == parentId)
.OrderBy(m => m.Rank)
.Select(m => new MenuTree
{
Id = m.Id,
Name = m.Name,
Title = m.Title,
Path = m.Path,
IsButton = m.IsButton,
Icon = m.Icon,
Auths = m.Auths,
Rank = m.Rank,
ShowLink = m.ShowLink,
ParentId = m.ParentId,
Children = GetChildren(menus, m.Id)
})
.ToArray();
}
}
}

View File

@ -12,7 +12,7 @@ using UserCenter.Model.Interface;
namespace Learn.Archives.API.Controllers namespace Learn.Archives.API.Controllers
{ {
[ApiController] [ApiController]
public class BaseController : ControllerBase public class _BaseController : ControllerBase
{ {
} }
@ -21,7 +21,7 @@ namespace Learn.Archives.API.Controllers
/// </summary> /// </summary>
[Authorize(AuthenticationSchemes = Authentication.Admin)] [Authorize(AuthenticationSchemes = Authentication.Admin)]
[Route("api/[controller]/[action]")] [Route("api/[controller]/[action]")]
public abstract class BackBaseController : BaseController public abstract class BackBaseController : _BaseController
{ {
} }
@ -108,9 +108,7 @@ namespace Learn.Archives.API.Controllers
[NonAction] [NonAction]
public virtual ISugarQueryable<T> BaseQuery(QueryDto model) public virtual ISugarQueryable<T> BaseQuery(QueryDto model)
{ {
List<IConditionalModel> where = new List<IConditionalModel>(); List<IConditionalModel> where = [.. model.Conditions];
foreach (var item in model.Conditions)
where.Add(item);
var d = _baseRepository.AsQueryable() var d = _baseRepository.AsQueryable()
.Where(where); .Where(where);
if ((typeof(T)).GetProperty("DeleteState") != null) if ((typeof(T)).GetProperty("DeleteState") != null)
@ -143,11 +141,7 @@ namespace Learn.Archives.API.Controllers
{ {
if (string.IsNullOrEmpty(model.ValueName) || string.IsNullOrEmpty(model.TextName)) if (string.IsNullOrEmpty(model.ValueName) || string.IsNullOrEmpty(model.TextName))
Oh.ModelError("ValueName TextName 是必填项"); Oh.ModelError("ValueName TextName 是必填项");
List<IConditionalModel> where = [.. model.Conditions]; var sqlquery = BaseQuery(model);
var sqlquery = _baseRepository.AsQueryable().Where(where).Where("DeleteState=0");
if (!string.IsNullOrEmpty(model.OrderBy))
sqlquery = sqlquery.OrderByPropertyName(model.OrderBy, model.OrderByType);
var res = await sqlquery.Select<ComboModel>($"{model.TextName} as Text , {model.ValueName} as Value").ToListAsync(); var res = await sqlquery.Select<ComboModel>($"{model.TextName} as Text , {model.ValueName} as Value").ToListAsync();
return res; return res;
} }

View File

@ -7,6 +7,7 @@ using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Authentication.JwtBearer;
using Learn.Archives.Core.Model.Dto; using Learn.Archives.Core.Model.Dto;
using Aliyun.OSS; using Aliyun.OSS;
using NetTaste;
namespace Learn.Archives.API.Expand namespace Learn.Archives.API.Expand
{ {
@ -18,6 +19,7 @@ namespace Learn.Archives.API.Expand
.AddJwtBearer(Authentication.Admin, options => .AddJwtBearer(Authentication.Admin, options =>
{ {
options.RequireHttpsMetadata = false; options.RequireHttpsMetadata = false;
options.UseSecurityTokenValidators = true;
options.TokenValidationParameters = new TokenValidationParameters options.TokenValidationParameters = new TokenValidationParameters
{ {
SaveSigninToken = false,//保存token,后台验证token是否生效(重要) SaveSigninToken = false,//保存token,后台验证token是否生效(重要)
@ -30,6 +32,19 @@ namespace Learn.Archives.API.Expand
}; };
options.Events = new JwtBearerEvents options.Events = new JwtBearerEvents
{ {
OnMessageReceived = context =>
{
var token = context.Request.Headers["Authorization"].FirstOrDefault();
// 3. 安全提取令牌
if (!string.IsNullOrEmpty(token) && token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
// 移除"Bearer "前缀并清除两端空格
token = token.Substring("Bearer ".Length).Trim();
context.Token = token;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context => OnAuthenticationFailed = context =>
{ {
context.Response.Clear(); context.Response.Clear();
@ -41,11 +56,13 @@ namespace Learn.Archives.API.Expand
}, },
OnChallenge = context => OnChallenge = context =>
{ {
if(context.Response.StatusCode == 403 || context.Response.StatusCode == 401)
return Task.CompletedTask;
context.HandleResponse(); context.HandleResponse();
context.Response.Clear(); context.Response.Clear();
context.Response.ContentType = "application/json"; context.Response.ContentType = "application/json";
context.Response.StatusCode = 403; context.Response.StatusCode = 401;
var data = new BaseReturn() { Code = 403, Message = context.Error + context.AuthenticateFailure?.Message }; var data = new BaseReturn() { Code = 401, Message = context.Error + context.AuthenticateFailure?.Message };
context.Response.WriteAsync(data.ToJson()); context.Response.WriteAsync(data.ToJson());
return Task.CompletedTask; return Task.CompletedTask;
} }

View File

@ -10,8 +10,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.18" /> <PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.18" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.13.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.13.0" />
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.21.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.2-pre01" /> <PackageReference Include="Mapster.DependencyInjection" Version="1.0.2-pre01" />
</ItemGroup> </ItemGroup>

View File

@ -28,18 +28,7 @@ builder.Services.AddControllers(options =>
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;// 默认小驼峰 null 大驼峰 options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;// 默认小驼峰 null 大驼峰
}); });
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c => builder.Services.AddSwaggerExpand("学校档案系统");
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Description = "学校档案系统v1"
});
var file = Path.Combine(AppContext.BaseDirectory, "Learn.Archives.API.xml"); // xml文档绝对路径
c.IncludeXmlComments(file, true); // true : 显示控制器层注释
c.OrderActionsBy(o => o.RelativePath); // 对action的名称进行排序如果有多个就可以看见效果了。
});
builder.Configuration.AddAppConfig(args); builder.Configuration.AddAppConfig(args);
builder.Services.AddPermissionAuthentication(); builder.Services.AddPermissionAuthentication();
builder.Services.AddSqlSugarExpand(); builder.Services.AddSqlSugarExpand();
@ -54,12 +43,16 @@ builder.Services.AddHttpContextAccessor();
var app = builder.Build(); var app = builder.Build();
AppCommon.Services = app.Services;
app.UseMiddleware<BasicAuthMiddleware>("Swagger"); app.UseMiddleware<BasicAuthMiddleware>("Swagger");
app.UseSwagger(); app.UseSwagger();
app.UseSwaggerUI(); app.UseSwaggerUI();
app.UseRouting();
//自定义 应用 //自定义 应用
app.UseCorsExpand(); app.UseCorsExpand();
app.UseSqlSugarExpand(); app.UseSqlSugarExpand();

View File

@ -11,6 +11,7 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Reflection.PortableExecutable; using System.Reflection.PortableExecutable;
using System.Runtime.Loader; using System.Runtime.Loader;
using System.Security.Cryptography;
using System.Text; using System.Text;
using System.Text.Json; using System.Text.Json;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@ -70,12 +71,16 @@ namespace Learn.Archives.Core.Common
public static class ExpandFunction public static class ExpandFunction
{ {
static Dictionary<string, string> FormulaData;
static string FormulaDataKey;
/// <summary> public static string GetMD5(this string input)
/// 帧文件名称 {
/// </summary> using (MD5 md5 = MD5.Create())
public static string FrameName = "frame_"; {
byte[] hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(input));
return Convert.ToHexString(hashBytes).ToUpper(); // 或者保留大写
}
}
/// <summary> /// <summary>
/// 对象转化为JSON字符串 /// 对象转化为JSON字符串

View File

@ -1,6 +1,8 @@
using Microsoft.AspNetCore.Http; using Learn.Archives.Core.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using OracleInternal.Secure.Network; using OracleInternal.Secure.Network;
using SqlSugar.IOC;
using System; using System;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -12,6 +14,8 @@ namespace Learn.Archives.Core.Common
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
private readonly string _realm; private readonly string _realm;
private Repository<Admin> baseservice;
public BasicAuthMiddleware(RequestDelegate next, string realm) public BasicAuthMiddleware(RequestDelegate next, string realm)
{ {
_next = next; _next = next;
@ -20,7 +24,8 @@ namespace Learn.Archives.Core.Common
public async Task InvokeAsync(HttpContext context) public async Task InvokeAsync(HttpContext context)
{ {
if (context.Request.Path.StartsWithSegments("/swagger")) if (context.Request.Path.StartsWithSegments("/swagger")
&& (context.Request.Path.Value?.Contains("swagger.json") ?? true))
{ {
string authHeader = context.Request.Headers["Authorization"]; string authHeader = context.Request.Headers["Authorization"];
if (authHeader != null && authHeader.StartsWith("Basic ")) if (authHeader != null && authHeader.StartsWith("Basic "))
@ -31,6 +36,7 @@ namespace Learn.Archives.Core.Common
if (await IsAuthorized(usernamePassword[0], usernamePassword[1])) if (await IsAuthorized(usernamePassword[0], usernamePassword[1]))
{ {
await _next(context); await _next(context);
return; return;
} }
@ -45,9 +51,19 @@ namespace Learn.Archives.Core.Common
private async Task<bool> IsAuthorized(string username, string password) private async Task<bool> IsAuthorized(string username, string password)
{ {
// 在这里验证用户名和密码 //if (baseservice == null)
return AppCommon.Config.Admin.Account == username //{
&& AppCommon.Config.Admin.Password == password; // using var scope = AppCommon.Services?.CreateScope();
// if (scope != null)
// baseservice = scope.ServiceProvider.GetService<Repository<Admin>>();
//}
//if (baseservice == null) return false;
var admin = await DbScoped.Sugar.Queryable<Admin>()
.FirstAsync(x => x.Account == username);
if (admin == null || !admin!.Enable) return false;
else if (admin.Password != password.GetMD5()) return false;
else return true;
} }
} }
} }

View File

@ -0,0 +1,106 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Learn.Archives.Core.Common.Expand
{
public static class SwaggerExpand
{
public static void AddSwaggerExpand(this IServiceCollection s,string name="")
{
s.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Description = name
});
c.OperationFilter<SwaggerFileUploadFilter>();
//按Http类型排序
c.OrderActionsBy(o => o.GroupName);
c.DocInclusionPredicate((docName, apiDesc) =>
{
try
{
if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
var versions = methodInfo.DeclaringType.GetCustomAttributes(true)
.OfType<ApiExplorerSettingsAttribute>().Select(attr => attr.GroupName);
if (docName.ToLower() == "v1" && versions.FirstOrDefault() == null)
return true;
return versions.Any(v => v.ToString() == docName);
}
catch (Exception ex)
{
throw;
}
});
//添加全局安全性需求
c.AddSecurityRequirement(new OpenApiSecurityRequirement{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "bearerAuth"
}
}, Array.Empty<string>()
}
});
//添加一个必须的全局安全信息和AddSecurityDefinition方法指定的方案名称要一致这里是Bearer。
c.AddSecurityDefinition("bearerAuth",
new OpenApiSecurityScheme
{
Description = "使用JWT授权头。示例:\"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
//内容为以 bearer开头
Scheme = "bearer",
BearerFormat = "JWT"
});
DirectoryInfo dirs = new DirectoryInfo(AppContext.BaseDirectory);
FileInfo[] files = dirs.GetFiles("*.xml");
foreach (var path in files)
{
c.IncludeXmlComments(path.FullName);
}
});
//s.AddSwaggerGenNewtonsoftSupport();
}
}
class SwaggerFileUploadFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (context.ApiDescription.ActionDescriptor.Parameters.Any(w => w.ParameterType == typeof(IFormCollection)))
{
Dictionary<string, OpenApiSchema> schema = new Dictionary<string, OpenApiSchema>();
schema["fileName"] = new OpenApiSchema { Description = "选择上传文件", Type = "string", Format = "binary" };
Dictionary<string, OpenApiMediaType> content = new Dictionary<string, OpenApiMediaType>();
content["multipart/form-data"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", Properties = schema } };
operation.RequestBody = new OpenApiRequestBody() { Content = content };
}
}
}
}

View File

@ -33,7 +33,7 @@ namespace Learn.Archives.Core.Common
//JWT ID 唯一标识符 //JWT ID 唯一标识符
new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString() ), new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString() ),
//发布时间戳 issued timestamp //发布时间戳 issued timestamp
new Claim(JwtRegisteredClaimNames.Iat, now.ToString()), new Claim(JwtRegisteredClaimNames.Iat, DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString(), ClaimValueTypes.Integer64),
}; };
if (claims != null && claims.Length > 0) if (claims != null && claims.Length > 0)
{ {

View File

@ -32,9 +32,9 @@ namespace Learn.Archives.Core.Common
/// <summary> /// <summary>
/// 管理员角色id /// 管理员角色id
/// </summary> /// </summary>
public string? RoleId public long? RoleId
{ {
get => _httpContextAccessor.HttpContext?.User.FindFirst(ClaimEnum.Role)?.Value; get => long.Parse(_httpContextAccessor.HttpContext?.User.FindFirst(ClaimEnum.Role)?.Value ?? "0");
} }
/// <summary> /// <summary>

View File

@ -11,7 +11,6 @@ using System.Threading.Channels;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using UserCenter.Model.Enum; using UserCenter.Model.Enum;
using static System.Runtime.InteropServices.JavaScript.JSType;
namespace Learn.Archives.Core.Common namespace Learn.Archives.Core.Common
{ {

View File

@ -23,6 +23,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" /> <PackageReference Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" /> <PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="SqlSugar.IOC" Version="2.0.0" /> <PackageReference Include="SqlSugar.IOC" Version="2.0.0" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.170" /> <PackageReference Include="SqlSugarCore" Version="5.1.4.170" />
<PackageReference Include="UserCenter.Model" Version="1.3.9" /> <PackageReference Include="UserCenter.Model" Version="1.3.9" />

View File

@ -33,7 +33,7 @@ namespace Learn.Archives.Core.Model
/// <summary> /// <summary>
/// 密码 /// 密码
/// </summary> /// </summary>
[SugarColumn(Length = 12)] [SugarColumn(Length = 32)]
public string Password { get; set; } public string Password { get; set; }
/// <summary> /// <summary>
/// 启用 /// 启用

View File

@ -19,15 +19,16 @@ namespace Learn.Archives.Core.Model
/// 名称 /// 名称
/// </summary> /// </summary>
[SugarColumn(Length = 50)] [SugarColumn(Length = 50)]
public required string Name { get; set; } public string Name { get; set; }
/// <summary> /// <summary>
/// 标题 /// 标题
/// </summary> /// </summary>
[SugarColumn(Length = 20)] [SugarColumn(Length = 20)]
public required string Title { get; set; } public string Title { get; set; }
/// <summary> /// <summary>
/// 路径 /// 路径
/// </summary> /// </summary>
[SugarColumn(IsNullable = true)]
public string? Path { get; set; } public string? Path { get; set; }
/// <summary> /// <summary>
/// 是按钮权限 /// 是按钮权限
@ -36,10 +37,12 @@ namespace Learn.Archives.Core.Model
/// <summary> /// <summary>
/// 图标 /// 图标
/// </summary> /// </summary>
[SugarColumn(IsNullable = true)]
public string? Icon { get; set; } public string? Icon { get; set; }
/// <summary> /// <summary>
/// 需要的授权码 /// 需要的授权码
/// </summary> /// </summary>
[SugarColumn(IsNullable = true)]
public string? Auths { get; set; } public string? Auths { get; set; }
/// <summary> /// <summary>
/// 排名 /// 排名
@ -51,5 +54,12 @@ namespace Learn.Archives.Core.Model
/// </summary> /// </summary>
public bool ShowLink { get; set; } public bool ShowLink { get; set; }
/// <summary>
/// 父级菜单ID
/// <para>属于<see cref="Menu.Id"/></para>
/// </summary>
[SugarColumn(IsNullable = true)]
public long ParentId { get; set; }
} }
} }

View File

@ -18,11 +18,11 @@ namespace Learn.Archives.Core.Model
/// <summary> /// <summary>
/// 菜单id /// 菜单id
/// </summary> /// </summary>
public required long MenuId { get; set; } public long MenuId { get; set; }
/// <summary> /// <summary>
/// 管理员id /// 角色id
/// </summary> /// </summary>
public required long AdminId { get; set; } public long RoleId { get; set; }
/// <summary> /// <summary>
/// 创建时间 /// 创建时间