基于本地Windows AD认证的登录页Swagger 404错误求助
问题描述
我正在开发一个采用本地Windows AD认证的登录页面,技术栈为.NET 8 WebApi搭配React前端。目前运行后能正常显示登录页和Swagger UI,但调用API接口时出现404错误,怀疑问题出在Program.cs的配置中,但无法定位具体位置,恳请提供解决建议。
相关代码
Program.cs
using Microsoft.AspNetCore.Authentication.BearerToken; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.EntityFrameworkCore; using MyApp.Server.Data; using MyApp.Server.Models; using System.Security.Claims; namespace MyApp.Server { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.LoginPath = "/Login"; });; var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); builder.Services.AddDbContext<Context>(options => options.UseMySQL(configuration.GetConnectionString("DefaultConnection"))); builder.Services.AddCors(options => { options.AddDefaultPolicy( builder => { builder.WithOrigins("https://localhost:5173/"); }); }); // Add services to the container. builder.Services.AddControllers(config => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); config.Filters.Add(new AuthorizeFilter(policy)); }); builder.Services.AddEndpointsApiExplorer(); builder.Services.AddSwaggerGen(); builder.Services.AddControllersWithViews(); builder.Services.AddRazorPages(); var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseExceptionHandler("/Home/Error"); app.UseHsts(); app.UseHttpsRedirection(); app.UseCors(); app.UseStaticFiles(); app.UseCookiePolicy(); app.UseAuthentication(); app.UseRouting(); app.MapControllers(); app.UseHttpsRedirection(); app.UseAuthorization(); app.MapControllers(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}").RequireAuthorization(); app.MapControllerRoute( name: "api", pattern: "{controller=api}/{action=Index}/{id?}").RequireAuthorization(); app.MapFallbackToFile("/index.html"); app.Run(); } } }
LoginController
using System.DirectoryServices.AccountManagement; using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; using MyApp.Server.Models; namespace MyApp.Server.Controllers { [ApiController] [Route("[controller]")] public class LoginController : Controller { [HttpPost] [Route("Index")] [ValidateAntiForgeryToken] public async Task<IActionResult> Index(LoginModel model) { try { using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, "CompanyDomain")) { if (pc.ValidateCredentials(model.userName, model.password)) { var up = UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, model.userName); var claims = new List<Claim> { new Claim(ClaimTypes.Name, up.Name) }; claims.Add(new Claim(ClaimTypes.NameIdentifier, model.userName)); foreach (var ag in up.GetAuthorizationGroups()) claims.Add(new Claim(ClaimTypes.Role, ag.Name)); var userIdentity = new ClaimsIdentity(claims, "login"); ClaimsPrincipal cp = new ClaimsPrincipal(userIdentity); await HttpContext.SignInAsync(cp); return Ok("Success!"); } } } catch { return BadRequest("Failed!"); } return BadRequest("Failed!"); } } }
Login.tsx
import React, { useState } from "react"; function Login() { // state variable for user name and passwords const [userName, setUserName] = useState<string>(""); const [password, setPassword] = useState<string>(""); //state variable for error messages const [error, setError] = useState<string>(""); /*const navigate = useNavigate();*/ // handle change events for input fields const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { name, value } = e.target; if (name === "userName") setUserName(value); if (name === "password") setPassword(value); }; // handle submit event for the form const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => { e.preventDefault(); // validate user name and password if (!userName || !password) { setError("Please fill in all fields."); } else { // clear error message setError(""); var loginurl = ""; loginurl = "/Login/Index"; fetch(loginurl, { method: "POST", headers: { "content-type": "Application/json", "Accept": "application/json", }, body: JSON.stringify({ userName: userName, password: password, }), }) .then((data) => { // handle success or error from the server console.log(data); if (data.ok) { setError("Successful login."); } else setError("Error logging in."); }) .catch((error) => { // handle network error console.error(error); setError("Error logging in."); }); } } return ( <div className="containerbox"> <h3>Login</h3> <form onSubmit={handleSubmit}> <div> <label className="forminput" htmlFor="userName">User Name:</label> </div> <div> <input type="name" id="userName" name="userName" value={userName} onChange={handleChange} /> </div> <div> <input type="password" id="password" name="password" value={password} onChange={handleChange} /> </div> <div> <button type="submit">Login</button> </div> </form> {error && <p className="error">{error}</p>} </div> ); } export default Login;
Login Model
using System.ComponentModel.DataAnnotations; namespace MyApp.Server.Models { public class LoginModel { [Required(ErrorMessage = "Please enter your username.")] [Display(Name = "Username")] public string userName { get; set; } [Required(ErrorMessage = "Please enter your password.")] [DataType(DataType.Password)] [Display(Name = "Password")] public string password { get; set; } } }
appsetting.json
{ "Logging": { "LogLevel": { "Default": "Information", "Microsoft.AspNetCore": "Warning" } }, "ConnectionStrings": { "DefaultConnection": "Data Source=dbname;Initial Catalog=dbcatalog; user=#; password=#" }, }
问题定位与解决建议
1. 路由与中间件顺序混乱
- 重复映射控制器:Program.cs中两次调用
app.MapControllers(),导致路由冲突。 - 中间件顺序错误:
UseAuthorization()必须放在UseAuthentication()之后、MapControllers()之前,否则授权逻辑无法生效;同时重复调用UseHttpsRedirection()会造成不必要的重定向。 - 错误的API路由模板:自定义的
api路由{controller=api}/{action=Index}/{id?}会干扰默认API路由解析,应该移除。
修复后的中间件顺序示例:
app.UseDefaultFiles(); app.UseStaticFiles(); if (app.Environment.IsDevelopment()) { app.UseSwagger(); app.UseSwaggerUI(); } app.UseExceptionHandler("/Home/Error"); app.UseHsts(); app.UseHttpsRedirection(); app.UseCors(); app.UseCookiePolicy(); app.UseAuthentication(); app.UseAuthorization(); // 仅保留一次控制器映射 app.MapControllers(); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}").RequireAuthorization(); app.MapFallbackToFile("/index.html");
2. CORS配置错误
- 原配置中
WithOrigins的地址多了末尾斜杠,应该改为"https://localhost:5173";同时因为使用Cookie认证,需要添加AllowCredentials()允许携带凭证:
builder.Services.AddCors(options => { options.AddDefaultPolicy( builder => { builder.WithOrigins("https://localhost:5173") .AllowAnyHeader() .AllowAnyMethod() .AllowCredentials(); }); });
3. 防伪造令牌限制
- LoginController的
Index方法添加了[ValidateAntiForgeryToken],但前端fetch请求未携带该令牌,会导致请求失败。前后端分离场景下可暂时注释该特性测试,后续再配置令牌传递逻辑:
[HttpPost] [Route("Index")] // [ValidateAntiForgeryToken] // 先注释测试 public async Task<IActionResult> Index(LoginModel model)
4. 配置文件加载冗余
- Program.cs中手动创建
ConfigurationBuilder属于冗余操作,builder.Configuration已自动加载appsettings.json,可直接使用:
// 移除冗余代码 // var configuration = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build(); builder.Services.AddDbContext<Context>(options => options.UseMySQL(builder.Configuration.GetConnectionString("DefaultConnection")));
内容的提问来源于stack exchange,提问作者JavaFox
相关产品推荐
相关产品推荐

