使用ASP.NET Core和IdentityServer4实现单点登录系统插图

使用ASP.NET Core和IdentityServer4实现单点登录系统:从零构建一个安全的身份认证中心

大家好,作为一名在.NET生态里摸爬滚打多年的开发者,我深知在微服务或分布式系统中,统一身份认证和授权是个绕不开的“硬骨头”。每次新开一个服务,都要重复造一遍登录的轮子,用户体验割裂,维护成本也高。直到我遇见了IdentityServer4(简称IDS4),它基于OpenID Connect和OAuth 2.0协议,堪称ASP.NET Core下实现单点登录(SSO)的“瑞士军刀”。今天,我就带大家手把手搭建一个最小可用的单点登录系统,过程中我也会分享一些实战中踩过的坑和心得。

第一步:项目准备与环境搭建

首先,我们需要创建两个核心项目:一个是作为“安全令牌服务(STS)”的IdentityServer4中心,另一个是作为“客户端”的Web应用。这里我假设你已经安装了.NET 6或更高版本的SDK。

打开终端,执行以下命令:

# 创建解决方案目录并进入
mkdir IdentityServerDemo
cd IdentityServerDemo

# 创建IdentityServer4认证中心项目(使用空模板)
dotnet new web -n IdentityServer
cd IdentityServer
# 添加IdentityServer4包(注意:由于IdentityServer4已停止对新版本.NET的主动支持,我们使用其社区维护的Duende Software版本,但为便于理解,本文仍沿用IdentityServer4概念)
dotnet add package Duende.IdentityServer
cd ..

# 创建客户端Web应用项目(使用MVC模板)
dotnet new mvc -n MvcClient
cd MvcClient
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
cd ..

踩坑提示:IdentityServer4官方已转向商业化的Duende IdentityServer,对于学习和测试,我们可以使用其免费的社区版。上述命令安装的 `Duende.IdentityServer` 包是兼容的继承者,API与IDS4高度相似。务必注意许可证变化,生产环境请仔细阅读Duende的授权条款。

第二步:配置IdentityServer4认证中心

现在,我们来武装我们的认证中心。编辑 `IdentityServer` 项目中的 `Program.cs` 文件。

首先,定义一些测试用户和客户端配置。在实际项目中,这些信息通常来自数据库。我们在 `Program.cs` 中添加:

using Duende.IdentityServer;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Test;

var builder = WebApplication.CreateBuilder(args);

// 添加IdentityServer服务,并配置内存中的测试资源和客户端
builder.Services.AddIdentityServer()
    .AddInMemoryIdentityResources(Config.IdentityResources)
    .AddInMemoryApiScopes(Config.ApiScopes)
    .AddInMemoryClients(Config.Clients)
    .AddTestUsers(TestUsers.Users) // 添加测试用户
    .AddDeveloperSigningCredential(); // 开发环境签名凭证,生产环境需使用持久化密钥

// ... 其余服务配置

var app = builder.Build();

// 中间件配置
app.UseStaticFiles();
app.UseRouting();
// 启用IdentityServer中间件
app.UseIdentityServer();
app.UseAuthorization();

app.MapGet("/", () => "IdentityServer is running!");

app.Run();

接下来,在项目根目录创建一个 `Config.cs` 文件,用于存放配置数据:

namespace IdentityServer;

public static class Config
{
    // OpenID Connect 标准身份资源
    public static IEnumerable IdentityResources =>
        new[]
        {
            new IdentityResources.OpenId(), // 必须的openid
            new IdentityResources.Profile(), // 用户基本信息(如姓名)
        };

    // API作用域
    public static IEnumerable ApiScopes =>
        new[] { new ApiScope("api1", "My API") };

    // 客户端定义(这里配置我们的MvcClient)
    public static IEnumerable Clients =>
        new[]
        {
            new Client
            {
                ClientId = "mvc_client",
                ClientSecrets = { new Secret("secret".Sha256()) }, // 客户端密码
                AllowedGrantTypes = GrantTypes.Code, // 授权码模式,最安全
                RedirectUris = { "https://localhost:5002/signin-oidc" }, // 登录后回调地址
                PostLogoutRedirectUris = { "https://localhost:5002/signout-callback-oidc" }, // 登出后回调地址
                AllowedScopes = new List
                {
                    IdentityServerConstants.StandardScopes.OpenId,
                    IdentityServerConstants.StandardScopes.Profile,
                    "api1"
                },
                RequirePkce = true, // 启用PKCE增强安全性
                AllowOfflineAccess = true // 允许刷新令牌
            }
        };
}

// 测试用户
public static class TestUsers
{
    public static List Users => new()
    {
        new TestUser
        {
            SubjectId = "1",
            Username = "alice",
            Password = "alice",
            Claims = new[]
            {
                new System.Security.Claims.Claim("name", "Alice"),
                new System.Security.Claims.Claim("website", "https://alice.com")
            }
        }
    };
}

实战经验:`RedirectUris` 和 `PostLogoutRedirectUris` 必须与客户端应用的地址完全匹配,包括端口,否则会报“无效的重定向URI”错误,这是我初学时最常遇到的坑之一。

第三步:配置客户端(MVC应用)以使用单点登录

现在切换到 `MvcClient` 项目。我们需要让它信任并连接到我们的认证中心。

首先,修改 `MvcClient` 的 `appsettings.json`,添加IdentityServer配置:

{
  "Authentication": {
    "Authority": "https://localhost:5001", // IdentityServer地址
    "ClientId": "mvc_client",
    "ClientSecret": "secret"
  },
  // ... 其他配置
}

然后,编辑 `Program.cs`,添加认证服务:

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

var builder = WebApplication.CreateBuilder(args);

// 添加认证服务
builder.Services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; // Cookie作为默认方案处理本地登录
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; // 当需要认证时,跳转到IDS4
    })
    .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) // 添加Cookie处理程序
    .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme, options => // 添加OpenID Connect处理程序
    {
        options.Authority = builder.Configuration["Authentication:Authority"];
        options.ClientId = builder.Configuration["Authentication:ClientId"];
        options.ClientSecret = builder.Configuration["Authentication:ClientSecret"];
        options.ResponseType = "code"; // 使用授权码模式
        options.SaveTokens = true; // 在Cookie中保存从IDS4返回的令牌
        // 配置作用域
        options.Scope.Clear();
        options.Scope.Add("openid");
        options.Scope.Add("profile");
        options.Scope.Add("api1");
        // 获取用户信息的声明
        options.GetClaimsFromUserInfoEndpoint = true;
    });

// 添加授权服务
builder.Services.AddAuthorization();

// 将MVC服务改为使用控制器和视图
builder.Services.AddControllersWithViews();

var app = builder.Build();

// ... 配置HTTP请求管道
app.UseStaticFiles();
app.UseRouting();
// **重要顺序**:先认证,再授权
app.UseAuthentication();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

最后,我们修改一下 `HomeController` 的 `Index` 视图,显示登录状态和用户声明。在 `Views/Home/Index.cshtml` 中简单添加:

@using Microsoft.AspNetCore.Authentication
@{
    ViewData["Title"] = "Home Page";
}

Claims

@foreach (var claim in User.Claims) {
@claim.Type
@claim.Value
}

Tokens

@if (await Context.GetTokenAsync("access_token") != null) {
access_token
@(await Context.GetTokenAsync("access_token"))
}

第四步:运行与测试

激动人心的时刻到了!我们需要同时运行两个项目。你可以使用多个终端窗口,或者配置IDE的多启动项目。这里我用命令行演示:

# 第一个终端,启动IdentityServer (在IdentityServer目录下)
dotnet run --urls="https://localhost:5001"

# 第二个终端,启动MvcClient (在MvcClient目录下)
dotnet run --urls="https://localhost:5002"

打开浏览器,访问 https://localhost:5002。此时,应用会检测到你未登录,自动跳转到IdentityServer的登录页面(https://localhost:5001)。

使用我们配置的测试用户:用户名 alice,密码 alice 登录。登录成功后,你会被重定向回MVC客户端,并且页面上会显示你的用户信息(Claims)和访问令牌(Access Token)。这标志着单点登录的核心流程已经打通!

踩坑提示:如果遇到HTTPS证书错误(尤其在Windows上),可以在项目属性中信任开发证书,或者临时在 `Program.cs` 的 `builder` 构建后添加 `builder.WebHost.UseSetting(“https_port”, “5001”)` 并暂时使用HTTP(仅限开发环境)。但为了符合OAuth 2.0安全最佳实践,强烈建议始终使用HTTPS。

总结与展望

至此,我们已经成功搭建了一个最基本的单点登录系统。你可能会觉得,这离“生产可用”还有距离。没错,我们还需要考虑:

  1. 持久化存储:将客户端、资源、用户数据从内存移到数据库(如使用 `AddConfigurationStore` 和 `AddOperationalStore`)。
  2. 正式密钥管理:替换 `AddDeveloperSigningCredential`,使用X.509证书或RSA密钥。
  3. 用户管理界面:集成ASP.NET Core Identity,提供用户注册、密码找回等功能。
  4. 更多客户端类型:配置SPA(如Vue、React)、移动App或后端服务间的通信。

IdentityServer4(及其后继者Duende IdentityServer)的功能非常强大,本文只是揭开了冰山一角。它妥善处理了令牌生命周期、刷新、撤销以及复杂的联合登录场景。希望这篇教程能为你打开单点登录和身份认证领域的大门,让你在构建现代分布式应用时,多一份从容和自信。记住,安全无小事,每一步配置都值得仔细推敲。Happy coding!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。