2025-04-16 乐帮网
webapi
本文章基于.NET 10.0.100-preview.3 编写的WebApi程序,基本诉求是前后端分离的后端WebAPI,由于关键点较多,所以会分为几个部分进行阐述,如有错误欢迎指正。
首先 有开发工具Microsoft Visual Studio 2022,再下载.NET 10 SDK 地址: https://dotnet.microsoft.com/zh-cn/download/dotnet/10.0 ,最新版本为 .NET 10.0.100-preview.3 发布说明参见:https://github.com/dotnet/core/discussions/9846。 默认以上条件都已准备,新建一个以 ASP.NET Core Web API为模板的项目,新建过程记得勾选 启用OpenApi,如果不勾选,在Program中手动添加代码也可以。
一、添加引用Nuget包
本节需要引用两个包:Microsoft.AspNetCore.Authentication.JwtBearer和Microsoft.AspNetCore.OpenApi。如果在创建项目时启用了OpenApi,则默认第二个包已经添加进来。
二、修改Program类
这里涉及的内容较多,主要是参考官方方法修改:
https://learn.microsoft.com/zh-cn/aspnet/core/security/authentication/configure-jwt-bearer-authentication?view=aspnetcore-10.0
这里我们是单服务器验证,签发和验证都是同一台服务器。以下行是实现签发类的注入后面会讲到实现:
builder.Services.AddScoped<IJwtProvider, JwtProvider>();
主要修改添加JWT配置和启用了全局验证,在验证时如何使用高级细分授权我感觉比之前容易控制,这里只做一个简单的Demo,并未做自定义授权和控制。
在API的控制器和方法上默认启用验证,不需要每个方法都添加标签 [Authorize]
app.MapControllers().RequireAuthorization();
完整个类如下:
var builder = WebApplication.CreateBuilder(args);
var defaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
builder.Services.AddAuthentication()
.AddJwtBearer(options =>
{
//options.Authority = "https://{--your-authority--}";
//options.Audience = "api://my-web-api";
//options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true, //是否验证Issuer
ValidIssuer = builder.Configuration["Jwt:Issuer"], //签发人Issuer
ValidateAudience = true, //是否验证Audience
ValidAudience = builder.Configuration["Jwt:Audience"], //订阅人Audience
ValidateIssuerSigningKey = true, //是否验证SecurityKey
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"] ?? string.Empty)), //SecurityKey
ValidateLifetime = true, //是否验证失效时间
ClockSkew = TimeSpan.FromSeconds(30), //过期时间容错值,解决服务器端时间不同步问题(秒)
RequireExpirationTime = true,
};
options.RequireHttpsMetadata = false;
});
builder.Services.AddScoped<IJwtProvider, JwtProvider>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.MapOpenApi();
}
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers().RequireAuthorization();
app.Run();
三、核心类JwtProvider实现签发
我们定义一个目标IJwtProvider里面两个方法,分别来实现JWT Token的生成和刷新, 另一个方法是Token的作废,我们在后面会讲到实现过程。
public interface IJwtProvider
{
TokenModel GenerateToken(UserModel user);
UserModel? ValidateToken(string token);
}
在编写前需要准备几项数据appsettings.json 中JWT配置如下:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Jwt": {
"Issuer": "demo",
"Audience": "demo-api",
"Key": "7a*4g@k9!xq3^v1s&00987l0p6n%rKhtqup`_n3",
"ExpireSeconds": 10800
}
}
用于定义Token的载体:
public class TokenModel
{
public TokenModel() : this(string.Empty, 0)
{
}
public TokenModel(string token, int expireIn)
{
AccessToken = token;
ExpireIn = expireIn;
}
public string AccessToken { get; set; }
/// <summary>
/// 秒
/// </summary>
public int ExpireIn { get; set; }
}
用于Token身份信息:
public class UserModel
{
private string[] _roles;
private string _roleString;
public required string UserId { get; set; }
public required string UserName { get; set; }
public required string RolesString
{
get => _roleString;
set
{
_roleString = value;
_roles = _roleString?.Split(';') ?? new string[] { };
}
}
public string[] Roles => _roles;
}
用于请求的类:
public class AuthUserRequest
{
public string UserId { get; set; } = string.Empty;
public string PassWord { get; set; } = string.Empty;
}
用于返回的共用类:
public class ApiResponse
{
public ApiResponse(object data) :this(0, "success", data)
{
}
public ApiResponse(int code,string message,object data)
{
Code = code;
Message = message;
Data = data;
}
public int Code { get; set; }
public string Message { get; set; }
public object Data { get; set; }
public static ApiResponse New(object data)
{
return new ApiResponse(data);
}
public static ApiResponse New(int code,string message)
{
return new ApiResponse(code, message, new { });
}
}
为了方便转实体我们定义扩展类:
public static class ClaimsPrincipalExtensions
{
public static UserModel? GetUser(this ClaimsPrincipal? principal)
{
if (principal == null)
return null;
if (principal != null && principal.Claims.Any())
{
return new UserModel()
{
UserId = principal.Claims.FirstOrDefault(c => c.Type.Equals("UserId", StringComparison.Ordinal))?.Value ?? string.Empty,
UserName = principal.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.Name, StringComparison.Ordinal))?.Value ?? string.Empty,
RolesString = principal.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.Role, StringComparison.Ordinal))?.Value ?? string.Empty,
};
}
;
return null;
}
}
最后是核心实现类JwtProvider:
public class JwtProvider : IJwtProvider
{
private readonly IConfiguration _configuration;
public JwtProvider(IConfiguration configuration)
{
_configuration = configuration;
}
public TokenModel GenerateToken(UserModel user)
{
string key = _configuration["Jwt:Key"] ?? string.Empty;
int expireSeconds = 10800;
int.TryParse(_configuration["Jwt:ExpireSeconds"], out expireSeconds);
if (string.IsNullOrEmpty(key))
return new TokenModel();
var claims = new[]
{
new Claim(ClaimTypes.Name, user.UserName), //HttpContext.User.Identity.Name
new Claim(ClaimTypes.Role, string.Join(";", user.Roles)), //HttpContext.User.IsInRole("r_admin")
new Claim("UserId", user.UserId),
};
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var creds = new SigningCredentials(secretKey, SecurityAlgorithms.HmacSha256);
var jwtSecurityToken = new JwtSecurityToken(
_configuration["Jwt:Issuer"], //Issuer
_configuration["Jwt:Audience"], //Audience
claims, //Claims,
DateTime.Now, //notBefore
DateTime.Now.AddSeconds(expireSeconds), //expires
creds //Credentials
);
var token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken);
return new TokenModel(token, expireSeconds);
}
public UserModel? ValidateToken(string token)
{
string key = _configuration["Jwt:Key"] ?? string.Empty;
if (string.IsNullOrEmpty(token)|| string.IsNullOrEmpty(key))
return default;
var tokenHandler = new JwtSecurityTokenHandler();
var secretKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(key));
var validateParameter = new TokenValidationParameters()
{
ValidateLifetime = true,
ValidateAudience = false,
ValidateIssuer = false,
ValidateIssuerSigningKey = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidAudience = _configuration["Jwt:Audience"],
IssuerSigningKey = secretKey
};
var principal = tokenHandler.ValidateToken(token, validateParameter, out SecurityToken validatedToken);
return principal.GetUser();
}
}
四、编写调用的AuthController
[ApiController]
[Route("api/[controller]/[action]")]
public class AuthController : ControllerBase
{
private readonly ILogger<AuthController> _logger;
private readonly IJwtProvider _jwtProvider;
public AuthController(ILogger<AuthController> logger, IJwtProvider jwtProvider)
{
_jwtProvider = jwtProvider;
_logger = logger;
}
[HttpPost]
[AllowAnonymous]
public ApiResponse Token(AuthUserRequest request)
{
if (string.IsNullOrEmpty(request.UserId) || string.IsNullOrEmpty(request.PassWord))
return ApiResponse.New(-1, "Please enter your username and password.");
UserModel user = new UserModel() { RolesString="admim",UserId="000296",UserName="lebang2020.cn" };
var token = _jwtProvider.GenerateToken(user);
return ApiResponse.New(token);
}
[HttpPost]
public ApiResponse Refresh()
{
//I can get user from token string in the header value.
var user = HttpContext.User.GetUser();
if(user==null)
return ApiResponse.New(-1, "Unknown error.");
//then get new token
var token = _jwtProvider.GenerateToken(user);
return ApiResponse.New(token);
}
//token 做废下次单独讲
}
五、测试调用API
以上API的调用地址 Post:http://localhost:5080/api/auth/token 拿到token,然后携带token再请求Refresh方法,试一下这个方法,其中调用Refresh方法必须携带token,否则返回401 无权限。
如下图:
调用http://localhost:5080/api/auth/token
调用http://localhost:5080/api/auth/Refresh
源码下载链接: https://pan.baidu.com/s/1ykhEtLeLzZ0nO5b02aCqIg
关注我的微信公众号
在公众号里留言交流
投稿邮箱:1052839972@qq.com
庭院深深深几许?杨柳堆烟,帘幕无重数。
玉勒雕鞍游冶处,楼高不见章台路。
雨横风狂三月暮。门掩黄昏,无计留春住。
泪眼问花花不语,乱红飞过秋千去。
如果感觉对您有帮助
欢迎向作者提供捐赠
这将是创作的最大动力