VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > 编程开发 > c#编程 >
  • C#网络编程之身份认证(JWT、OAuth2、OpenID Connect)

身份认证:JWT、OAuth2、OpenID Connect深度解析
18.9.1 引言:身份认证的核心价值
在Web应用、API服务中,身份认证是保护敏感资源的第一道防线——它vb.net教程C#教程python教程SQL教程access 2010教程解决了“你是谁”的问题。随着分布式系统、微服务的普及,传统的Session-Cookie认证(依赖服务器存储会话状态)已无法满足跨域、跨服务的需求。JWT、OAuth2、OpenID Connect应运而生,成为现代身份认证体系的核心技术:
JWT:轻量级的令牌格式,实现无状态认证;
OAuth2:授权框架,解决“第三方应用如何安全访问用户资源”的问题;
OpenID Connect:基于OAuth2的身份认证协议,实现单点登录(SSO)。
本章将从概念、代码实践、底层原理三个维度,逐一解析这三项技术。
18.9.2 JWT:无状态身份认证令牌

  1. 什么是JWT?
    JWT(JSON Web Token)是一种自包含、无状态的身份认证令牌,通过JSON格式存储用户身份信息,无需服务器存储会话状态。令牌由三部分组成,用.分隔:
    Header:令牌类型和签名算法(如{"alg":"HS256","typ":"JWT"});
    Payload:用户身份信息(如用户ID、角色)和元数据(如过期时间),称为Claims;
    Signature:用Header指定的算法,将Header+Payload用密钥签名,防止令牌被篡改。
    JWT的核心优势:
    无状态:服务器无需存储会话,降低分布式系统的复杂度;
    跨域:令牌通过HTTP Header传输,支持跨域、跨服务认证;
    自包含:令牌本身包含用户信息,无需额外查询数据库。
  2. 代码实践:JWT的生成与验证(C#)
    使用.NET官方库System.IdentityModel.Tokens.Jwt实现JWT的生成与验证,代码逐行讲解。
    步骤1:安装NuGet包
    bash
    Install-Package System.IdentityModel.Tokens.Jwt
    Install-Package Microsoft.IdentityModel.Tokens
    步骤2:生成JWT令牌
    csharp
	using System; 
	using System.IdentityModel.Tokens.Jwt; 
	using System.Security.Claims; 
	using Microsoft.IdentityModel.Tokens; 
	
	class JwtGenerator 
	{ 
	// 密钥:必须保密,生产环境应从配置文件读取,避免硬编码 
	private const string SecretKey = "YourSuperSecretKey@321"; // 长度≥16字节(HS256要求) 
	
	static void Main() 
	{ 
	// 1. 配置签名密钥:使用HMAC-SHA256算法 
	var securityKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(SecretKey)); 
	var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256); 
	
	// 2. 设置Claims(用户身份信息) 
	var claims = new[] 
	{ 
	new Claim(ClaimTypes.NameIdentifier, "123"), // 用户ID(标准Claim类型) 
	new Claim(ClaimTypes.Name, "张三"), // 用户名 
	new Claim(ClaimTypes.Role, "Admin"), // 用户角色 
	new Claim("CustomClaim", "CustomValue") // 自定义Claim 
	}; 
	
	// 3. 配置JWT令牌参数 
	var token = new JwtSecurityToken( 
	issuer: "https://your-auth-server.com", // 令牌颁发者(可选) 
	audience: "https://your-resource-server.com", // 令牌受众(可选) 
	claims: claims, // 身份信息 
	expires: DateTime.UtcNow.AddHours(1), // 过期时间(UTC时间,避免时区问题) 
	signingCredentials: credentials // 签名凭证 
	); 
	
	// 4. 生成JWT字符串 
	var tokenString = new JwtSecurityTokenHandler().WriteToken(token); 
	Console.WriteLine("生成的JWT令牌:"); 
	Console.WriteLine(tokenString); 
	} 
	}

逐行讲解:
1.SymmetricSecurityKey:将字符串密钥转换为字节数组,用于HMAC-SHA256签名(对称加密算法,密钥需在服务器和客户端共享);
2.Claims:存储用户身份信息,分为标准Claim(如NameIdentifier、Role)和自定义Claim(如CustomClaim);
3.JwtSecurityToken:配置令牌的核心参数:
oissuer:令牌颁发者,验证时可校验是否来自信任的服务器;
oaudience:令牌受众,验证时可校验是否允许访问当前资源服务器;
oexpires:过期时间,必须设置为UTC时间,避免时区差异导致的过期错误;
4.JwtSecurityTokenHandler().WriteToken(token):将JwtSecurityToken转换为字符串格式的JWT令牌。
生成的JWT令牌示例:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6IjEyMyIsIm5iZiI6MTcxOTg0MjQwMCwiZXhwIjoxNzE5ODQ2MDAwLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjUwMDAiLCJhdWQiOiJodHRwOi8vbG9jYWxob3N0OjUwMDEifQ.7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X7Z8X
步骤3:验证JWT令牌
csharp

	using System; 
	using System.IdentityModel.Tokens.Jwt; 
	using Microsoft.IdentityModel.Tokens; 
	
	class JwtValidator 
	{ 
	private const string SecretKey = "YourSuperSecretKey@321"; // 与生成时的密钥一致 
	
	static void Main(string[] args) 
	{ 
	string tokenString = args[0]; // 传入生成的JWT令牌 
	var tokenHandler = new JwtSecurityTokenHandler(); 
	
	try 
	{ 
	// 1. 配置验证参数 
	var validationParameters = new TokenValidationParameters 
	{ 
	ValidateIssuer = true, // 验证颁发者 
	ValidIssuer = "https://your-auth-server.com", // 信任的颁发者 
	ValidateAudience = true, // 验证受众 
	ValidAudience = "https://your-resource-server.com", // 信任的受众 
	ValidateIssuerSigningKey = true, // 验证签名密钥 
	IssuerSigningKey = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(SecretKey)), // 签名密钥 
	ValidateLifetime = true, // 验证过期时间 
	ClockSkew = TimeSpan.Zero // 允许的时间偏差(生产环境可设为5分钟,避免服务器时间差异) 
	}; 
	
	// 2. 验证令牌并解析Claims 
	SecurityToken validatedToken; 
	var claimsPrincipal = tokenHandler.ValidateToken(tokenString, validationParameters, out validatedToken); 
	
	// 3. 解析用户信息 
	var userId = claimsPrincipal.FindFirst(ClaimTypes.NameIdentifier)?.Value; 
	var userName = claimsPrincipal.FindFirst(ClaimTypes.Name)?.Value; 
	var role = claimsPrincipal.FindFirst(ClaimTypes.Role)?.Value; 
	
	Console.WriteLine("令牌验证成功:"); 
	Console.WriteLine($"用户ID:{userId}"); 
	Console.WriteLine($"用户名:{userName}"); 
	Console.WriteLine($"角色:{role}"); 
	} 
	catch (SecurityTokenExpiredException) 
	{ 
	Console.WriteLine("令牌已过期"); 
	} 
	catch (SecurityTokenInvalidSignatureException) 
	{ 
	Console.WriteLine("令牌签名无效(可能被篡改)"); 
	} 
	catch (Exception ex) 
	{ 
	Console.WriteLine($"令牌验证失败:{ex.Message}"); 
	} 
	} 
	}

逐行讲解:
1.TokenValidationParameters:配置验证规则,核心是验证签名(确保令牌未被篡改)和验证过期时间(确保令牌有效);
2.tokenHandler.ValidateToken():验证令牌的合法性,若验证通过则返回ClaimsPrincipal(包含用户身份信息);
3.claimsPrincipal.FindFirst():从验证后的Claims中提取用户信息,无需查询数据库(JWT自包含特性)。
3. 基础知识拓展
JWT的优缺点

优点 缺点
无状态,服务器无需存储会话 无法主动吊销令牌(除非服务器维护黑名单)
跨域、跨服务支持好 令牌长度较长(包含用户信息),增加网络传输开销
自包含,减少数据库查询 敏感信息不能放在Payload中(Base64编码可解码,非加密)

安全注意事项
HTTPS传输:JWT令牌通过HTTP Header传输,必须使用HTTPS防止令牌被窃听;
短有效期:设置较短的过期时间(如1小时),减少令牌泄露的风险;
签名密钥保密:对称加密的密钥必须严格保密,生产环境应使用非对称加密(如RS256,私钥签名,公钥验证);
避免敏感信息:Payload仅存储非敏感信息(如用户ID、角色),密码、手机号等敏感信息不能放在JWT中。
18.9.3 OAuth2:授权框架(解决“第三方应用如何访问用户资源”)

  1. 什么是OAuth2?
    OAuth2(Open Authorization 2.0)是一种授权框架,而非身份认证协议。它解决了“第三方应用如何在不获取用户密码的情况下,安全访问用户资源”的问题。例如:
    微信小程序请求获取用户的微信头像(用户资源);
    GitHub授权第三方应用访问用户的代码仓库。
    OAuth2的核心角色
    资源所有者:用户,拥有资源的所有权;
    客户端:第三方应用,请求访问用户资源;
    授权服务器:颁发授权令牌的服务器(如微信授权服务器);
    资源服务器:存储用户资源的服务器(如微信的用户头像服务器)。
    OAuth2的四种授权模式
    | 模式 | 适用场景 | 流程复杂度 | 安全性 |
    | ---- | ---- | ---- | ---- |
    | 授权码模式 | Web应用、移动应用 | 高 | 最高 |
    | 密码模式 | 信任的客户端(如内部系统) | 低 | 低(需用户提供密码) |
    | 客户端凭证模式 | 服务间通信(无用户参与) | 低 | 高 |
    | 隐式模式 | 纯前端应用(无后端) | 中 | 中(令牌暴露在前端) |

  2. 核心实践:授权码模式(最常用)
    授权码模式是OAuth2最安全、最常用的模式,流程如下:
    1.用户访问客户端,客户端重定向用户到授权服务器的授权页面;
    2.用户登录并授权客户端访问资源;
    3.授权服务器返回授权码给客户端;
    4.客户端使用授权码向授权服务器请求访问令牌(Access Token);
    5.授权服务器返回Access Token给客户端;
    6.客户端使用Access Token向资源服务器请求用户资源。
    代码示例:ASP.NET Core配置OAuth2客户端
    以下示例展示如何在ASP.NET Core Web应用中配置OAuth2客户端,使用GitHub作为授权服务器:
    csharp

	// Program.cs 
	using Microsoft.AspNetCore.Authentication.OAuth; 
	using Microsoft.AspNetCore.Authentication.Cookies; 
	
	var builder = WebApplication.CreateBuilder(args); 
	
	// 1. 配置Cookie认证(存储用户会话) 
	builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) 
	.AddCookie() 
	// 2. 配置OAuth2客户端(GitHub) 
	.AddOAuth("GitHub", options => 
	{ 
	// 客户端ID和密钥(从GitHub开发者平台获取) 
	options.ClientId = builder.Configuration["GitHub:ClientId"]; 
	options.ClientSecret = builder.Configuration["GitHub:ClientSecret"]; 
	
	// 授权服务器端点 
	options.CallbackPath = "/signin-github"; // 授权成功后的回调地址 
	options.AuthorizationEndpoint = "https://github.com/login/oauth/authorize"; 
	options.TokenEndpoint = "https://github.com/login/oauth/access_token"; 
	options.UserInformationEndpoint = "https://api.github.com/user"; // 获取用户信息的端点 
	
	// 3. 配置授权范围(请求访问的资源) 
	options.Scope.Add("user:email"); // 请求访问用户邮箱 
	
	// 4. 从UserInfo端点解析用户信息 
	options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "id"); 
	options.ClaimActions.MapJsonKey(ClaimTypes.Name, "login"); 
	options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email"); 
	
	// 5. 验证令牌(可选,GitHub返回的Access Token无需签名验证) 
	options.Events = new OAuthEvents 
	{ 
	OnCreatingTicket = async context => 
	{ 
	// 请求UserInfo端点获取用户信息 
	var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint); 
	request.Headers.Accept.Add(new System.Net.Http.Headers.MediaTypeWithQualityHeaderValue("application/json")); 
	request.Headers.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", context.AccessToken); 
	
	var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted); 
	response.EnsureSuccessStatusCode(); 
	
	var user = await response.Content.ReadFromJsonAsync<Dictionary<string, object>>(); 
	context.RunClaimActions(user); // 解析用户信息到Claims 
	} 
	}; 
	}); 
	
	var app = builder.Build(); 
	
	app.UseAuthentication(); // 启用认证中间件 
	app.UseAuthorization(); // 启用授权中间件 
	
	app.MapGet("/login", async context => 
	{ 
	// 重定向到GitHub授权页面 
	await context.ChallengeAsync("GitHub", new AuthenticationProperties { RedirectUri = "/" }); 
	}); 
	
	app.MapGet("/", (ClaimsPrincipal user) => 
	{ 
	if (user.Identity.IsAuthenticated) 
	{ 
	return Results.Ok(new 
	{ 
	UserId = user.FindFirst(ClaimTypes.NameIdentifier)?.Value, 
	UserName = user.FindFirst(ClaimTypes.Name)?.Value, 
	Email = user.FindFirst(ClaimTypes.Email)?.Value 
	}); 
	} 
	return Results.Redirect("/login"); 
	}); 
	
	app.Run();

逐行讲解:
1.AddAuthentication().AddCookie():配置Cookie认证,用于存储用户登录后的会话状态;
2.AddOAuth("GitHub"):配置GitHub作为OAuth2授权服务器,核心参数:
oClientId/ClientSecret:从GitHub开发者平台注册应用后获取;
oCallbackPath:授权成功后,授权服务器重定向到客户端的地址;
oScope:请求访问的资源范围(如user:email表示请求访问用户邮箱);
3.OnCreatingTicket:事件回调,在获取Access Token后,请求GitHub的UserInfo端点获取用户信息,并解析到Claims中;
4.context.ChallengeAsync("GitHub"):触发OAuth2授权流程,重定向用户到GitHub的授权页面。
3. 基础知识拓展
OAuth2与身份认证的区别
OAuth2是授权框架,解决“第三方应用如何访问用户资源”的问题,不直接提供身份认证功能(无法确认用户是谁)。例如:
OAuth2的Access Token仅用于访问资源服务器的API,不包含用户身份信息;
若需要身份认证,需结合JWT或OpenID Connect。
四种授权模式的适用场景
授权码模式:Web应用、移动应用(最安全,授权码通过后端传输,避免令牌暴露在前端);
密码模式:内部系统(如公司OA系统,用户信任客户端,可直接输入密码);
客户端凭证模式:服务间通信(如微服务A请求微服务B的API,无用户参与);
隐式模式:纯前端应用(如Vue/React单页应用,无后端,令牌直接返回给前端)。
18.9.4 OpenID Connect:基于OAuth2的身份认证协议(解决“单点登录”)

  1. 什么是OpenID Connect?
    OpenID Connect(OIDC)是基于OAuth2的身份认证协议,它在OAuth2的基础上增加了身份认证的功能,解决了“第三方应用如何确认用户身份”的问题。OIDC的核心是ID Token(包含用户身份信息的JWT令牌),实现了单点登录(SSO):用户登录一次,即可访问多个关联应用。
    OIDC的核心组件
    ID Token:JWT格式的身份令牌,包含用户身份信息(如用户ID、姓名、邮箱);
    Access Token:OAuth2的访问令牌,用于访问资源服务器的API;
    Refresh Token:用于刷新Access Token和ID Token;
    UserInfo端点:用于获取更详细的用户信息(如头像、手机号)。
  2. 核心实践:OIDC授权码模式
    OIDC的授权流程基于OAuth2的授权码模式,增加了获取ID Token的步骤:
    1.用户访问客户端,客户端重定向用户到OIDC授权服务器的授权页面;
    2.用户登录并授权客户端;
    3.授权服务器返回授权码给客户端;
    4.客户端使用授权码请求ID Token、Access Token、Refresh Token;
    5.客户端验证ID Token的合法性,确认用户身份;
    6.客户端使用Access Token访问资源服务器的API。
    代码示例:ASP.NET Core配置OIDC客户端
    以下示例展示如何在ASP.NET Core Web应用中配置OIDC客户端,使用IdentityServer4作为授权服务器:
    csharp
	// Program.cs 
	using Microsoft.AspNetCore.Authentication.OpenIdConnect; 
	using Microsoft.AspNetCore.Authentication.Cookies; 
	
	var builder = WebApplication.CreateBuilder(args); 
	
	builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) 
	.AddCookie() 
	// 配置OpenID Connect客户端 
	.AddOpenIdConnect("OIDC", options => 
	{ 
	// OIDC授权服务器地址 
	options.Authority = "https://your-oidc-server.com"; 
	options.ClientId = "your-client-id"; // 客户端ID 
	options.ClientSecret = "your-client-secret"; // 客户端密钥 
	options.ResponseType = "code"; // 授权码模式 
	options.Scope.Add("openid"); // 必须包含的OIDC范围 
	options.Scope.Add("profile"); // 请求用户基本信息(姓名、头像) 
	options.Scope.Add("email"); // 请求用户邮箱 
	
	// 回调地址(需在授权服务器注册) 
	options.CallbackPath = "/signin-oidc"; 
	// 登出回调地址 
	options.SignedOutCallbackPath = "/signout-callback-oidc"; 
	
	// 配置ID Token验证参数 
	options.TokenValidationParameters = new TokenValidationParameters 
	{ 
	ValidateIssuer = true, // 验证颁发者 
	ValidateAudience = true, // 验证受众 
	ValidateLifetime = true, // 验证过期时间 
	NameClaimType = "name", // 用户名对应的Claim类型 
	RoleClaimType = "role" // 用户角色对应的Claim类型 
	}; 
	
	// 事件回调:获取ID Token后处理用户信息 
	options.Events = new OpenIdConnectEvents 
	{ 
	OnTokenValidated = context => 
	{ 
	// 从ID Token中提取用户信息 
	var userId = context.Principal.FindFirst("sub")?.Value; // OIDC标准Claim:用户唯一ID 
	var userName = context.Principal.FindFirst("name")?.Value; 
	Console.WriteLine($"用户登录成功:{userName}(ID:{userId})"); 
	return Task.CompletedTask; 
	} 
	}; 
	}); 
	
	var app = builder.Build(); 
	
	app.UseAuthentication(); 
	app.UseAuthorization(); 
	
	app.MapGet("/login", async context => 
	{ 
	// 触发OIDC登录流程 
	await context.ChallengeAsync("OIDC", new AuthenticationProperties { RedirectUri = "/" }); 
	}); 
	
	app.MapGet("/", (ClaimsPrincipal user) => 
	{ 
	if (user.Identity.IsAuthenticated) 
	{ 
	return Results.Ok(new 
	{ 
	UserId = user.FindFirst("sub")?.Value, 
	UserName = user.FindFirst("name")?.Value, 
	Email = user.FindFirst("email")?.Value 
	}); 
	} 
	return Results.Redirect("/login"); 
	}); 
	
	app.Run();

逐行讲解:
1.AddOpenIdConnect("OIDC"):配置OIDC客户端,核心参数:
oAuthority:OIDC授权服务器的地址(如IdentityServer4的地址);
oScope:必须包含openid(OIDC标准范围),可选profile、email等;
oResponseType = "code":使用授权码模式(OIDC推荐的模式);
2.TokenValidationParameters:配置ID Token的验证规则,核心是验证签名(确保ID Token来自信任的授权服务器);
3.OnTokenValidated:事件回调,在ID Token验证通过后,提取用户身份信息(OIDC标准Claim如sub(用户唯一ID)、name(用户名))。
3. 基础知识拓展
OIDC与OAuth2的区别

OAuth2 OIDC
授权框架,解决“第三方应用如何访问用户资源” 身份认证协议,解决“第三方应用如何确认用户身份”
核心令牌:Access Token 核心令牌:ID Token(JWT格式)+ Access Token
无用户身份信息 自包含用户身份信息,支持单点登录(SSO)

OIDC的单点登录(SSO)原理
用户登录一次OIDC授权服务器后,授权服务器会在用户浏览器中设置会话Cookie。当用户访问其他关联的OIDC客户端时,客户端会重定向用户到授权服务器,授权服务器检测到用户已登录,直接返回授权码,无需用户再次登录,实现单点登录。
18.9.5 对比与最佳实践

  1. 技术选型对比
技术 核心作用 适用场景
JWT 无状态身份认证令牌 API服务认证、前后端分离应用
OAuth2 授权框架 第三方应用访问用户资源、服务间通信
OIDC 身份认证协议(基于OAuth2) 单点登录(SSO)、跨应用身份认证
  1. 安全最佳实践
    优先使用OIDC:若需要身份认证和单点登录,优先使用OIDC而非纯OAuth2;
    JWT使用非对称加密:生产环境中JWT的签名算法优先使用RS256(私钥签名,公钥验证),避免对称加密的密钥泄露风险;
    OAuth2使用授权码模式:除内部系统外,优先使用授权码模式,避免使用密码模式(需用户提供密码);
    令牌存储安全:前端应用中,令牌应存储在HttpOnly Cookie中,避免XSS攻击;后端应用中,令牌应存储在内存或加密的配置中心。
    18.9.6 思考题
    1.JWT令牌无法主动吊销,如何解决这个问题?(提示:服务器维护令牌黑名单、使用Refresh Token刷新)
    2.OAuth2的授权码模式和密码模式的区别是什么?分别适用于什么场景?
    3.OIDC的ID Token和Access Token的区别是什么?分别用于什么场景?
    4.如何在ASP.NET Core Web API中配置JWT认证,保护敏感接口?
    5.设计一个基于OIDC的单点登录系统,包含两个客户端应用和一个OIDC授权服务器,描述核心流程。

 本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49552.html


相关教程