簡體   English   中英

NET 6 WebAPI 與 Hotchocolate GraphQL 未授權

[英]NET 6 WebAPI with Hotchocolate GraphQL not authorizing

我有一個 .NET 6 api,我正在使用 HotChocolate 12.7。我的查詢有效,但是當我嘗試將 [Authorize] 裝飾器添加到查詢中並使用 Bearer 令牌發送請求時,我得到了未經授權的響應. 我無法讓它識別經過正確身份驗證的用戶的 JWT。

這是 Program.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;
    }
}

作為對@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();

希望有所幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM