添加授权鉴权模块

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

@@ -3,14 +3,17 @@ 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.Authentication.JwtBearer
namespace Yi.Framework.Auth.JwtBearer.Authentication
{
public class YiJwtAuthenticationHandler : IAuthenticationHandler
{
public YiJwtAuthenticationHandler()
private JwtTokenManager _jwtTokenManager;
public YiJwtAuthenticationHandler(JwtTokenManager jwtTokenManager)
{
_jwtTokenManager = jwtTokenManager;
}
public const string YiJwtSchemeName = "YiJwtAuth";
@@ -36,13 +39,26 @@ namespace Yi.Framework.Authentication.JwtBearer
/// <param name="name"></param>
/// <param name="role"></param>
/// <returns></returns>
private AuthenticationTicket GetAuthTicket(string name, string role)
private AuthenticationTicket GetAuthTicket(IDictionary<string, object> dicClaims)
{
var claimsIdentity = new ClaimsIdentity(new Claim[]
List<Claim> claims = new List<Claim>();
foreach (var claim in dicClaims)
{
new Claim(ClaimTypes.Name, name),
new Claim(ClaimTypes.Role, role),
}, YiJwtSchemeName);
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);
}
@@ -54,29 +70,34 @@ namespace Yi.Framework.Authentication.JwtBearer
/// <exception cref="NotImplementedException"></exception>
public Task<AuthenticateResult> AuthenticateAsync()
{
AuthenticateResult result;
AuthenticateResult result = AuthenticateResult.Fail("未发现授权令牌");
_context.Request.Headers.TryGetValue("Authorization", out StringValues values);
string valStr = values.ToString();
if (!string.IsNullOrWhiteSpace(valStr))
{
//认证模拟basic认证cusAuth YWRtaW46YWRtaW4=
string[] authVal = System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(valStr.Substring(YiJwtSchemeName.Length + 1))).Split(':');
//var loginInfo = new Dto.LoginDto() { Username = authVal[0], Password = authVal[1] };
//var validVale = _userService.IsValid(loginInfo);
bool validVale = true;
if (!validVale)
result = AuthenticateResult.Fail("未登陆");
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
{
//这里应该将token进行效验然后加入解析的claim中即可
var ticket = GetAuthTicket("cc", "admin");
result = AuthenticateResult.Success(ticket);
result = AuthenticateResult.Fail("授权令牌格式错误");
}
}
else
{
result = AuthenticateResult.Fail("未登陆");
}
return Task.FromResult(result);
}

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

@@ -6,8 +6,13 @@
<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>();
});
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Core.Const
{
public class TokenTypeConst
{
public const string Id = nameof(Id);
public const string UserName= nameof(UserName);
public const string TenantId = nameof(TenantId);
public const string Email = nameof(Email);
public const string PhoneNumber = nameof(PhoneNumber);
public const string Roles = nameof(Roles);
public const string Permission = nameof(Permission);
}
}

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Core.CurrentUser
namespace Yi.Framework.Core.CurrentUsers
{
public class CurrentUser : ICurrentUser
{

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Yi.Framework.Core.CurrentUser
namespace Yi.Framework.Core.CurrentUsers
{
public interface ICurrentUser
{

View File

@@ -0,0 +1,52 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using Yi.Framework.Core.Enums;
namespace Yi.Framework.Core.Exceptions
{
public class AuthException : Exception,
IHasErrorCode,
IHasErrorDetails,
IHasLogLevel
{
public ResultCodeEnum Code { get; set; }
public string? Details { get; set; }
public LogLevel LogLevel { get; set; }
public AuthException(
ResultCodeEnum code = ResultCodeEnum.NoPermission,
string? message = null,
string? details = null,
Exception? innerException = null,
LogLevel logLevel = LogLevel.Warning)
: base(message, innerException)
{
Code = code;
Details = details;
LogLevel = logLevel;
}
/// <summary>
/// 序列化参数的构造函数
/// </summary>
public AuthException(SerializationInfo serializationInfo, StreamingContext context)
: base(serializationInfo, context)
{
}
public AuthException WithData(string name, object value)
{
Data[name] = value;
return this;
}
}
}

View File

@@ -9,7 +9,8 @@ using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
using Yi.Framework.Core.CurrentUser;
using Yi.Framework.Core.Const;
using Yi.Framework.Core.CurrentUsers;
namespace Yi.Framework.Core.Extensions
{
@@ -17,7 +18,7 @@ namespace Yi.Framework.Core.Extensions
{
public static IServiceCollection AddCurrentUserServer(this IServiceCollection services)
{
return services.AddScoped<ICurrentUser, CurrentUser.CurrentUser>();
return services.AddScoped<ICurrentUser, CurrentUser>();
}
@@ -51,10 +52,10 @@ namespace Yi.Framework.Core.Extensions
var claims = authenticateContext.Principal.Claims;
//通过鉴权之后,开始赋值
_currentUser.IsAuthenticated = true;
//_currentUser.Id = claims.GetClaim(JwtRegisteredClaimNames.Sid) is null ? 0 : Convert.ToInt64(claims.GetClaim(JwtRegisteredClaimNames.Sid));
//_currentUser.UserName = claims.GetClaim(SystemConst.UserName) ?? "";
//_currentUser.Permission = claims.GetClaims(SystemConst.PermissionClaim);
//_currentUser.TenantId = claims.GetClaim(SystemConst.TenantId) is null ? null : Guid.Parse(claims.GetClaim(SystemConst.TenantId)!);
_currentUser.Id = claims.GetClaim(TokenTypeConst.Id) is null ? 0 : Convert.ToInt64(claims.GetClaim(TokenTypeConst.Id));
_currentUser.UserName = claims.GetClaim(TokenTypeConst.UserName) ?? "";
_currentUser.Permission = claims.GetClaims(TokenTypeConst.Permission);
_currentUser.TenantId = claims.GetClaim(TokenTypeConst.TenantId) is null ? null : Guid.Parse(claims.GetClaim(TokenTypeConst.TenantId)!);
await _next(context);
}

View File

@@ -8,7 +8,7 @@ namespace Yi.Framework.Core.Model
{
public static class ServiceLocatorModel
{
public static IServiceProvider? Instance { get; set; }
public static IServiceProvider Instance { get; set; }
}
}

View File

@@ -8,6 +8,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Yi.Framework.Core.Configuration;
using Yi.Framework.Core.CurrentUsers;
using Yi.Framework.Core.Extensions;
using Yi.Framework.Core.Model;
@@ -22,6 +23,8 @@ namespace Yi.Framework.Core
//全局错误,需要靠前,放在此处无效
//app.UseErrorHandlingServer();
app.UseCurrentUserServer();
}
public void ConfigureServices(IServiceCollection services, ConfigureServicesContext context)
@@ -31,6 +34,7 @@ namespace Yi.Framework.Core
//全盘扫描,自动依赖注入
services.AddAutoIocServer();
services.AddCurrentUserServer();
//全局日志
GobalLogModel.SqlLogEnable = Appsettings.appBool("SqlLog_Enable");
GobalLogModel.LoginCodeEnable = Appsettings.appBool("LoginCode_Enable");

View File

@@ -0,0 +1,7 @@
namespace Yi.Framework.Data
{
public class Class1
{
}
}

View File

@@ -0,0 +1,106 @@
//using System;
//using System.Collections.Concurrent;
//using System.Collections.Generic;
//using System.Linq;
//using System.Text;
//using System.Threading.Tasks;
//namespace Yi.Framework.Data
//{
// public class DataFilter : IDataFilter
// {
// private readonly ConcurrentDictionary<Type, object> _filters;
// private readonly IServiceProvider _serviceProvider;
// public DataFilter(IServiceProvider serviceProvider)
// {
// _serviceProvider = serviceProvider;
// _filters = new ConcurrentDictionary<Type, object>();
// }
// public IDisposable Enable<TFilter>()
// where TFilter : class
// {
// return GetFilter<TFilter>().Enable();
// }
// public IDisposable Disable<TFilter>()
// where TFilter : class
// {
// return GetFilter<TFilter>().Disable();
// }
// public bool IsEnabled<TFilter>()
// where TFilter : class
// {
// return GetFilter<TFilter>().IsEnabled;
// }
// private IDataFilter<TFilter> GetFilter<TFilter>()
// where TFilter : class
// {
// return _filters.GetOrAdd(
// typeof(TFilter),
// valueFactory: (k) => _serviceProvider.GetRequiredService<IDataFilter<TFilter>>()
// ) as IDataFilter<TFilter>;
// }
// }
// public class DataFilter<TFilter> : IDataFilter<TFilter>
// where TFilter : class
// {
// public bool IsEnabled
// {
// get
// {
// EnsureInitialized();
// return _filter.Value.IsEnabled;
// }
// }
// private readonly AbpDataFilterOptions _options;
// private readonly AsyncLocal<DataFilterState> _filter;
// public DataFilter(IOptions<AbpDataFilterOptions> options)
// {
// _options = options.Value;
// _filter = new AsyncLocal<DataFilterState>();
// }
// public IDisposable Enable()
// {
// if (IsEnabled)
// {
// return NullDisposable.Instance;
// }
// _filter.Value.IsEnabled = true;
// return new DisposeAction(() => Disable());
// }
// public IDisposable Disable()
// {
// if (!IsEnabled)
// {
// return NullDisposable.Instance;
// }
// _filter.Value.IsEnabled = false;
// return new DisposeAction(() => Enable());
// }
// private void EnsureInitialized()
// {
// if (_filter.Value != null)
// {
// return;
// }
// _filter.Value = _options.DefaultStates.GetOrDefault(typeof(TFilter))?.Clone() ?? new DataFilterState(true);
// }
// }
//}

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.Data
//{
// public interface IDataFilter<TFilter>
// where TFilter : class
// {
// IDisposable Enable();
// IDisposable Disable();
// bool IsEnabled { get; }
// }
// public interface IDataFilter
// {
// IDisposable Enable<TFilter>()
// where TFilter : class;
// IDisposable Disable<TFilter>()
// where TFilter : class;
// bool IsEnabled<TFilter>()
// where TFilter : class;
// }
//}

View File

@@ -0,0 +1,9 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
</Project>