GraphQL 既是一種用于 API 的查詢語言也是一個滿足你數據查詢的運行時。GraphQL 對你的 API 中的數據提供了一套易于理解的完整描述,使得客戶端能夠準確地獲得它需要的數據,而且沒有任何冗余,也讓 API 更容易地隨著時間推移而演進,還能用于構建強大的開發者工具。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??——出自?https://graphql.cn
由于HotChocklate是是基于asp.net core框架,所以授權策略與原生的asp.net core mvc項目大同小異,都是通過固定角色,自定義策略等方式來進行的。下面的例子就是通過一個自定義策略的例子來進行的。
并且授權是在實體類和實體類的屬性上的,而不是像之前在Controller的Action上,對應的是一個api的url。
看實例了解詳情:
添加Nuget包
HotChocolate.AspNetCore
HotChocolate.Data
HotChocolate.Data.EntityFramework
HotChocolate.AspNetCore.Authorization
權限類
namespace GraphQLDemo02
{/// <summary>/// 用戶或角色或其他憑據實體/// </summary>public class Permission{/// <summary>/// 用戶或角色或其他憑據名稱/// </summary>public virtual string Name{ get; set; }/// <summary>/// 請求Url/// </summary>public virtual string Url{ get; set; }}
}
創建自定義策略處理類
using Microsoft.AspNetCore.Authorization;
using System.Threading.Tasks;
using System;namespace GraphQLDemo02
{/// <summary>/// 權限授權Handler/// </summary>public class PermissionHandler : AuthorizationHandler<PermissionRequirement>{/// <summary>/// 驗證權限/// </summary>/// <param name="context"></param>/// <param name="requirement"></param>/// <returns></returns>protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, PermissionRequirement requirement)
{//這里可以過濾屬性授權Console.WriteLine(context.Resource.GetType().GetProperty("Path").GetValue(context.Resource));//是否經過驗證var isAuthenticated = context?.User?.Identity?.IsAuthenticated;if (isAuthenticated.HasValue && isAuthenticated.Value){context.Succeed(requirement);}else{context.Fail();}return Task.CompletedTask;}}
}
AuthorizationRequirement類
using Microsoft.AspNetCore.Authorization;
using Microsoft.IdentityModel.Tokens;
using System;namespace GraphQLDemo02
{/// <summary>/// 必要參數類/// </summary>public class PermissionRequirement : IAuthorizationRequirement{ /// <summary>/// 認證授權類型/// </summary>public string ClaimType { internal get; set; }/// <summary>/// 發行人/// </summary>public string Issuer { get; set; }/// <summary>/// 訂閱人/// </summary>public string Audience { get; set; }/// <summary>/// 過期時間/// </summary>public TimeSpan Expiration { get; set; }/// <summary>/// 簽名驗證/// </summary>public SigningCredentials SigningCredentials { get; set; } /// <summary>/// 構造/// </summary>/// <param name="deniedAction">拒約請求的url</param>/// <param name="permissions">權限集合</param>/// <param name="claimType">聲明類型</param>/// <param name="issuer">發行人</param>/// <param name="audience">訂閱人</param>/// <param name="signingCredentials">簽名驗證實體</param>public PermissionRequirement(string claimType, string issuer, string audience, SigningCredentials signingCredentials, TimeSpan expiration){ClaimType = claimType; Issuer = issuer;Audience = audience;Expiration = expiration;SigningCredentials = signingCredentials;}}
}
Token生成類
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;namespace GraphQLDemo02
{public class JwtToken{/// <summary>/// 獲取基于JWT的Token/// </summary>/// <param name="username"></param>/// <returns></returns>public static dynamic BuildJwtToken(Claim[] claims, PermissionRequirement permissionRequirement){var now = DateTime.UtcNow;var jwt = new JwtSecurityToken(issuer: permissionRequirement.Issuer,audience: permissionRequirement.Audience,claims: claims,notBefore: now,expires: now.Add(permissionRequirement.Expiration),signingCredentials: permissionRequirement.SigningCredentials);var encodedJwt = new JwtSecurityTokenHandler().WriteToken(jwt);var response = new{Status = true,access_token = encodedJwt,expires_in = permissionRequirement.Expiration.TotalMilliseconds,token_type = "Bearer"};return response;}}
}
Starup.cs
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Security.Claims;
using System.Text;
using Microsoft.EntityFrameworkCore;namespace GraphQLDemo02
{public class Startup{public IConfiguration Configuration { get; }public Startup(IConfiguration configuration){Configuration = configuration;}public void ConfigureServices(IServiceCollection services){ services.AddPooledDbContextFactory<AdventureWorks2016Context>((services, options) => options.UseSqlServer(Configuration.GetConnectionString("ConnectionString")).UseLoggerFactory(services.GetRequiredService<ILoggerFactory>())).AddGraphQLServer().AddAuthorization().AddQueryType<Query>().AddFiltering().AddSorting().AddProjections();AddAuth(services);}public void Configure(IApplicationBuilder app, IWebHostEnvironment env){if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseAuthentication();app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapGraphQL();});}void AddAuth(IServiceCollection services){//讀取配置文件var audienceConfig = Configuration.GetSection("Audience");var symmetricKeyAsBase64 = audienceConfig["Secret"];var keyByteArray = Encoding.ASCII.GetBytes(symmetricKeyAsBase64);var signingKey = new SymmetricSecurityKey(keyByteArray);var tokenValidationParameters = new TokenValidationParameters{ValidateIssuerSigningKey = true,IssuerSigningKey = signingKey,ValidateIssuer = true,ValidIssuer = audienceConfig["Issuer"],ValidateAudience = true,ValidAudience = audienceConfig["Audience"],ValidateLifetime = true,ClockSkew = TimeSpan.Zero,RequireExpirationTime = true,};var signingCredentials = new SigningCredentials(signingKey, SecurityAlgorithms.HmacSha256);//如果第三個參數,是ClaimTypes.Role,上面集合的每個元素的Name為角色名稱,如果ClaimTypes.Name,即上面集合的每個元素的Name為用戶名var permissionRequirement = new PermissionRequirement( ClaimTypes.Role,audienceConfig["Issuer"],audienceConfig["Audience"],signingCredentials,expiration: TimeSpan.FromSeconds(1000000)//設置Token過期時間);services.AddAuthorization(options =>{options.AddPolicy("Permission", policy => policy.AddRequirements(permissionRequirement));}).AddAuthentication(options =>{options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;}).AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =>{//不使用httpso.RequireHttpsMetadata = false;o.TokenValidationParameters = tokenValidationParameters;});//注入授權Handlerservices.AddSingleton<IAuthorizationHandler, PermissionHandler>();services.AddSingleton(permissionRequirement);}}
}
Query.cs
using HotChocolate;
using HotChocolate.Data;
using HotChocolate.Types;
using System;
using System.Linq;
using System.Security.Claims;namespace GraphQLDemo02
{ public class Query{[UseDbContext(typeof(AdventureWorks2016Context))][UseOffsetPaging][UseProjection][UseFiltering][UseSorting]public IQueryable<Product> GetProducts([ScopedService] AdventureWorks2016Context context){return context.Products;}[UseDbContext(typeof(AdventureWorks2016Context))][UsePaging][UseProjection][UseFiltering][UseSorting]public IQueryable<Person> GetPersons([ScopedService] AdventureWorks2016Context context){return context.People;}public TokenModel Login(string username, string password, [Service] PermissionRequirement requirement){Console.WriteLine(username);var isValidated = username == "gsw" && password == "111111";if (!isValidated){return new TokenModel(){Result = false,Message = "認證失敗"};}else{//如果是基于用戶的授權策略,這里要添加用戶;如果是基于角色的授權策略,這里要添加角色var claims = new Claim[] {new Claim(ClaimTypes.Name, username),new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(200000).ToString())};var token = JwtToken.BuildJwtToken(claims, requirement);return new TokenModel(){Result = true,Data = token.access_token};}} }
}
HotChocklate的驗證是通過在實體類或實體類的屬性上加自定義策略來對數據進行權限控制,所以下面的例子是加在實體類上,全部屬性進行授權驗證。
Product.cs
[HotChocolate.AspNetCore.Authorization.Authorize(Policy = "Permission")]public partial class Product{//此處略n多行}
Person.cs
[Authorize(Policy = "Permission")]public partial class Person{//此處略n多行}
運行結果:
未驗證
登錄
拿到token后,在header上追加驗證token信息,再次訪問,成功獲取數據
總結
以上是生活随笔為你收集整理的GraphQL:验证与授权的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。