using System.IdentityModel.Tokens.Jwt; using System.Net; using System.Net.Http.Headers; using System.Security.Claims; using System.Text; using FsCheck; using FsCheck.Xunit; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.IdentityModel.Tokens; namespace CampusErrand.Tests; /// /// Property 26: JWT 认证 /// 验证:未携带有效 JWT 时返回 401;携带有效 JWT 但非管理员访问管理接口时返回 403。 /// **Feature: login-and-homepage, Property 26: JWT 认证** /// /// public class JwtAuthPropertyTests : IClassFixture> { private readonly WebApplicationFactory _factory; // 与 appsettings.json 中一致的配置 private const string Secret = "YourSuperSecretKeyForJwtTokenGeneration_AtLeast32Chars!"; private const string Issuer = "CampusErrand"; private const string Audience = "CampusErrandApp"; public JwtAuthPropertyTests(WebApplicationFactory factory) { _factory = factory; } /// /// 生成有效的 JWT 令牌 /// private static string GenerateValidToken(int userId, string role, int expireMinutes = 60) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Secret)); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(ClaimTypes.NameIdentifier, userId.ToString()), new Claim(ClaimTypes.Role, role) }; var token = new JwtSecurityToken( issuer: Issuer, audience: Audience, claims: claims, expires: DateTime.UtcNow.AddMinutes(expireMinutes), signingCredentials: credentials ); return new JwtSecurityTokenHandler().WriteToken(token); } /// /// 生成已过期的 JWT 令牌 /// private static string GenerateExpiredToken(int userId, string role) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Secret)); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new[] { new Claim(ClaimTypes.NameIdentifier, userId.ToString()), new Claim(ClaimTypes.Role, role) }; var token = new JwtSecurityToken( issuer: Issuer, audience: Audience, claims: claims, expires: DateTime.UtcNow.AddMinutes(-1), signingCredentials: credentials ); return new JwtSecurityTokenHandler().WriteToken(token); } /// /// 属性:对任意用户 ID,未携带 JWT 访问受保护接口应返回 401 /// [Property(MaxTest = 20)] public bool 无Token访问受保护接口返回401(PositiveInt userId) { var client = _factory.CreateClient(); var response = client.GetAsync("/api/protected").Result; return response.StatusCode == HttpStatusCode.Unauthorized; } /// /// 属性:对任意用户 ID,携带有效 JWT 访问受保护接口应返回 200 /// [Property(MaxTest = 20)] public bool 有效Token访问受保护接口返回200(PositiveInt userId) { var token = GenerateValidToken(userId.Get, "User"); var client = _factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = client.GetAsync("/api/protected").Result; return response.StatusCode == HttpStatusCode.OK; } /// /// 属性:对任意用户 ID,非管理员角色访问管理接口应返回 403 /// [Property(MaxTest = 20)] public bool 非管理员访问管理接口返回403(PositiveInt userId) { var token = GenerateValidToken(userId.Get, "User"); var client = _factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = client.GetAsync("/api/admin/protected").Result; return response.StatusCode == HttpStatusCode.Forbidden; } /// /// 属性:对任意用户 ID,管理员角色访问管理接口应返回 200 /// [Property(MaxTest = 20)] public bool 管理员访问管理接口返回200(PositiveInt userId) { var token = GenerateValidToken(userId.Get, "Admin"); var client = _factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var response = client.GetAsync("/api/admin/protected").Result; return response.StatusCode == HttpStatusCode.OK; } /// /// 属性:对任意用户 ID,过期 JWT 访问受保护接口应返回 401 /// [Property(MaxTest = 20)] public bool 过期Token访问受保护接口返回401(PositiveInt userId) { var tokenStr = GenerateExpiredToken(userId.Get, "User"); var client = _factory.CreateClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenStr); var response = client.GetAsync("/api/protected").Result; return response.StatusCode == HttpStatusCode.Unauthorized; } }