using System.Globalization;
using OpenIddict.Abstractions;
using OpenIddict.Validation.AspNetCore;

namespace Dpz.Core.Auth.Controllers;

public class HomeController : Controller
{
    public async Task<IActionResult> Index(
        [FromServices] IHttpCurrentUserService httpCurrentUser,
        CancellationToken cancellationToken = default
    )
    {
        VmUserInfo? userInfo = null;
        if (User.Identity?.IsAuthenticated == true)
        {
            userInfo = await httpCurrentUser.GetUserInfoAsync(cancellationToken);
        }
        return this.ShellView(
            new AuthShellViewModel<EmptyAuthShellPayload>
            {
                EntryName = "public",
                PageName = "home",
                User = userInfo,
            }
        );
    }

    /// <summary>
    /// 用户信息
    /// </summary>
    /// <returns></returns>
    [Authorize(AuthenticationSchemes = OpenIddictValidationAspNetCoreDefaults.AuthenticationScheme)]
    [HttpGet("~/connect/userinfo")]
    public IActionResult GetUserInfo()
    {
        var scopes = User.FindAll("oi_scp").Select(c => c.Value);
        var claims = User
            .Claims.Where(c => !c.Type.StartsWith("oi_"))
            .ToDictionary(c => c.Type, c => c.Value);
        claims["scope"] = string.Join(" ", scopes);

        var result = claims.ToDictionary(x => x.Key, object (x) => x.Value);

        var emailVerifiedValue = User.FindFirst(OpenIddictConstants.Claims.EmailVerified)?.Value;

        if (bool.TryParse(emailVerifiedValue, out var emailVerified))
        {
            result[OpenIddictConstants.Claims.EmailVerified] = emailVerified;
        }
        var updatedAtValue = User.FindFirst(OpenIddictConstants.Claims.UpdatedAt)?.Value;

        if (
            long.TryParse(
                updatedAtValue,
                NumberStyles.Integer,
                CultureInfo.InvariantCulture,
                out var updatedAt
            )
        )
        {
            result[OpenIddictConstants.Claims.UpdatedAt] = updatedAt;
        }

        return Ok(result);
    }

    [Route("robots.txt")]
    public IActionResult Robots()
    {
        return Content("User-agent: *\nDisallow: /", "text/plain");
    }

#if DEBUG
    public IActionResult Forbidden()
    {
        Response.StatusCode = StatusCodes.Status403Forbidden;
        return new EmptyResult();
    }

    public IActionResult Error()
    {
        Response.StatusCode = StatusCodes.Status500InternalServerError;
        return new EmptyResult();
    }

#endif
}
⚠⚠    以下内容为AI分析的结果,请根据实际情况进行判断。

代码解释

这是一个基于 ASP.NET Core 的 HomeController,整合了 OpenIddict 身份验证框架,主要提供以下功能:

主要功能模块

1. Index 方法 - 首页入口

public async Task<IActionResult> Index(...)
  • 功能:应用程序主页入口
  • 逻辑流程
    • 检查用户是否已认证(User.Identity?.IsAuthenticated
    • 如果已认证,通过 IHttpCurrentUserService 异步获取用户信息
    • 返回一个 Shell 视图,包含:
      • EntryName: "public" - 入口名称
      • PageName: "home" - 页面名称
      • User: 用户信息(已认证)或 null(未认证)

2. GetUserInfo 方法 - OAuth/OIDC 用户信息端点

[Authorize(...)]
[HttpGet("~/connect/userinfo")]
public IActionResult GetUserInfo()
  • 功能:标准的 OpenID Connect UserInfo 端点
  • 安全性:需要 OpenIddict 验证令牌(Bearer Token)
  • 处理逻辑
    1. 提取作用域(Scopes):从 "oi_scp" 声明中获取所有权限范围
    2. 过滤声明(Claims)
      • 移除所有以 "oi_" 开头的内部声明(OpenIddict 内部使用)
      • 将剩余声明转换为字典
    3. 添加 scope 字段:将所有作用域用空格连接
    4. 特殊字段处理
      • email_verified:转换为布尔值
      • updated_at:转换为长整型(Unix 时间戳)
    5. 返回 JSON:包含用户所有公开信息的字典

3. Robots 方法 - SEO 爬虫控制

[Route("robots.txt")]
public IActionResult Robots()
  • 功能:返回 robots.txt 文件
  • 内容:禁止所有搜索引擎爬虫访问整个站点
  • 用途:隐私保护或避免认证页面被索引

4. 调试端点(仅 DEBUG 模式)

#if DEBUG
public IActionResult Forbidden() // 403 错误测试
public IActionResult Error()     // 500 错误测试
#endif
  • 用途:开发环境测试错误页面渲染
  • 特点:生产环境自动移除(条件编译)

技术要点

特性说明
依赖注入IHttpCurrentUserService 通过 [FromServices] 注入
取消令牌支持异步操作的优雅取消
OpenIddict 集成使用标准 OAuth 2.0 / OIDC 认证方案
Claims 处理智能过滤和类型转换(bool、long)
国际化使用 CultureInfo.InvariantCulture 确保数字解析一致性

应用场景

这是一个身份认证服务的核心控制器,典型应用于:

  • 单点登录(SSO)系统
  • OAuth 2.0 授权服务器
  • 企业级身份管理平台
评论加载中...