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;
}
}