带有 Hotchocolate GraphQL 的 NET 6 WebAPI 未授权

问题描述 投票:0回答:2

我有一个 NET 6 api,正在使用 HotChocolate 12.7。我的查询可以工作,但是当我尝试将 [Authorize] 装饰器添加到查询中,并使用 Bearer 令牌发送请求时,我收到未经授权的响应。我无法让它识别经过正确身份验证的用户的 JWT。

这是程序.cs

using altitude_api.Entities;
using altitude_api.Models;
using altitude_api.Queries;
using altitude_api.Services;
using altitude_api.Services.Interfaces;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using Serilog;


namespace altitude_api
{
    public class Program
    {
        public static void Main(string[] args)
        {

            var configuration = new ConfigurationBuilder()
                .AddJsonFile("appsettings.json")
                .Build();
            var key = configuration.GetSection("AppSettings").GetSection("SecretKey");
            var builder = WebApplication.CreateBuilder(args);
            builder.Services.AddAuthorization();
            builder.Services.AddAuthentication(options =>
                {
                    options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
                    options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
                })
                .AddJwtBearer(opt =>
                {
                    opt.SaveToken = true;
                    opt.RequireHttpsMetadata = false;
                    opt.TokenValidationParameters = new TokenValidationParameters()
                    {
                        ValidateIssuer = true,
                        ValidateAudience = true,
                        ValidateLifetime = true,
                        ValidateIssuerSigningKey = true,
                        IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes((key.Value))),
                    };
                });

            builder.Services
                .AddGraphQLServer()
                .AddAuthorization()
                .AddQueryType<Query>()
                .AddTypeExtension<HealthQuery>();
            var services = builder.Services;
            
            
            // Add Services
            var appSettingsSection = builder.Configuration.GetSection("AppSettings").Get<AppSettings>();
            services.Configure<AppSettings>(configuration.GetSection("AppSettings"));
            services.AddScoped<IAuthService, AuthService>();
            
            var signinKey = new SymmetricSecurityKey(
                Encoding.UTF8.GetBytes(key.Value));
            
            Log.Logger = new LoggerConfiguration()
                .ReadFrom.Configuration(configuration)
                .CreateLogger();
            
            
            Log.Information("App starting up");
            
            services.AddAuthorization(options =>
            {
                options.DefaultPolicy = new AuthorizationPolicyBuilder(JwtBearerDefaults.AuthenticationScheme).RequireAuthenticatedUser().Build();
            });
            var allowedOrigins = appSettingsSection.AllowedOriginList;
            
            services.AddDbContext<AltitudeContext>(
                options => options.UseSqlServer(configuration.GetConnectionString("AltitudeContext")));
            
            services.AddCors(options => options.AddPolicy("EnableCORS", build =>
            {
                build
                    .AllowAnyMethod()
                    .AllowAnyHeader()
                    .WithOrigins(allowedOrigins.ToString())
                    .AllowCredentials();
            }));
            
            var app = builder.Build();
            // app.MapControllers();
            app.UseRouting();
            app.UseCors("EnableCORS");
            app.UseAuthentication();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGraphQL();
            });
            app.Run();
        }
    }
}

这是验证码。

using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using altitude_api.Entities;
using altitude_api.Models;
using altitude_api.Services.Interfaces;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Novell.Directory.Ldap;
using Serilog;


namespace altitude_api.Services;

public class AuthService: IAuthService
{

    private readonly AltitudeContext _altitudeContext;
    private readonly AppSettings appSettings;
    public AuthService(AltitudeContext altitudeContext, IOptions<AppSettings> _appSettings)
    {
        _altitudeContext = altitudeContext;
        appSettings = _appSettings.Value;
    }

    public UserWithToken AuthenticateUser(string userName, string password)
    {
        var isValid = false;

        var port = appSettings.LdapPort;

        Log.Information($"Beginning to authenticate user {userName}");

        using (var cn = new LdapConnection())
        {
            // connect

            try
            {
                cn.Connect(appSettings.LdapServer, Convert.ToInt32(port));
                cn.Bind(appSettings.ldapDomain + userName, password);
                if (cn.Bound)
                {
                    isValid = true;
                    Log.Information($"Successfully found user in LDAP {userName}");
                }
            }
            catch (Exception ex)
            {
                Log.Error( ex,$"Error looking up {userName} in LDAP.");
                throw ex;
            }
        }

        return isValid ? GetToken(userName) : throw new Exception("Unable to authenticate user at this time");
    }


    public Users GetUser()
    {
        return _altitudeContext.Users.First(p => p.Username == "maxwell.sands");
    }

    public UserWithToken GetToken(string userName)
    {

        try
        {
            var roles = _altitudeContext.Roles;
            
            var dbUser = _altitudeContext.Users.FirstOrDefault(p => p != null && p.Username == userName);
            
            if (dbUser == null)
            {
                var ex = new Exception("User not found");
                Log.Error(ex, "User is not found could not authenticate");
                throw ex;
            }
            if(dbUser.ExpiryDttm < DateTime.Now)
            {
                var ex = new Exception("ERROR: User access expired.");
                Log.Error(ex, "User is expired could not authenticate");
                throw ex;
            }
             var role = (from rle in _altitudeContext.Roles
                 join m in _altitudeContext.UserRoles on rle.RoleId equals m.RoleId
                 join usr in _altitudeContext.Users on m.UserId equals usr.UserId
                 where usr.Username.ToLower() == dbUser.Username.ToLower()
                 select rle.RoleName).FirstOrDefault();
             
             if (role == null)
             {
                 var ex = new Exception("Role not found");
                 Log.Error(ex, "User is expired could not authenticate");
                 throw ex;
             }
             

             var secret = appSettings.SecretKey;
             
             // authentication successful so generate jwt token
             var tokenHandler = new JwtSecurityTokenHandler();
             var key = Encoding.ASCII.GetBytes(secret);
             var tokenDescriptor = new SecurityTokenDescriptor
             {
                 Subject = new ClaimsIdentity(new Claim[]
                 {
                     new Claim(ClaimTypes.Name, userName),
                     new Claim(ClaimTypes.Role, role),
                 }),
                 Expires = DateTime.UtcNow.AddDays(1),
                 SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256)
             };
             var token = tokenHandler.CreateToken(tokenDescriptor);
     
             UserWithToken webuser = new UserWithToken();
     
             webuser.UserName = dbUser.Username;
             webuser.FirstName = dbUser.FirstName;
             webuser.LastName = dbUser.LastName;
             webuser.Id = dbUser.UserId;
             webuser.Role = role;
             webuser.Token = tokenHandler.WriteToken(token);
             Log.Information($"{webuser.FirstName} {webuser.LastName} was successfully logged in.");
             return webuser;   
                    
        }
        catch(Exception e)
        {
            Log.Information(e, "There was an error loading the user");
            throw new Exception("There was an issue loading the user", e);
        }
    }
}

最后这是我需要授权的端点。

using HotChocolate.AspNetCore.Authorization;

namespace altitude_api.Queries;

[ExtendObjectType(typeof(Query))]
public class HealthQuery
{
    [Authorize]
    public bool HeartBeat()
    {
        return true;
    }
}
c# .net graphql authorization hotchocolate
2个回答
1
投票

回应@Arjav Dave,是的,我确实找到了这个问题的答案,几乎是运气不好。 Program.cs 中的顺序非常重要。我的代码如下所示...

    builder.Services
 .AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
    {
        // omitted
    }).Services
    .AddAuthorization();

builder.Services
    .AddGraphQLServer()
    .AddAuthorization()
    .AddFiltering()
    .AddSorting()
    .AddQueryType<Query>()

当我设置应用程序时,它看起来像......

var app = builder.Build();
app.UseRouting();
app.UseCors("EnableCORS");
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
    endpoints.MapGraphQL();
});
app.Run();

希望有帮助。


0
投票

不知何故最新需要添加两次授权

下面是我的应用程序的工作代码

services
.AddCors()
.AddAuthorization();

services
.AddGraphQLServer()
.AddAuthorization()
.AddFiltering<ExtendedFilterConvention>()
.AddSorting()
.AddQueryType<Query>()
.AddMutationType<Mutation>()
© www.soinside.com 2019 - 2024. All rights reserved.