在 gRPC 中使用 JWT 时出现授权错误 ASP.NET
Authorization error while using JWT in gRPC ASP.NET
我使用gRPC尝试进行JWT授权,但是当我尝试授权时,客户端出现错误:
Grpc.Core.RpcException: "Status(StatusCode="Unimplemented", Detail="Bad gRPC response. HTTP status code: 404")"
我不明白这是怎么回事。
我的 gRPC 服务器:
Startup.cs:
using AspNetCore.Identity.Mongo;
using GrpcServiceTiEventsy.MongoDB.Identity;
using GrpcServiceTiEventsy.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using MongoDB.Driver;
using System.Security.Claims;
using System.Text;
namespace GrpcServiceTiEventsy
{
public class Startup
{
private readonly SymmetricSecurityKey _securityKey;
public static MongoClient MongoDbClient { get; private set; }
public static IConfiguration Configuration { get; private set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
_securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Token:SecretKey"]));
MongoDbClient = new MongoClient(Configuration.GetConnectionString("MongoDB"));
}
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddSingleton<IMongoClient, MongoClient>(sp => MongoDbClient);
services.AddIdentityMongoDbProvider<DatabaseAccount, DatabaseRole>(identity =>
{
identity.Password.RequiredLength = 8;
identity.Password.RequireNonAlphanumeric = false;
identity.Password.RequireLowercase = false;
identity.Password.RequireUppercase = false;
identity.Password.RequireDigit = false;
},
mongo =>
mongo.ConnectionString = Configuration.GetConnectionString("MongoDB") + "/TiEventsyIdentity"
);
services.AddAuthorization(options =>
{
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.Name);
});
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(
opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateActor = false,
ValidateIssuer = true,
ValidIssuer = Configuration["Token:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["Token:Audience"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = _securityKey
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
endpoints.MapGrpcService<LoginService>();
endpoints.MapGrpcService<RefreshedTokenService>();
endpoints.MapGrpcService<RegistrationService>();
});
}
}
}
TokensGenerator.cs:
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Grpc.Core;
using GrpcServiceTiEventsy.MongoDB.Identity;
using Microsoft.IdentityModel.Tokens;
using MongoDB.Driver;
namespace GrpcServiceTiEventsy.Models.Tokens
{
public static class TokensGenerator
{
private static readonly SymmetricSecurityKey SecurityKey
= new (Encoding.ASCII.GetBytes(Startup.Configuration["Token:SecretKey"]));
public static async Task<(string, string)> CreateTokens(DatabaseAccount account) =>
(CreateJwt(account), await CreateRefreshToken(account));
public static async Task<(string, string)> CreateTokens(DatabaseAccount account, string jwtToken, string refreshToken)
{
if (ValidateJwtToken(jwtToken) && ValidateRefreshToken(account, refreshToken))
return (CreateJwt(account), await CreateRefreshToken(account));
throw new RpcException(Status.DefaultSuccess,"Tokens are not real");
}
private static bool ValidateRefreshToken(DatabaseAccount account, string token)
=> account.RefreshToken == token;
private static bool ValidateJwtToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateActor = false,
ValidateIssuer = true,
ValidIssuer = Startup.Configuration["Token:Issuer"],
ValidateAudience = true,
ValidAudience = Startup.Configuration["Token:Audience"],
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = SecurityKey
}, out var validatedToken);
return true;
}
catch
{
return false;
}
}
private static string CreateJwt(DatabaseAccount account)
{
var claims = new[] { new Claim(ClaimTypes.Name, account.UserName) };
var credentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);
var expiresDays = Convert.ToInt32(Startup.Configuration["Token:LifetimeDay"]);
var tokenHandler = new JwtSecurityTokenHandler();
var token = new JwtSecurityToken(
Startup.Configuration["Token:Issuer"],
Startup.Configuration["Token:Audience"],
claims,
expires: DateTime.Now.AddDays(expiresDays),
signingCredentials: credentials);
return tokenHandler.WriteToken(token);
}
private static async Task<string> CreateRefreshToken(DatabaseAccount account)
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
rng.GetBytes(randomNumber);
var token = Convert.ToBase64String(randomNumber);
var identityDb = Startup.MongoDbClient.GetDatabase("TiEventsyIdentity");
var accountsCollection = identityDb.GetCollection<DatabaseAccount>("Users");
var user = await accountsCollection.Find(ac => ac.Id == account.Id).FirstOrDefaultAsync();
if (user != null)
{
var filter = Builders<DatabaseAccount>.Filter.Eq(s => s.Id, account.Id);
var update = Builders<DatabaseAccount>.Update.Set(s => s.RefreshToken, token);
await accountsCollection.UpdateOneAsync(filter, update);
}
else throw new RpcException(Status.DefaultSuccess,"No such user exists");
return token;
}
}
}
GreeterService.cs:
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
namespace GrpcServiceTiEventsy.Services
{
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
[Authorize]
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
}
我的客户:
Program.cs:
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using GrpcServiceTiEventsy;
Console.WriteLine("Param...");
var channel = GrpcChannel.ForAddress("https://localhost:5001");
Console.WriteLine("Log...");
var l = new Login.LoginClient(channel);
var tokens = await l.LogAsync(new LoginRequest { Email = "igorka@gmail.com", Password = "pas@jKH$KJJT34592345" });
Console.WriteLine($"\n Access: {tokens.AccessToken}" +
$"\n Refresh: {tokens.RefreshToken}" +
"\n");
var credentials = CallCredentials.FromInterceptor((context, metadata) =>
{
metadata.Add("Authorization", $"Bearer {tokens.AccessToken}");
return Task.CompletedTask;
});
channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
});
Console.WriteLine("Test Auth...");
var te = new Greeter.GreeterClient(channel);
var outD = await te.SayHelloAsync(new HelloRequest { Name = "igorka@gmail.com" });
Console.WriteLine("Out: " +outD);
此处推断错误var outD = await te.SayHelloAsync(new HelloRequest { Name = "igorka@gmail.com" });
如有待补充,请在评论中诋毁
经过长时间的调试和阅读文章,我做出了正确的Startup.cs。
这是工作 Startup.cs:
using AspNetCore.Identity.Mongo;
using GrpcServiceTiEventsy.MongoDB.Identity;
using GrpcServiceTiEventsy.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using MongoDB.Driver;
using System.Text;
using Microsoft.AspNetCore.Identity;
namespace GrpcServiceTiEventsy
{
public class Startup
{
private readonly SymmetricSecurityKey _securityKey;
public static MongoClient MongoDbClient { get; private set; }
public static IConfiguration Configuration { get; private set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
_securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Token:SecretKey"]));
MongoDbClient = new MongoClient(Configuration.GetConnectionString("MongoDB"));
}
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddSingleton<IMongoClient, MongoClient>(sp => MongoDbClient);
services.AddIdentityMongoDbProvider<DatabaseAccount, DatabaseRole>(identity =>
{
identity.Password.RequiredLength = 8;
identity.Password.RequireNonAlphanumeric = false;
identity.Password.RequireLowercase = false;
identity.Password.RequireUppercase = false;
identity.Password.RequireDigit = false;
},
mongo =>
mongo.ConnectionString = Configuration.GetConnectionString("MongoDB") + "/TiEventsyIdentity"
).AddDefaultTokenProviders();
services
.AddAuthorization()
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(
opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateActor = false,
ValidateIssuer = true,
ValidIssuer = Configuration["Token:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["Token:Audience"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = _securityKey
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
endpoints.MapGrpcService<LoginService>();
endpoints.MapGrpcService<RefreshedTokenService>();
endpoints.MapGrpcService<RegistrationService>();
});
}
}
}
我使用gRPC尝试进行JWT授权,但是当我尝试授权时,客户端出现错误:
Grpc.Core.RpcException: "Status(StatusCode="Unimplemented", Detail="Bad gRPC response. HTTP status code: 404")"
我不明白这是怎么回事。
我的 gRPC 服务器:
Startup.cs:
using AspNetCore.Identity.Mongo;
using GrpcServiceTiEventsy.MongoDB.Identity;
using GrpcServiceTiEventsy.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using MongoDB.Driver;
using System.Security.Claims;
using System.Text;
namespace GrpcServiceTiEventsy
{
public class Startup
{
private readonly SymmetricSecurityKey _securityKey;
public static MongoClient MongoDbClient { get; private set; }
public static IConfiguration Configuration { get; private set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
_securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Token:SecretKey"]));
MongoDbClient = new MongoClient(Configuration.GetConnectionString("MongoDB"));
}
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddSingleton<IMongoClient, MongoClient>(sp => MongoDbClient);
services.AddIdentityMongoDbProvider<DatabaseAccount, DatabaseRole>(identity =>
{
identity.Password.RequiredLength = 8;
identity.Password.RequireNonAlphanumeric = false;
identity.Password.RequireLowercase = false;
identity.Password.RequireUppercase = false;
identity.Password.RequireDigit = false;
},
mongo =>
mongo.ConnectionString = Configuration.GetConnectionString("MongoDB") + "/TiEventsyIdentity"
);
services.AddAuthorization(options =>
{
options.AddPolicy(JwtBearerDefaults.AuthenticationScheme, policy =>
{
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme);
policy.RequireClaim(ClaimTypes.Name);
});
});
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(
opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateActor = false,
ValidateIssuer = true,
ValidIssuer = Configuration["Token:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["Token:Audience"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = _securityKey
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
endpoints.MapGrpcService<LoginService>();
endpoints.MapGrpcService<RefreshedTokenService>();
endpoints.MapGrpcService<RegistrationService>();
});
}
}
}
TokensGenerator.cs:
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Grpc.Core;
using GrpcServiceTiEventsy.MongoDB.Identity;
using Microsoft.IdentityModel.Tokens;
using MongoDB.Driver;
namespace GrpcServiceTiEventsy.Models.Tokens
{
public static class TokensGenerator
{
private static readonly SymmetricSecurityKey SecurityKey
= new (Encoding.ASCII.GetBytes(Startup.Configuration["Token:SecretKey"]));
public static async Task<(string, string)> CreateTokens(DatabaseAccount account) =>
(CreateJwt(account), await CreateRefreshToken(account));
public static async Task<(string, string)> CreateTokens(DatabaseAccount account, string jwtToken, string refreshToken)
{
if (ValidateJwtToken(jwtToken) && ValidateRefreshToken(account, refreshToken))
return (CreateJwt(account), await CreateRefreshToken(account));
throw new RpcException(Status.DefaultSuccess,"Tokens are not real");
}
private static bool ValidateRefreshToken(DatabaseAccount account, string token)
=> account.RefreshToken == token;
private static bool ValidateJwtToken(string token)
{
var tokenHandler = new JwtSecurityTokenHandler();
try
{
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateActor = false,
ValidateIssuer = true,
ValidIssuer = Startup.Configuration["Token:Issuer"],
ValidateAudience = true,
ValidAudience = Startup.Configuration["Token:Audience"],
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = SecurityKey
}, out var validatedToken);
return true;
}
catch
{
return false;
}
}
private static string CreateJwt(DatabaseAccount account)
{
var claims = new[] { new Claim(ClaimTypes.Name, account.UserName) };
var credentials = new SigningCredentials(SecurityKey, SecurityAlgorithms.HmacSha256);
var expiresDays = Convert.ToInt32(Startup.Configuration["Token:LifetimeDay"]);
var tokenHandler = new JwtSecurityTokenHandler();
var token = new JwtSecurityToken(
Startup.Configuration["Token:Issuer"],
Startup.Configuration["Token:Audience"],
claims,
expires: DateTime.Now.AddDays(expiresDays),
signingCredentials: credentials);
return tokenHandler.WriteToken(token);
}
private static async Task<string> CreateRefreshToken(DatabaseAccount account)
{
var randomNumber = new byte[32];
using (var rng = RandomNumberGenerator.Create())
rng.GetBytes(randomNumber);
var token = Convert.ToBase64String(randomNumber);
var identityDb = Startup.MongoDbClient.GetDatabase("TiEventsyIdentity");
var accountsCollection = identityDb.GetCollection<DatabaseAccount>("Users");
var user = await accountsCollection.Find(ac => ac.Id == account.Id).FirstOrDefaultAsync();
if (user != null)
{
var filter = Builders<DatabaseAccount>.Filter.Eq(s => s.Id, account.Id);
var update = Builders<DatabaseAccount>.Update.Set(s => s.RefreshToken, token);
await accountsCollection.UpdateOneAsync(filter, update);
}
else throw new RpcException(Status.DefaultSuccess,"No such user exists");
return token;
}
}
}
GreeterService.cs:
using System.Threading.Tasks;
using Grpc.Core;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Logging;
namespace GrpcServiceTiEventsy.Services
{
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;
public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}
[Authorize]
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Message = "Hello " + request.Name
});
}
}
}
我的客户:
Program.cs:
using System;
using System.Threading.Tasks;
using Grpc.Core;
using Grpc.Net.Client;
using GrpcServiceTiEventsy;
Console.WriteLine("Param...");
var channel = GrpcChannel.ForAddress("https://localhost:5001");
Console.WriteLine("Log...");
var l = new Login.LoginClient(channel);
var tokens = await l.LogAsync(new LoginRequest { Email = "igorka@gmail.com", Password = "pas@jKH$KJJT34592345" });
Console.WriteLine($"\n Access: {tokens.AccessToken}" +
$"\n Refresh: {tokens.RefreshToken}" +
"\n");
var credentials = CallCredentials.FromInterceptor((context, metadata) =>
{
metadata.Add("Authorization", $"Bearer {tokens.AccessToken}");
return Task.CompletedTask;
});
channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
});
Console.WriteLine("Test Auth...");
var te = new Greeter.GreeterClient(channel);
var outD = await te.SayHelloAsync(new HelloRequest { Name = "igorka@gmail.com" });
Console.WriteLine("Out: " +outD);
此处推断错误var outD = await te.SayHelloAsync(new HelloRequest { Name = "igorka@gmail.com" });
如有待补充,请在评论中诋毁
经过长时间的调试和阅读文章,我做出了正确的Startup.cs。
这是工作 Startup.cs:
using AspNetCore.Identity.Mongo;
using GrpcServiceTiEventsy.MongoDB.Identity;
using GrpcServiceTiEventsy.Services;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.IdentityModel.Tokens;
using MongoDB.Driver;
using System.Text;
using Microsoft.AspNetCore.Identity;
namespace GrpcServiceTiEventsy
{
public class Startup
{
private readonly SymmetricSecurityKey _securityKey;
public static MongoClient MongoDbClient { get; private set; }
public static IConfiguration Configuration { get; private set; }
public Startup(IConfiguration configuration)
{
Configuration = configuration;
_securityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(Configuration["Token:SecretKey"]));
MongoDbClient = new MongoClient(Configuration.GetConnectionString("MongoDB"));
}
public void ConfigureServices(IServiceCollection services)
{
services.AddGrpc();
services.AddSingleton<IMongoClient, MongoClient>(sp => MongoDbClient);
services.AddIdentityMongoDbProvider<DatabaseAccount, DatabaseRole>(identity =>
{
identity.Password.RequiredLength = 8;
identity.Password.RequireNonAlphanumeric = false;
identity.Password.RequireLowercase = false;
identity.Password.RequireUppercase = false;
identity.Password.RequireDigit = false;
},
mongo =>
mongo.ConnectionString = Configuration.GetConnectionString("MongoDB") + "/TiEventsyIdentity"
).AddDefaultTokenProviders();
services
.AddAuthorization()
.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(
opt =>
{
opt.TokenValidationParameters = new TokenValidationParameters
{
ValidateActor = false,
ValidateIssuer = true,
ValidIssuer = Configuration["Token:Issuer"],
ValidateAudience = true,
ValidAudience = Configuration["Token:Audience"],
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
IssuerSigningKey = _securityKey
};
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapGrpcService<GreeterService>();
endpoints.MapGrpcService<LoginService>();
endpoints.MapGrpcService<RefreshedTokenService>();
endpoints.MapGrpcService<RegistrationService>();
});
}
}
}