You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于本地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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 11:59:54