NewLife/NewLife.Remoting

[refactor] 抽象提取ITokenService
智能大石头 authored at 2025-08-26 12:38:23
18b8493
Tree
1 Parent(s) 8f92bbd
Summary: 9 changed files with 105 additions and 108 deletions.
Modified +20 -14
Modified +23 -8
Modified +13 -21
Modified +1 -1
Modified +22 -40
Added +13 -0
Modified +2 -2
Modified +6 -3
Modified +5 -19
Modified +20 -14
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;
     }
Modified +23 -8
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;
     }
Modified +13 -21
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
Modified +1 -1
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);
         }
 
Modified +22 -40
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
Added +13 -0
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
Modified +2 -2
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;
 
Modified +6 -3
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;
Modified +5 -19
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;