WebApi+Grpc+Jwt身份认证
使用Grpc做服务间通信,使用JWT,JWT可以使用在前端,后端,微服务等。
服务端:
首先需要安装nuget包 Microsoft.AspNetCore.Authentication.JwtBearer
首先创建JWTHelp.cs

using DataService01.protos; using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; using System.Threading.Tasks; namespace DataService01.Models { public class JWTHelper { /// <summary> /// 创建token /// </summary> /// <param name="_users"></param> /// <param name="jwtDTO"></param> /// <returns></returns> public async Task<TokenModel> IssueJwt(users _users, JWTDTO jwtDTO) { var exp = $"{new DateTimeOffset(DateTime.Now.AddMinutes(jwtDTO.ExpireMinutes)).ToUnixTimeMilliseconds()}"; List<Claim> claims = new List<Claim>() { new Claim(JwtRegisteredClaimNames.Jti,Guid.NewGuid().ToString()), new Claim(JwtRegisteredClaimNames.Iat,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds()}"), new Claim(JwtRegisteredClaimNames.Nbf,$"{new DateTimeOffset(DateTime.Now).ToUnixTimeMilliseconds()}"), new Claim(JwtRegisteredClaimNames.Exp,exp), new Claim(JwtRegisteredClaimNames.Iss,jwtDTO.Issuer), new Claim(JwtRegisteredClaimNames.Aud,jwtDTO.Audience), new Claim(ClaimTypes.Name,_users.Name), new Claim(ClaimTypes.Role,_users.Roleid.ToString()), new Claim("loginname",_users.LoginName), new Claim("isman",_users.IsMan.ToString()) }; var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtDTO.SecurityKey)); var cerds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); JwtSecurityToken jwtSecurityToken = new JwtSecurityToken(issuer: jwtDTO.Issuer,audience:jwtDTO.Audience, claims: claims,expires:DateTime.Now.AddMinutes(jwtDTO.ExpireMinutes), signingCredentials: cerds); string jwt_token = new JwtSecurityTokenHandler().WriteToken(jwtSecurityToken); // jwtSecurityToken.ValidTo;//过期时间 return new TokenModel { Token = jwt_token, ExpireTime = jwtSecurityToken.ValidTo, Success = "ok" }; } /// <summary> /// 解析jwt中的明文内容,不建议放关键信息 /// </summary> /// <param name="jwtstr"></param> /// <returns></returns> public users SerializeJwt(string jwtstr) { var jwt_hender = new JwtSecurityTokenHandler(); JwtSecurityToken securityToken = jwt_hender.ReadJwtToken(jwtstr); users _users = new users() { //ID = Convert.ToInt32(securityToken.Id), Name = securityToken.Payload[ClaimTypes.Name].ToString(), LoginName = securityToken.Payload["loginname"].ToString(), Roleid = Convert.ToInt32(securityToken.Payload[ClaimTypes.Role] ?? 0), IsMan = Convert.ToBoolean(securityToken.Payload["isman"]) }; return _users; } } public class TokenModel { public string Token { get; set; } public DateTime ExpireTime { get; set; } public string Success { get; set; } } }
在服务端的Startup.cs中添加身份认证和授权
首先在 ConfigureServices 方法中增加

services.AddAuthorization(option => option.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy => { policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme); policy.RequireClaim("sub"); })); //services.AddAuthorization(); services.AddAuthentication().AddJwtBearer(options=> { options.TokenValidationParameters = new TokenValidationParameters() { ValidIssuer = "http://localhost:5001", ValidAudience = "http://localhost:5000", IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetSection("JWTDTO").GetSection("SecurityKey").Value))// "9e79234cd150108e5048d0e0cb4ca5e4" }; });
然后在Configure方法中增加
app.UseAuthentication();
app.UseAuthorization();
在需要使用认证的服务中添加
[Authorize(AuthenticationSchemes =JwtBearerDefaults.AuthenticationScheme)]
JWT所使用的{发行方、使用方、key、超时时间等}放在了appsetting.json里
Startup.cs代码参考

using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using DataService01.Models; using DataService01.protos; using DataService01.Services; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.IdentityModel.Tokens; using System.Text; namespace DataService01 { public class Startup { private readonly IConfiguration configuration; public Startup(IConfiguration configuration) { this.configuration = configuration; } // This method gets called by the runtime. Use this method to add services to the container. // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 public void ConfigureServices(IServiceCollection services) { services.AddGrpc(); services.AddAuthorization(option => option.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy => { policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme); policy.RequireClaim("sub"); })); //services.AddAuthorization(); services.AddAuthentication().AddJwtBearer(options=> { options.TokenValidationParameters = new TokenValidationParameters() { ValidIssuer = "http://localhost:5001", ValidAudience = "http://localhost:5000", IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration.GetSection("JWTDTO").GetSection("SecurityKey").Value))// "9e79234cd150108e5048d0e0cb4ca5e4" }; }); services.Configure<JWTDTO>(configuration.GetSection("JWTDTO")); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } app.UseRouting(); app.UseHttpsRedirection(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { //endpoints.MapGet("/", async context => //{ // await context.Response.WriteAsync("Hello World!"); //}); endpoints.MapGrpcService<ds01>(); // endpoints.MapGrpcService<ds02>(); }); } } }
为了方便使用jwt的配置信息,创建一个JWTDTO类

using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; namespace DataService01.Models { public class JWTDTO { /// <summary> /// 颁发者 /// </summary> public string Issuer { get; set; } /// <summary> /// 使用者 /// </summary> public string Audience { get; set; } /// <summary> /// 密钥key /// </summary> public string SecurityKey { get; set; } /// <summary> /// 过期时间 /// </summary> public int ExpireMinutes { get; set; } } }
在ConfigureServices 注册,在ds01.cs(服务实现类)的构造函数中注入

using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.Extensions.Logging; using Grpc.AspNetCore.Server; using Grpc.AspNetCore; using DataService01.protos; using Grpc.Core; using System.IO; using Microsoft.AspNetCore.Authorization; using System.Configuration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Options; using DataService01.Models; using Microsoft.AspNetCore.Authentication.JwtBearer; namespace DataService01.Services { [Authorize(AuthenticationSchemes =JwtBearerDefaults.AuthenticationScheme)] public class ds01 : userservice.userserviceBase { private readonly ILogger<ds01> logger; private readonly IConfiguration configuration; private readonly IOptions<JWTDTO> jwt_Options; public ds01(ILogger<ds01> logger,IConfiguration configuration,IOptions<JWTDTO> Jwt_options) { this.logger = logger; this.configuration = configuration; jwt_Options = Jwt_options; } [AllowAnonymous] public override async Task<return_token> gettoken(get_token request, ServerCallContext context) { users _user = new users(); _user.LoginName = request.LoginName; if (request.LoginName.Equals("admin") && request.Password.Equals("123456")) { var jwttoken=await new JWTHelper().IssueJwt(_user, jwt_Options.Value); return await Task.FromResult(new return_token() { Token = jwttoken.Token, ExpireTime = new DateTimeOffset(jwttoken.ExpireTime).ToUnixTimeSeconds().ToString() }); } return await Task.FromResult(new return_token() { Token = "", ExpireTime = "" }); } public override Task<getusersresponse> Getuser(getusers request, ServerCallContext context) { var matedata_md=context.RequestHeaders; foreach (var pire in matedata_md) { logger.LogInformation($"{pire.Key}:{pire.Value}"); logger.LogInformation(pire.Key+":"+pire.Value); } users item = userdatas.userslist.SingleOrDefault(n => n.ID == request.ID); if (item != null) { return Task.FromResult(new getusersresponse() { Code = 0, Msg = "成功", Usermodel = item }); } else { return Task.FromResult(new getusersresponse() { Code = -1, Msg = "失败" }); } } public override async Task getall(getusers request, IServerStreamWriter<getusersresponse> responseStream, ServerCallContext context) { foreach (var item in userdatas.userslist) { //逐步返回数据 await responseStream.WriteAsync(new getusersresponse() { Usermodel = item } ) ; } } public override async Task<getusers> Add(IAsyncStreamReader<addphoto> requestStream, ServerCallContext context) { List<byte> bt = new List<byte>(); while (await requestStream.MoveNext())//有数据进入 { bt.AddRange(requestStream.Current.Data); } //while 执行完之后表示没有数据再进来 FileStream file = new FileStream(AppDomain.CurrentDomain.BaseDirectory+"01.png",FileMode.OpenOrCreate) ; file.Write(bt.ToArray(), 0, bt.Count); file.Flush(); file.Close(); return new getusers() { Name = "成功",ID = 0 }; } public override async Task saveall(IAsyncStreamReader<addphoto> requestStream, IServerStreamWriter<getusersresponse> responseStream, ServerCallContext context) { List<byte> bt = new List<byte>(); while (await requestStream.MoveNext())//有数据进入 { bt.AddRange(requestStream.Current.Data); } //while 执行完之后表示没有数据再进来 FileStream file = new FileStream("/01.png", FileMode.OpenOrCreate); file.Write(bt.ToArray(), 0, bt.Count); file.Flush(); file.Close(); //返回数据 foreach (var item in userdatas.userslist) { await responseStream.WriteAsync(new getusersresponse() { Msg = "成功", Code = 0, Usermodel = item }); } } } public class userdatas { public static IList<users> userslist = new List<users>() { new users(){ID=1,Name="11",LoginName="111",Roleid=1,IsMan=true}, new users(){ID=2,Name="22",LoginName="222",Roleid=2,IsMan=false}, new users(){ID=3,Name="33",LoginName="333",Roleid=3,IsMan=true} }; } }
在ds01 中增加一个获取jwttoken的方法,设置不鉴权
[AllowAnonymous] public override async Task<return_token> gettoken(get_token request, ServerCallContext context) { users _user = new users(); _user.LoginName = request.LoginName; if (request.LoginName.Equals("admin") && request.Password.Equals("123456")) { var jwttoken=await new JWTHelper().IssueJwt(_user, jwt_Options.Value); return await Task.FromResult(new return_token() { Token = jwttoken.Token, ExpireTime = new DateTimeOffset(jwttoken.ExpireTime).ToUnixTimeSeconds().ToString() }); } return await Task.FromResult(new return_token() { Token = "", ExpireTime = "" }); }
#region 以下一段是jwt刷新的内容,我没有在代码中实现,仅参考
由于jwt是无状态的,jwt超时后需要更新,不然刚刚还在操作的用户就被提示 认证失败了,所以需要在token过期前更新token。
网上看到一些方案 设置两个时间,一个是token有效时间(20分钟),另一个是token存活的时间(1小时,将token存放在redis中,1小时是redis的有效时间),如果超过了token时间,就到redis中取,我个人认为不太好。
看了下另外一个项目案例的方案觉得可以:token时间为20分钟,当请求时如果超过了时间的2/3,则返回202状态吗;客户端接收到202状态码后,客户端重新获取token,获取后再次请求业务逻辑;
案例是vue写的,以下为服务端与客户端代码:

function post(url, params, showLoading,config) { _showLoading = showLoading; axios.defaults.headers[_Authorization] = getToken(); return new Promise((resolve, reject) => { // axios.post(url, qs.stringify(params)) // axios.post(url, params,config) .then(response => { if (response.status == 202) { getNewToken(() => { post(url, params, _showLoading); }); return; } resolve(response.data); }, err => { if (err.status == 202) { getNewToken(() => { post(url, params, _showLoading); }); return; } reject(err.data && err.data.message ? err.data.message : '网络好像出了点问题~~'); }) .catch((error) => { reject(error) }) }) } //当前token快要过期时,用现有的token换成一个新的token function getNewToken(callBack) { ajax({ url: "/api/User/replaceToken", param: {}, json: true, success: function (x) { if (x.status) { let userInfo = $httpVue.$store.getters.getUserInfo(); userInfo.token = x.data; currentToken = x.data; $httpVue.$store.commit('setUserInfo', userInfo); callBack(); } else { console.log(x.message); toLogin(); } }, errror: function (ex) { console.log(ex); toLogin(); }, type: "post", async: false }); }
客户端判断状态码为202时,先调用gettoken()gettoken的callback 是递归调用原方法。

DateTime expDate = context.HttpContext.User.Claims.Where(x => x.Type == JwtRegisteredClaimNames.Exp) .Select(x => x.Value).FirstOrDefault().GetTimeSpmpToDate(); //如果过期时间小于设置定分钟数的1/3时,返回状态需要刷新token if (expDate < DateTime.Now || (expDate - DateTime.Now).TotalMinutes < AppSetting.ExpMinutes / 3) { context.FilterResult(HttpStatusCode.Accepted, "Token即将过期,请更换token");//202 return; }
服务端为Webapi 在Startup.cs的 mvc增加两个过滤器,其中第一个是 用来检验jwt的。服务端返回202状态码的这段,写在过滤器里就可以。
#endregion
至此服务端代码结束。
客户端
using GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:5001"); var service01 = new userservice.userserviceClient(channel); return_token rt_token= service01.gettoken(new get_token() { LoginName = "admin", Password = "123456" });//获取JWTtoken var md_add_token = new Metadata() ; if (!string.IsNullOrEmpty(rt_token.Token)) { md_add_token.Add("Authorization", $"Bearer {rt_token.Token}");//将Token 添加到 Hearers里,key和Value 是固定写法,value中 Bearer 与token中间 要加一个空格 } getusersresponse us = await service01.GetuserAsync(new getusers() { ID = 2 },headers:md_add_token);//一元请求+传送元数据// header是Jwttoken _logger.LogInformation(us.Msg);
感谢B站Up主“软件工艺师”。
https://gitee.com/zeran/core01.git
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· C#/.NET/.NET Core优秀项目和框架2025年2月简报
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 【杭电多校比赛记录】2025“钉耙编程”中国大学生算法设计春季联赛(1)