.NET Core 中 JWT + OAuth2.0 实现 SSO(附完整源码.NET6)

在现代分布式系统中,单点登录(SSO)是一个非常重要的功能。它允许用户在多个相关联的应用程序中只需登录一次,就可以访问所有相互信任的应用系统。.NET Core 提供了丰富的工具和框架来实现这一功能,本文将详细介绍如何使用 JWT(JSON Web Token)和 OAuth2.0 在.NET6 中实现 SSO,并附上完整的源码。

目录#

  1. JWT 简介
  2. OAuth2.0 简介
  3. SSO 实现原理
  4. 创建认证服务器(.NET6)
  5. 创建资源服务器(.NET6)
  6. 客户端集成(示例)
  7. 常见实践与最佳实践
  8. 完整源码
  9. 参考

1. JWT 简介#

JWT(JSON Web Token)是一种用于在网络应用中安全传输信息的开放标准(RFC 7519)。它由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。

  • 头部:通常包含令牌的类型(如 JWT)和所使用的签名算法(如 HMAC SHA256 或 RSA)。
  • 载荷:包含声明(Claims),即关于实体(通常是用户)和其他数据的陈述。
  • 签名:用于验证消息在传输过程中没有被更改,并且对于使用私钥签名的令牌,还可以验证 JWT 的发送者是否为它所称的发送者。

示例#

var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("your_secret_key");
var tokenDescriptor = new SecurityTokenDescriptor
{
    Subject = new ClaimsIdentity(new[]
    {
        new Claim(ClaimTypes.Name, "user1")
    }),
    Expires = DateTime.UtcNow.AddHours(1),
    SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);

2. OAuth2.0 简介#

OAuth2.0 是一个授权框架,它允许用户授权第三方应用访问他们存储在另一个服务提供者上的信息,而不需要将用户名和密码提供给第三方应用。OAuth2.0 定义了四种授权类型:

  • 授权码(Authorization Code):最常用的授权类型,适用于有后端的 Web 应用。
  • 隐式(Implicit):适用于没有后端的 Web 应用(如单页应用)。
  • 密码(Resource Owner Password Credentials):用户将自己的用户名和密码直接提供给客户端,客户端使用这些信息获取访问令牌。
  • 客户端凭证(Client Credentials):客户端使用自己的凭证(如客户端 ID 和客户端密钥)获取访问令牌,适用于客户端本身就是资源所有者的情况。

3. SSO 实现原理#

SSO 基于 OAuth2.0 和 JWT 实现的原理如下:

  1. 用户访问客户端应用。
  2. 客户端应用重定向用户到认证服务器进行登录。
  3. 用户在认证服务器登录成功后,认证服务器生成 JWT 并返回给客户端应用。
  4. 客户端应用将 JWT 存储(如在 Cookie 或本地存储中)。
  5. 当客户端应用访问资源服务器时,将 JWT 作为请求头(如 Authorization: Bearer <token>)发送给资源服务器。
  6. 资源服务器验证 JWT 的有效性(如签名、过期时间等),如果验证通过,则返回资源给客户端应用。

4. 创建认证服务器(.NET6)#

步骤#

  1. 创建一个新的.NET6 Web API 项目。
  2. 安装 Microsoft.AspNetCore.Authentication.JwtBearerMicrosoft.AspNetCore.Authentication.OAuth 包。
  3. 配置认证服务:
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
    options.TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = true,
        ValidateAudience = true,
        ValidateLifetime = true,
        ValidateIssuerSigningKey = true,
        ValidIssuer = "https://your_auth_server",
        ValidAudience = "https://your_resource_server",
        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your_secret_key"))
    };
});
  1. 创建登录接口:
[HttpPost("login")]
public IActionResult Login([FromBody] LoginModel model)
{
    if (model.Username == "user1" && model.Password == "password1")
    {
        var tokenHandler = new JwtSecurityTokenHandler();
        var key = Encoding.ASCII.GetBytes("your_secret_key");
        var tokenDescriptor = new SecurityTokenDescriptor
        {
            Subject = new ClaimsIdentity(new[]
            {
                new Claim(ClaimTypes.Name, model.Username)
            }),
            Expires = DateTime.UtcNow.AddHours(1),
            SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
        };
        var token = tokenHandler.CreateToken(tokenDescriptor);
        var tokenString = tokenHandler.WriteToken(token);
        return Ok(new { Token = tokenString });
    }
    return Unauthorized();
}

5. 创建资源服务器(.NET6)#

步骤#

  1. 创建一个新的.NET6 Web API 项目。
  2. 安装 Microsoft.AspNetCore.Authentication.JwtBearer 包。
  3. 配置认证服务:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = "https://your_auth_server",
            ValidAudience = "https://your_resource_server",
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("your_secret_key"))
        };
    });
  1. 创建受保护的资源接口:
[HttpGet("protected-resource")]
[Authorize]
public IActionResult GetProtectedResource()
{
    return Ok("This is a protected resource");
}

6. 客户端集成(示例)#

步骤#

  1. 在客户端应用(如 Blazor WebAssembly 或 JavaScript 应用)中,使用 fetch 或其他 HTTP 客户端库调用认证服务器的登录接口获取 JWT。
  2. 将 JWT 存储在 Cookie 或本地存储中。
  3. 在后续请求资源服务器时,将 JWT 作为请求头发送:
fetch('https://your_resource_server/protected-resource', {
    headers: {
        'Authorization': `Bearer ${jwtToken}`
    }
})
.then(response => response.json())
.then(data => console.log(data));

7. 常见实践与最佳实践#

  • 安全存储 JWT:在客户端,避免将 JWT 存储在本地存储中(因为本地存储容易受到 XSS 攻击),推荐使用 HttpOnly Cookie。
  • 定期轮换密钥:定期更换 JWT 的签名密钥,以提高安全性。
  • 验证令牌:在资源服务器上严格验证 JWT 的所有参数(如签名、过期时间、颁发者等)。
  • 日志记录:记录认证和授权相关的操作,以便审计和故障排除。

8. 完整源码#

GitHub 仓库链接(请替换为实际的仓库链接)

9. 参考#