在 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>();
            });
        }
    }
}