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

如何在ASP.NET Core 2.0 API+Angular 6中实现多子域名共用代码库及用户验证

解决方案:多子域名共用代码库的身份验证与子域名传递

首先,你的核心问题有两个:把解析到的子域名可靠传递到API层,以及实现用户只能在注册的子域名登录/访问。咱们一步步来解决:


一、替换自定义IRouter为中间件,全局获取子域名

你当前用SubdomainRoute实现子域名解析,但这种方式会干扰默认路由配置(比如你的SPA fallback路由可能失效)。更可靠的方式是用中间件,它能在所有请求处理前执行,不管是MVC页面还是API接口,都能拿到子域名:

1. 创建Subdomain中间件

public class SubdomainMiddleware
{
    private readonly RequestDelegate _next;
    // 定义需要忽略的子域名列表
    private readonly List<string> _ignoredSubdomains = new() { "www", "drivebuyrei", "mail" };

    public SubdomainMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var host = context.Request.Host.Host;
        if (!string.IsNullOrEmpty(host))
        {
            var index = host.IndexOf('.');
            if (index > 0)
            {
                var subdomain = host.Substring(0, index);
                // 忽略指定子域名,视为主站请求
                if (!_ignoredSubdomains.Contains(subdomain, StringComparer.OrdinalIgnoreCase))
                {
                    // 把域名存入HttpContext.Items,全局可访问
                    context.Items["Subdomain"] = subdomain;
                    // 同时添加请求头,方便Angular前端获取
                    context.Request.Headers.TryAdd("X-Subdomain", subdomain);
                }
            }
        }

        // 继续执行后续中间件
        await _next(context);
    }
}

2. 注册中间件

在Startup.cs的Configure方法中,把这个中间件放在UseMvc之前(确保路由处理前就拿到子域名):

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // ...其他中间件(比如UseStaticFiles、UseAuthentication)

    // 注册子域名中间件
    app.UseMiddleware<SubdomainMiddleware>();

    app.UseMvc(routes => {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
        routes.MapRoute(
            "Sitemap", "sitemap.xml", new { controller = "Home", action = "SitemapXml" });
        routes.MapSpaFallbackRoute(
            name: "spa-fallback",
            defaults: new { controller = "Home", action = "Index" });
    });
}

二、在API层获取并使用子域名

现在所有请求都能拿到子域名了,咱们在API控制器中使用它:

1. 创建API基类(可选,但更优雅)

[ApiController]
[Route("api/[controller]")]
public class BaseApiController : ControllerBase
{
    // 封装获取子域名的方法
    protected string GetCurrentSubdomain()
    {
        return HttpContext.Items["Subdomain"] as string ?? string.Empty;
    }
}

2. 注册/登录时关联子域名

假设你的用户表已经添加了Subdomain字段(用来存储用户注册时的子域名),在注册和登录逻辑中使用:

public class UserController : BaseApiController
{
    private readonly UserManager<ApplicationUser> _userManager;
    private readonly SignInManager<ApplicationUser> _signInManager;

    public UserController(UserManager<ApplicationUser> userManager, SignInManager<ApplicationUser> signInManager)
    {
        _userManager = userManager;
        _signInManager = signInManager;
    }

    [HttpPost("register")]
    public async Task<IActionResult> Register(RegisterViewModel model)
    {
        var subdomain = GetCurrentSubdomain();
        if (string.IsNullOrEmpty(subdomain))
        {
            return BadRequest("请从合法子域名注册");
        }

        var user = new ApplicationUser
        {
            UserName = model.Username,
            Email = model.Email,
            Subdomain = subdomain // 保存子域名到用户表
        };

        var result = await _userManager.CreateAsync(user, model.Password);
        if (result.Succeeded)
        {
            await _signInManager.SignInAsync(user, isPersistent: false);
            return Ok();
        }

        return BadRequest(result.Errors);
    }

    [HttpPost("login")]
    public async Task<IActionResult> Login(LoginViewModel model)
    {
        var subdomain = GetCurrentSubdomain();
        // 先根据用户名/邮箱找用户,再验证子域名
        var user = await _userManager.FindByNameAsync(model.Username) 
                   ?? await _userManager.FindByEmailAsync(model.Email);

        if (user == null || user.Subdomain != subdomain)
        {
            return Unauthorized("未授权:只能在注册的子域名登录");
        }

        var result = await _signInManager.CheckPasswordSignInAsync(user, model.Password, lockoutOnFailure: false);
        if (result.Succeeded)
        {
            await _signInManager.SignInAsync(user, isPersistent: model.RememberMe);
            return Ok();
        }

        return Unauthorized("用户名或密码错误");
    }
}

三、全局验证用户子域名权限

除了登录时验证,咱们还需要确保已登录用户只能访问自己注册的子域名,可以用ASP.NET Core的授权策略实现:

1. 创建授权需求和处理器

// 定义授权需求(空类,用来标记策略)
public class SubdomainAuthorizationRequirement : IAuthorizationRequirement { }

// 实现授权处理器
public class SubdomainAuthorizationHandler : AuthorizationHandler<SubdomainAuthorizationRequirement>
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly UserManager<ApplicationUser> _userManager;

    public SubdomainAuthorizationHandler(IHttpContextAccessor httpContextAccessor, UserManager<ApplicationUser> userManager)
    {
        _httpContextAccessor = httpContextAccessor;
        _userManager = userManager;
    }

    protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, SubdomainAuthorizationRequirement requirement)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        var subdomain = httpContext.Items["Subdomain"] as string;

        // 如果是忽略的子域名(比如www),直接允许访问
        if (string.IsNullOrEmpty(subdomain))
        {
            context.Succeed(requirement);
            return;
        }

        // 获取当前登录用户ID
        var userId = context.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
        if (string.IsNullOrEmpty(userId))
        {
            context.Fail();
            return;
        }

        // 验证用户的子域名是否匹配当前请求的子域名
        var user = await _userManager.FindByIdAsync(userId);
        if (user != null && user.Subdomain == subdomain)
        {
            context.Succeed(requirement);
        }
        else
        {
            context.Fail();
        }
    }
}

2. 注册授权服务

在Startup.cs的ConfigureServices中添加:

public void ConfigureServices(IServiceCollection services)
{
    // ...其他服务配置

    // 注册HttpContextAccessor(用于在授权处理器中获取请求上下文)
    services.AddHttpContextAccessor();

    // 添加授权策略
    services.AddAuthorization(options =>
    {
        options.AddPolicy("SubdomainPolicy", policy =>
            policy.Requirements.Add(new SubdomainAuthorizationRequirement()));
    });

    // 注册授权处理器
    services.AddScoped<IAuthorizationHandler, SubdomainAuthorizationHandler>();
}

3. 应用授权策略

在需要验证的API控制器或动作上添加[Authorize(Policy = "SubdomainPolicy")]:

[ApiController]
[Route("api/[controller]")]
[Authorize(Policy = "SubdomainPolicy")]
public class ProfileController : BaseApiController
{
    // 该控制器下的所有动作都会验证子域名权限
    [HttpGet("me")]
    public async Task<IActionResult> GetCurrentUser()
    {
        var user = await _userManager.GetUserAsync(User);
        return Ok(user);
    }
}

四、Angular前端获取子域名

前端可以直接解析当前域名拿到子域名,用来显示或请求时验证:

// 创建一个子域名服务
import { Injectable } from '@angular/core';

@Injectable({ providedIn: 'root' })
export class SubdomainService {
  getCurrentSubdomain(): string {
    const hostname = window.location.hostname;
    const parts = hostname.split('.');
    
    // 处理多级域名(比如abc.example.com)
    if (parts.length >= 3) {
      const subdomain = parts[0];
      const ignoredSubdomains = ['www', 'drivebuyrei', 'mail'];
      if (!ignoredSubdomains.includes(subdomain)) {
        return subdomain;
      }
    }
    
    return '';
  }
}

在组件中使用:

import { Component } from '@angular/core';
import { SubdomainService } from './subdomain.service';

@Component({
  selector: 'app-register',
  templateUrl: './register.component.html'
})
export class RegisterComponent {
  currentSubdomain: string;

  constructor(private subdomainService: SubdomainService) {
    this.currentSubdomain = subdomainService.getCurrentSubdomain();
  }
}

关键注意事项

  1. 数据库设计:必须在用户表中添加Subdomain字段(建议为字符串类型,可加索引优化查询)。
  2. Cookie共享:确保所有子域名共享身份验证Cookie,需要在Startup.cs的ConfigureServices中配置:
    services.ConfigureApplicationCookie(options =>
    {
        options.Cookie.Domain = ".example.com"; // 注意前面的点,允许所有子域名共享Cookie
        options.Cookie.Name = ".YourApp.Auth";
        // ...其他Cookie配置
    });
    
  3. HTTPS配置:生产环境确保所有子域名都配置了HTTPS,避免Cookie安全问题。

内容的提问来源于stack exchange,提问作者Dhruv Patel

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:12:58