diff --git a/NewLife.Remoting.Extensions/Controllers/BaseController.cs b/NewLife.Remoting.Extensions/Controllers/BaseController.cs
index 8328469..0bebf4c 100644
--- a/NewLife.Remoting.Extensions/Controllers/BaseController.cs
+++ b/NewLife.Remoting.Extensions/Controllers/BaseController.cs
@@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc.Filters;
using NewLife.Log;
using NewLife.Reflection;
using NewLife.Remoting.Extensions.Services;
+using NewLife.Remoting.Services;
using NewLife.Serialization;
using NewLife.Web;
using IWebFilter = Microsoft.AspNetCore.Mvc.Filters.IActionFilter;
@@ -16,13 +17,15 @@ namespace NewLife.Remoting.Extensions;
/// <remarks>
/// 提供统一的令牌解码验证架构
/// </remarks>
+/// <remarks>实例化</remarks>
+/// <param name="serviceProvider"></param>
[ApiFilter]
[Route("[controller]")]
-public abstract class BaseController : ControllerBase, IWebFilter, ILogProvider
+public abstract class BaseController(IServiceProvider serviceProvider) : ControllerBase, IWebFilter, ILogProvider
{
#region 属性
/// <summary>令牌</summary>
- public String? Token { get; private set; }
+ public String? Token { get; set; }
/// <summary>令牌对象</summary>
public JwtBuilder Jwt { get; set; } = null!;
@@ -30,23 +33,17 @@ public abstract class BaseController : ControllerBase, IWebFilter, ILogProvider
/// <summary>用户主机</summary>
public String UserHost { get; set; } = null!;
+ private readonly ITokenService _tokenService = serviceProvider.GetRequiredService<ITokenService>();
private IDictionary<String, Object?>? _args;
- private readonly TokenService _tokenService;
private static Action<String>? _setip;
#endregion
#region 构造
static BaseController()
{
+ // 反射获取ManageProvider.UserHost的Set方法,避免直接引用XCode
_setip = "ManageProvider".GetTypeEx()?.GetPropertyEx("UserHost")?.SetMethod?.CreateDelegate<Action<String>>();
}
-
- /// <summary>实例化</summary>
- /// <param name="serviceProvider"></param>
- public BaseController(IServiceProvider serviceProvider)
- {
- _tokenService = serviceProvider.GetRequiredService<TokenService>();
- }
#endregion
#region 令牌验证
@@ -63,11 +60,10 @@ public abstract class BaseController : ControllerBase, IWebFilter, ILogProvider
try
{
-
if (context.ActionDescriptor is ControllerActionDescriptor act && !act.MethodInfo.IsDefined(typeof(AllowAnonymousAttribute)))
{
// 匿名访问接口无需验证。例如星尘Node的SendCommand接口,并不使用Node令牌,而是使用App令牌
- var rs = !token.IsNullOrEmpty() && OnAuthorize(token);
+ var rs = OnAuthorize(token, context);
if (!rs) throw new ApiException(ApiCode.Unauthorized, "认证失败");
}
}
@@ -91,10 +87,20 @@ public abstract class BaseController : ControllerBase, IWebFilter, ILogProvider
/// <summary>验证令牌,并获取Jwt对象,子类可借助Jwt.Subject获取设备</summary>
/// <param name="token"></param>
/// <returns></returns>
- protected virtual Boolean OnAuthorize(String token)
+ [Obsolete("=>OnAuthorize(String token, ActionExecutingContext context)", true)]
+ protected virtual Boolean OnAuthorize(String token) => OnAuthorize(token, null!);
+
+ /// <summary>验证令牌,并获取Jwt对象,子类可借助Jwt.Subject获取设备</summary>
+ /// <param name="token">访问令牌</param>
+ /// <param name="context"></param>
+ /// <returns></returns>
+ protected virtual Boolean OnAuthorize(String token, ActionContext context)
{
- var jwt = _tokenService.DecodeToken(token);
+ if (token.IsNullOrEmpty()) return false;
+
+ var (jwt, ex) = _tokenService.DecodeToken(token);
Jwt = jwt;
+ if (ex != null) throw ex;
return jwt != null;
}
diff --git a/NewLife.Remoting.Extensions/Controllers/BaseDeviceController.cs b/NewLife.Remoting.Extensions/Controllers/BaseDeviceController.cs
index e61f3b2..d318532 100644
--- a/NewLife.Remoting.Extensions/Controllers/BaseDeviceController.cs
+++ b/NewLife.Remoting.Extensions/Controllers/BaseDeviceController.cs
@@ -18,7 +18,7 @@ public abstract class BaseDeviceController : BaseController
protected IDeviceModel _device = null!;
private readonly IDeviceService _deviceService;
- private readonly TokenService _tokenService;
+ private readonly ITokenService _tokenService;
private readonly ISessionManager _sessionManager;
private readonly IServiceProvider _serviceProvider;
@@ -28,7 +28,7 @@ public abstract class BaseDeviceController : BaseController
public BaseDeviceController(IServiceProvider serviceProvider) : base(serviceProvider)
{
_deviceService = serviceProvider.GetRequiredService<IDeviceService>();
- _tokenService = serviceProvider.GetRequiredService<TokenService>();
+ _tokenService = serviceProvider.GetRequiredService<ITokenService>();
_sessionManager = serviceProvider.GetRequiredService<ISessionManager>();
_serviceProvider = serviceProvider;
}
@@ -38,7 +38,7 @@ public abstract class BaseDeviceController : BaseController
/// <param name="tokenService"></param>
/// <param name="sessionManager"></param>
/// <param name="serviceProvider"></param>
- public BaseDeviceController(IDeviceService deviceService, TokenService tokenService, ISessionManager sessionManager, IServiceProvider serviceProvider) : base(serviceProvider)
+ public BaseDeviceController(IDeviceService deviceService, ITokenService tokenService, ISessionManager sessionManager, IServiceProvider serviceProvider) : base(serviceProvider)
{
_deviceService = deviceService;
_tokenService = tokenService;
@@ -48,16 +48,31 @@ public abstract class BaseDeviceController : BaseController
/// <summary>验证身份</summary>
/// <param name="token"></param>
+ /// <param name="context"></param>
/// <returns></returns>
/// <exception cref="ApiException"></exception>
- protected override Boolean OnAuthorize(String token)
+ protected override Boolean OnAuthorize(String token, ActionContext context)
{
- if (!base.OnAuthorize(token) || Jwt == null || Jwt.Subject.IsNullOrEmpty()) return false;
+ // 先调用基类,获取Jwt。即使失败,也要继续往下走,获取设备信息。最后再决定是否抛出异常
+ Exception? error = null;
+ try
+ {
+ if (!base.OnAuthorize(token, context)) return false;
+ }
+ catch (Exception ex)
+ {
+ error = ex;
+ }
+
+ var code = Jwt?.Subject;
+ if (code.IsNullOrEmpty()) return false;
+
+ var dv = _deviceService.QueryDevice(code);
+ if (dv == null || !dv.Enable) error ??= new ApiException(ApiCode.Forbidden, "无效客户端!");
- var dv = _deviceService.QueryDevice(Jwt.Subject);
- if (dv == null || !dv.Enable) throw new ApiException(ApiCode.Forbidden, "无效客户端!");
+ _device = dv!;
- _device = dv;
+ if (error != null) throw error;
return true;
}
diff --git a/NewLife.Remoting.Extensions/Controllers/BaseOAuthController.cs b/NewLife.Remoting.Extensions/Controllers/BaseOAuthController.cs
index d99ff88..0b7cbf0 100644
--- a/NewLife.Remoting.Extensions/Controllers/BaseOAuthController.cs
+++ b/NewLife.Remoting.Extensions/Controllers/BaseOAuthController.cs
@@ -1,28 +1,18 @@
using Microsoft.AspNetCore.Mvc;
using NewLife.Data;
using NewLife.Remoting.Extensions.Models;
-using NewLife.Remoting.Extensions.Services;
using NewLife.Remoting.Models;
+using NewLife.Remoting.Services;
using NewLife.Web;
namespace NewLife.Remoting.Extensions;
/// <summary>OAuth控制器基类。向应用提供令牌颁发与验证服务</summary>
+/// <param name="tokenService"></param>
+/// <param name="setting"></param>
[Route("[controller]/[action]")]
-public abstract class BaseOAuthController : ControllerBase
+public abstract class BaseOAuthController(ITokenService tokenService, ITokenSetting setting) : ControllerBase
{
- private readonly TokenService _tokenService;
- private readonly ITokenSetting _setting;
-
- /// <summary>实例化</summary>
- /// <param name="tokenService"></param>
- /// <param name="setting"></param>
- public BaseOAuthController(TokenService tokenService, ITokenSetting setting)
- {
- _tokenService = tokenService;
- _setting = setting;
- }
-
/// <summary>验证密码颁发令牌,或刷新令牌</summary>
/// <param name="model"></param>
/// <returns></returns>
@@ -30,7 +20,7 @@ public abstract class BaseOAuthController : ControllerBase
[ApiFilter]
public virtual TokenModel Token([FromBody] TokenInModel model)
{
- var set = _setting;
+ var set = setting;
if (model.grant_type.IsNullOrEmpty()) model.grant_type = "password";
@@ -46,7 +36,7 @@ public abstract class BaseOAuthController : ControllerBase
var app = Authorize(model.UserName, model.Password, set.AutoRegister, ip);
- var tokenModel = _tokenService.IssueToken(app.Name, clientId);
+ var tokenModel = tokenService.IssueToken(app.Name, clientId);
app.WriteLog("Authorize", true, model.UserName, ip, clientId);
@@ -57,7 +47,7 @@ public abstract class BaseOAuthController : ControllerBase
{
if (model.refresh_token.IsNullOrEmpty()) throw new ArgumentNullException(nameof(model.refresh_token));
- var (jwt, ex) = _tokenService.DecodeTokenWithError(model.refresh_token);
+ var (jwt, ex) = tokenService.DecodeToken(model.refresh_token);
// 验证应用
var name = jwt?.Subject;
@@ -73,7 +63,7 @@ public abstract class BaseOAuthController : ControllerBase
throw ex;
}
- var tokenModel = _tokenService.IssueToken(app!.Name, clientId);
+ var tokenModel = tokenService.IssueToken(app!.Name, clientId);
//app.WriteHistory("RefreshToken", true, model.refresh_token, ip, clientId);
@@ -138,7 +128,7 @@ public abstract class BaseOAuthController : ControllerBase
/// <param name="password"></param>
/// <param name="ip"></param>
/// <returns></returns>
- protected abstract Boolean OnAuthorize(IAppModel app, String? password, String? ip = null);
+ protected virtual Boolean OnAuthorize(IAppModel app, String? password, String? ip = null) => app.Authorize(password, ip);
/// <summary>根据令牌获取应用信息,同时也是验证令牌是否有效</summary>
/// <param name="token"></param>
@@ -146,7 +136,9 @@ public abstract class BaseOAuthController : ControllerBase
[ApiFilter]
public Object Info(String token)
{
- var jwt = _tokenService.DecodeToken(token);
+ var (jwt, ex) = tokenService.DecodeToken(token);
+ if (ex != null) throw ex;
+
var name = jwt?.Subject;
var app = name.IsNullOrEmpty() ? default : FindByName(name);
if (app is IModel model)
@@ -160,7 +152,7 @@ public abstract class BaseOAuthController : ControllerBase
else
return new
{
- Name = name,
+ app?.Name,
};
}
}
\ No newline at end of file
diff --git a/NewLife.Remoting.Extensions/RemotingExtensions.cs b/NewLife.Remoting.Extensions/RemotingExtensions.cs
index 18571cd..d424cd4 100644
--- a/NewLife.Remoting.Extensions/RemotingExtensions.cs
+++ b/NewLife.Remoting.Extensions/RemotingExtensions.cs
@@ -39,7 +39,7 @@ public static class RemotingExtensions
// 注册Remoting所必须的服务
if (setting != null)
{
- services.TryAddSingleton<TokenService>();
+ services.TryAddSingleton<ITokenService, TokenService>();
services.TryAddSingleton(setting);
}
diff --git a/NewLife.Remoting.Extensions/Services/TokenService.cs b/NewLife.Remoting.Extensions/Services/TokenService.cs
index 84e36e8..884ed1a 100644
--- a/NewLife.Remoting.Extensions/Services/TokenService.cs
+++ b/NewLife.Remoting.Extensions/Services/TokenService.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using NewLife.Remoting.Models;
+using NewLife.Remoting.Services;
using NewLife.Security;
using NewLife.Web;
@@ -7,17 +8,13 @@ namespace NewLife.Remoting.Extensions.Services;
/// <summary>令牌服务。颁发与验证令牌</summary>
/// <remarks>可重载覆盖功能逻辑</remarks>
-public class TokenService
+/// <param name="tokenSetting"></param>
+public class TokenService(ITokenSetting tokenSetting) : ITokenService
{
- private readonly ITokenSetting _tokenSetting;
-
- /// <summary>实例化</summary>
- /// <param name="tokenSetting"></param>
- public TokenService(ITokenSetting tokenSetting) => _tokenSetting = tokenSetting;
-
- private JwtBuilder GetJwt()
+ /// <summary>令牌配置</summary>
+ protected virtual JwtBuilder GetJwt()
{
- var ss = _tokenSetting.TokenSecret.Split(':');
+ var ss = tokenSetting.TokenSecret.Split(':');
return new JwtBuilder
{
Algorithm = ss[0],
@@ -38,36 +35,36 @@ public class TokenService
jwt.Issuer = Assembly.GetEntryAssembly()?.GetName().Name;
jwt.Subject = name;
jwt.Id = id;
- jwt.Expire = DateTime.Now.AddSeconds(_tokenSetting.TokenExpire);
+ jwt.Expire = DateTime.Now.AddSeconds(tokenSetting.TokenExpire);
return new TokenModel
{
AccessToken = jwt.Encode(null!),
TokenType = jwt.Type ?? "JWT",
- ExpireIn = _tokenSetting.TokenExpire,
+ ExpireIn = tokenSetting.TokenExpire,
RefreshToken = jwt.Encode(null!),
};
}
- /// <summary>验证并续发新令牌,过期前10分钟才能续发</summary>
- /// <param name="name"></param>
- /// <param name="token"></param>
- /// <returns></returns>
- public virtual TokenModel? ValidAndIssueToken(String name, String token)
- {
- if (token.IsNullOrEmpty()) return null;
+ ///// <summary>验证并续发新令牌,过期前10分钟才能续发</summary>
+ ///// <param name="name"></param>
+ ///// <param name="token"></param>
+ ///// <returns></returns>
+ //public virtual TokenModel? ValidAndIssueToken(String name, String token)
+ //{
+ // if (token.IsNullOrEmpty()) return null;
- // 令牌有效期检查,10分钟内过期者,重新颁发令牌
- var jwt = GetJwt();
- if (!jwt.TryDecode(token, out _)) return null;
+ // // 令牌有效期检查,10分钟内过期者,重新颁发令牌
+ // var jwt = GetJwt();
+ // if (!jwt.TryDecode(token, out _)) return null;
- return DateTime.Now.AddMinutes(10) > jwt.Expire ? IssueToken(name, jwt.Id) : null;
- }
+ // return DateTime.Now.AddMinutes(10) > jwt.Expire ? IssueToken(name, jwt.Id) : null;
+ //}
/// <summary>解码令牌</summary>
/// <param name="token"></param>
/// <returns></returns>
- public virtual (JwtBuilder, Exception?) DecodeTokenWithError(String token)
+ public virtual (JwtBuilder, Exception?) DecodeToken(String token)
{
if (token.IsNullOrEmpty()) throw new ArgumentNullException(nameof(token));
@@ -76,23 +73,8 @@ public class TokenService
Exception? ex = null;
if (!jwt.TryDecode(token, out var message))
- ex = new ApiException(ApiCode.Forbidden, $"[{jwt.Subject}]非法访问 {message}");
+ ex = new ApiException(ApiCode.Forbidden, $"非法访问[{jwt.Subject}] {message}");
return (jwt, ex);
}
-
- /// <summary>解码令牌,得到App应用</summary>
- /// <param name="token"></param>
- /// <returns></returns>
- public virtual JwtBuilder DecodeToken(String token)
- {
- if (token.IsNullOrEmpty()) throw new ArgumentNullException(nameof(token));
-
- // 解码令牌
- var jwt = GetJwt();
- if (!jwt.TryDecode(token, out var message) || jwt.Subject.IsNullOrEmpty())
- throw new ApiException(ApiCode.Forbidden, $"非法访问[{jwt.Subject}],{message}");
-
- return jwt;
- }
}
\ No newline at end of file
diff --git a/NewLife.Remoting/Services/ITokenService.cs b/NewLife.Remoting/Services/ITokenService.cs
new file mode 100644
index 0000000..b7dd79b
--- /dev/null
+++ b/NewLife.Remoting/Services/ITokenService.cs
@@ -0,0 +1,13 @@
+using NewLife.Web;
+
+namespace NewLife.Remoting.Services;
+
+/// <summary>令牌服务。颁发与验证令牌</summary>
+public interface ITokenService
+{
+ /// <summary>颁发令牌(使用ITokenSetting中的密钥)</summary>
+ TokenModel IssueToken(String name, String? id = null);
+
+ /// <summary>验证令牌</summary>
+ (JwtBuilder, Exception?) DecodeToken(String token);
+}
\ No newline at end of file
diff --git a/Samples/IoTZero/Controllers/DeviceController.cs b/Samples/IoTZero/Controllers/DeviceController.cs
index 6f9fb0a..07db662 100644
--- a/Samples/IoTZero/Controllers/DeviceController.cs
+++ b/Samples/IoTZero/Controllers/DeviceController.cs
@@ -21,9 +21,9 @@ public class DeviceController(IServiceProvider serviceProvider) : BaseDeviceCont
public Device Device { get; set; }
#region 构造
- protected override Boolean OnAuthorize(String token)
+ protected override Boolean OnAuthorize(String token, ActionContext context)
{
- if (!base.OnAuthorize(token)) return false;
+ if (!base.OnAuthorize(token, context)) return false;
Device = _device as Device;
diff --git a/Samples/IoTZero/Controllers/ThingController.cs b/Samples/IoTZero/Controllers/ThingController.cs
index 632a231..42f818a 100644
--- a/Samples/IoTZero/Controllers/ThingController.cs
+++ b/Samples/IoTZero/Controllers/ThingController.cs
@@ -22,11 +22,14 @@ public class ThingController(IServiceProvider serviceProvider, ThingService thin
public Device Device { get; set; }
#region 构造
- protected override Boolean OnAuthorize(String token)
+ protected override Boolean OnAuthorize(String token, ActionContext context)
{
- if (!base.OnAuthorize(token) || Jwt == null) return false;
+ if (!base.OnAuthorize(token, context)) return false;
- var dv = Device.FindByCode(Jwt.Subject);
+ var code = Jwt?.Subject;
+ if (code.IsNullOrEmpty()) return false;
+
+ var dv = Device.FindByCode(code);
if (dv == null || !dv.Enable) throw new ApiException(ApiCode.Unauthorized, "无效设备!");
Device = dv;
diff --git a/Samples/ZeroServer/Controllers/NodeController.cs b/Samples/ZeroServer/Controllers/NodeController.cs
index 31b0837..ab2633e 100644
--- a/Samples/ZeroServer/Controllers/NodeController.cs
+++ b/Samples/ZeroServer/Controllers/NodeController.cs
@@ -1,38 +1,24 @@
using Microsoft.AspNetCore.Mvc;
-using NewLife.Log;
using NewLife.Remoting.Extensions;
using NewLife.Remoting.Models;
-using NewLife.Remoting.Services;
using Zero.Data.Nodes;
-using ZeroServer.Services;
namespace ZeroServer.Controllers;
/// <summary>设备控制器</summary>
+/// <param name="serviceProvider"></param>
[ApiFilter]
[ApiController]
[Route("[controller]")]
-public class NodeController : BaseDeviceController
+public class NodeController(IServiceProvider serviceProvider) : BaseDeviceController(serviceProvider)
{
/// <summary>当前设备</summary>
public Node Node { get; set; }
- private readonly NodeService _nodeService;
- private readonly ITracer _tracer;
-
#region 构造
- /// <summary>实例化设备控制器</summary>
- /// <param name="serviceProvider"></param>
- /// <param name="tracer"></param>
- public NodeController(IServiceProvider serviceProvider, ITracer tracer) : base(serviceProvider)
- {
- _nodeService = serviceProvider.GetRequiredService<IDeviceService>() as NodeService;
- _tracer = tracer;
- }
-
- protected override Boolean OnAuthorize(String token)
+ protected override Boolean OnAuthorize(String token, ActionContext context)
{
- if (!base.OnAuthorize(token)) return false;
+ if (!base.OnAuthorize(token, context)) return false;
Node = _device as Node;
@@ -48,7 +34,7 @@ public class NodeController : BaseDeviceController
var rs = base.OnPing(request);
var node = Node;
- if (node != null)
+ if (node != null && rs != null)
{
rs.Period = node.Period;