140 lines
5.2 KiB
C#
140 lines
5.2 KiB
C#
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;
|
||
|
||
/// <summary>
|
||
/// Property 26: JWT 认证
|
||
/// 验证:未携带有效 JWT 时返回 401;携带有效 JWT 但非管理员访问管理接口时返回 403。
|
||
/// **Feature: login-and-homepage, Property 26: JWT 认证**
|
||
///
|
||
/// </summary>
|
||
public class JwtAuthPropertyTests : IClassFixture<WebApplicationFactory<Program>>
|
||
{
|
||
private readonly WebApplicationFactory<Program> _factory;
|
||
|
||
// 与 appsettings.json 中一致的配置
|
||
private const string Secret = "YourSuperSecretKeyForJwtTokenGeneration_AtLeast32Chars!";
|
||
private const string Issuer = "CampusErrand";
|
||
private const string Audience = "CampusErrandApp";
|
||
|
||
public JwtAuthPropertyTests(WebApplicationFactory<Program> factory)
|
||
{
|
||
_factory = factory;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成有效的 JWT 令牌
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成已过期的 JWT 令牌
|
||
/// </summary>
|
||
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);
|
||
}
|
||
|
||
/// <summary>
|
||
/// 属性:对任意用户 ID,未携带 JWT 访问受保护接口应返回 401
|
||
/// </summary>
|
||
[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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 属性:对任意用户 ID,携带有效 JWT 访问受保护接口应返回 200
|
||
/// </summary>
|
||
[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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 属性:对任意用户 ID,非管理员角色访问管理接口应返回 403
|
||
/// </summary>
|
||
[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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 属性:对任意用户 ID,管理员角色访问管理接口应返回 200
|
||
/// </summary>
|
||
[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;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 属性:对任意用户 ID,过期 JWT 访问受保护接口应返回 401
|
||
/// </summary>
|
||
[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;
|
||
}
|
||
}
|