添加授权鉴权模块

This commit is contained in:
橙子
2023-01-19 15:35:50 +08:00
parent fc74a000a6
commit f88655e214
33 changed files with 496 additions and 46 deletions

View File

@@ -0,0 +1,100 @@
using JWT;
using JWT.Algorithms;
using JWT.Builder;
using JWT.Exceptions;
using Microsoft.Extensions.Options;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Yi.Framework.Auth.JwtBearer.Authentication.Options;
using Yi.Framework.Core.Helper;
namespace Yi.Framework.Auth.JwtBearer.Authentication
{
public class JwtTokenManager
{
private JwtTokenOptions _jwtTokenOptions;
public JwtTokenManager(IOptions<JwtTokenOptions> options)
{
_jwtTokenOptions = options.Value;
}
public string CreateToken(Dictionary<string, object>? claimDic)
{
var token = JwtBuilder.Create()
.WithAlgorithm(new RS256Algorithm(RSAFileHelper.GetKey(), RSAFileHelper.GetKey()))
.AddClaim(ClaimName.Issuer, _jwtTokenOptions.Issuer)
.AddClaim(ClaimName.Audience, _jwtTokenOptions.Audience)
.AddClaim(ClaimName.Subject, _jwtTokenOptions.Subject)
.AddClaim(ClaimName.IssuedAt, UnixEpoch.GetSecondsSince(new DateTimeOffset(DateTime.UtcNow)))
.ExpirationTime(DateTime.Now.AddSeconds(_jwtTokenOptions.ExpSecond));
if (claimDic is not null)
{
foreach (var d in claimDic)
{
token.AddClaim(d.Key, d.Value);
};
}
return token.Encode();
}
public IDictionary<string, object>? VerifyToken(string token, TokenVerifyErrorAction tokenVerifyErrorAction)
{
IDictionary<string, object>? claimDic = null;
try
{
claimDic = JwtBuilder.Create()
.WithAlgorithm(new RS256Algorithm(RSAFileHelper.GetPublicKey()))
.WithValidationParameters(ValidationParameters.Default)
.Decode<IDictionary<string, object>>(token);
}
catch (TokenNotYetValidException ex)
{
if (tokenVerifyErrorAction.TokenNotYetValidAction is not null)
{
tokenVerifyErrorAction.TokenNotYetValidAction(ex);
}
//Console.WriteLine("Token错误");
}
catch (TokenExpiredException ex)
{
if (tokenVerifyErrorAction.TokenExpiredAction is not null)
{
tokenVerifyErrorAction.TokenExpiredAction(ex);
}
//Console.WriteLine("Token过期");
}
catch (SignatureVerificationException ex)
{
if (tokenVerifyErrorAction.SignatureVerificationAction is not null)
{
tokenVerifyErrorAction.SignatureVerificationAction(ex);
}
//Console.WriteLine("Token无效");
}
catch (Exception ex)
{
if (tokenVerifyErrorAction.ErrorAction is not null)
{
tokenVerifyErrorAction.ErrorAction(ex);
}
//Console.WriteLine("Token内部错误json序列化");
}
return claimDic;
}
public class TokenVerifyErrorAction
{
public Action<TokenNotYetValidException>? TokenNotYetValidAction { get; set; }
public Action<TokenExpiredException>? TokenExpiredAction { get; set; }
public Action<SignatureVerificationException>? SignatureVerificationAction { get; set; }
public Action<Exception>? ErrorAction { get; set; }
}
}
}

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Auth.JwtBearer.Authentication.Options
{
public class JwtTokenOptions
{
/// <summary>
/// 听众
/// </summary>
public string Audience { get; set; } = string.Empty;
/// <summary>
/// 发行者
/// </summary>
public string Issuer { get; set; } = string.Empty;
/// <summary>
/// 主题
/// </summary>
public string Subject { get; set; } = string.Empty;
/// <summary>
/// 过期时间,单位秒
/// </summary>
public long ExpSecond { get; set; }
}
}

View File

@@ -0,0 +1,132 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Primitives;
using System.Net;
using System.Security.Claims;
using System.Text.Json;
using Yi.Framework.Core.Helper;
namespace Yi.Framework.Auth.JwtBearer.Authentication
{
public class YiJwtAuthenticationHandler : IAuthenticationHandler
{
private JwtTokenManager _jwtTokenManager;
public YiJwtAuthenticationHandler(JwtTokenManager jwtTokenManager)
{
_jwtTokenManager = jwtTokenManager;
}
public const string YiJwtSchemeName = "YiJwtAuth";
private AuthenticationScheme _scheme;
private HttpContext _context;
/// <summary>
/// 初始化数据
/// </summary>
/// <param name="scheme"></param>
/// <param name="context"></param>
/// <returns></returns>
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_scheme = scheme;
_context = context;
return Task.CompletedTask;
}
/// <summary>
/// 生成认证票据
/// </summary>
/// <param name="name"></param>
/// <param name="role"></param>
/// <returns></returns>
private AuthenticationTicket GetAuthTicket(IDictionary<string, object> dicClaims)
{
List<Claim> claims = new List<Claim>();
foreach (var claim in dicClaims)
{
var p = (JsonElement)claim.Value;
string? resp=null;
switch (p.ValueKind)
{
case JsonValueKind.String:
resp = p.GetString();
break;
case JsonValueKind.Number:
resp = p.GetInt32().ToString();
break;
}
claims.Add(new Claim(claim.Key, resp ?? ""));
}
var claimsIdentity = new ClaimsIdentity(claims.ToArray(), YiJwtSchemeName);
var principal = new ClaimsPrincipal(claimsIdentity);
return new AuthenticationTicket(principal, _scheme.Name);
}
/// <summary>
/// 处理操作
/// </summary>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public Task<AuthenticateResult> AuthenticateAsync()
{
AuthenticateResult result = AuthenticateResult.Fail("未发现授权令牌");
_context.Request.Headers.TryGetValue("Authorization", out StringValues values);
string valStr = values.ToString();
if (!string.IsNullOrWhiteSpace(valStr))
{
var tokenHeader = valStr.Substring(0, 6);
if (tokenHeader == "Bearer")
{
var token = valStr.Substring(7);
var claimDic = _jwtTokenManager.VerifyToken(token, new JwtTokenManager.TokenVerifyErrorAction()
{
TokenExpiredAction = (ex) => { result = AuthenticateResult.Fail("Token过期"); },
SignatureVerificationAction = (ex) => { result = AuthenticateResult.Fail("Token效验失效"); },
TokenNotYetValidAction = (ex) => { result = AuthenticateResult.Fail("Token完全错误"); },
ErrorAction = (ex) => { result = AuthenticateResult.Fail("Token内部错误"); }
});
if (claimDic is not null)
{
//成功
result = AuthenticateResult.Success(GetAuthTicket(claimDic));
}
}
else
{
result = AuthenticateResult.Fail("授权令牌格式错误");
}
}
return Task.FromResult(result);
}
/// <summary>
/// 未登录时的处理
/// </summary>
/// <param name="properties"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public Task ChallengeAsync(AuthenticationProperties? properties)
{
_context.Request.Headers.TryGetValue("Authorization", out StringValues values);
_context.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
return Task.CompletedTask;
}
/// <summary>
/// 权限不足的处理
/// </summary>
/// <param name="properties"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public Task ForbidAsync(AuthenticationProperties? properties)
{
_context.Response.StatusCode = (int)HttpStatusCode.Forbidden;
return Task.CompletedTask;
}
}
}

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Yi.Framework.Core.CurrentUsers;
namespace Yi.Framework.Auth.JwtBearer.Authorization
{
public class DefaultPermissionHandler : IPermissionHandler
{
public bool IsPass(string permission, ICurrentUser currentUser)
{
return true;
}
}
}

View File

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Yi.Framework.Core.CurrentUsers;
namespace Yi.Framework.Auth.JwtBearer.Authorization
{
public interface IPermissionHandler
{
bool IsPass(string permission,ICurrentUser currentUser);
}
}

View File

@@ -0,0 +1,47 @@
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Yi.Framework.Core.CurrentUsers;
using Yi.Framework.Core.Exceptions;
using Yi.Framework.Core.Model;
namespace Yi.Framework.Auth.JwtBearer.Authorization
{
[AttributeUsage(AttributeTargets.Method)]
public class PermissionAttribute : ActionFilterAttribute
{
private string Permission { get; set; }
public PermissionAttribute(string permission)
{
this.Permission = permission;
}
/// <summary>
/// 动作鉴权
/// </summary>
/// <param name="context"></param>
/// <exception cref="Exception"></exception>
public override void OnActionExecuting(ActionExecutingContext context)
{
var permissionHandler = ServiceLocatorModel.Instance.GetRequiredService<IPermissionHandler>();
var currentUser = ServiceLocatorModel.Instance.GetRequiredService<ICurrentUser>();
var result = permissionHandler.IsPass(Permission, currentUser);
if (!result)
{
throw new AuthException(message: "您无权限访问该接口");
}
}
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="JWT" Version="10.0.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Yi.Framework.AspNetCore\Yi.Framework.AspNetCore.csproj" />
<ProjectReference Include="..\Yi.Framework.Core\Yi.Framework.Core.csproj" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,41 @@
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using StartupModules;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Yi.Framework.Auth.JwtBearer.Authentication;
using Yi.Framework.Auth.JwtBearer.Authentication.Options;
using Yi.Framework.Auth.JwtBearer.Authorization;
using Yi.Framework.Core;
using Yi.Framework.Core.Attributes;
using Yi.Framework.Core.Configuration;
namespace Yi.Framework.Auth.JwtBearer
{
[DependsOn(typeof(YiFrameworkCoreModule))]
public class YiFrameworkAuthJwtBearerModule : IStartupModule
{
public void Configure(IApplicationBuilder app, ConfigureMiddlewareContext context)
{
}
public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context)
{
services.Configure<JwtTokenOptions>(Appsettings.appConfiguration("JwtTokenOptions"));
services.AddAuthentication(YiJwtAuthenticationHandler.YiJwtSchemeName);
services.AddTransient<JwtTokenManager>();
services.AddSingleton<IPermissionHandler, DefaultPermissionHandler>();
services.AddAuthentication(option =>
{
option.AddScheme<YiJwtAuthenticationHandler>(YiJwtAuthenticationHandler.YiJwtSchemeName, YiJwtAuthenticationHandler.YiJwtSchemeName);
});
services.AddSingleton<PermissionAttribute>(_=>new PermissionAttribute(string.Empty));
services.AddControllers(options => {
options.Filters.Add<PermissionAttribute>();
});
}
}
}