diff --git a/FastTunnel.Client/FastTunnel.Client.csproj b/FastTunnel.Client/FastTunnel.Client.csproj index a9d0752..39204f2 100644 --- a/FastTunnel.Client/FastTunnel.Client.csproj +++ b/FastTunnel.Client/FastTunnel.Client.csproj @@ -5,10 +5,10 @@ - - - - + + + + diff --git a/FastTunnel.Core/Client/FastTunnelServer.cs b/FastTunnel.Core/Client/FastTunnelServer.cs index 2ac3b29..1fe6c11 100644 --- a/FastTunnel.Core/Client/FastTunnelServer.cs +++ b/FastTunnel.Core/Client/FastTunnelServer.cs @@ -64,7 +64,7 @@ namespace FastTunnel.Core.Client internal void ClientLogout(TunnelClient client) { Interlocked.Decrement(ref ConnectedClientCount); - logger.LogInformation($"客户端关闭 {client.RemoteIpAddress} 当前在线数:{ConnectedClientCount},统计CLIENT连接数:{FastTunnelClientHandler.ConnectionCount}"); + logger.LogInformation($"客户端关闭 {client.RemoteIpAddress} 当前在线数:{ConnectedClientCount},统计CLIENT连接数:{FastTunnelClientHandler.ConnectionCount - 1}"); Clients.Remove(client); client.Logout(); } diff --git a/FastTunnel.Server/Controllers/AccountController.cs b/FastTunnel.Server/Controllers/AccountController.cs new file mode 100644 index 0000000..a86cdad --- /dev/null +++ b/FastTunnel.Server/Controllers/AccountController.cs @@ -0,0 +1,90 @@ +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE +// Copyright (c) 2019 Gui.H + +using FastTunnel.Api.Models; +using FastTunnel.Core.Config; +using FastTunnel.Server.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; +using System; +using System.Collections.Generic; +using System.IdentityModel.Tokens.Jwt; +using System.Linq; +using System.Security.Claims; + +namespace FastTunnel.Api.Controllers +{ + public class AccountController : BaseController + { + readonly IOptionsMonitor serverOptionsMonitor; + + public AccountController(IOptionsMonitor optionsMonitor) + { + serverOptionsMonitor = optionsMonitor; + } + + /// + /// 获取Token + /// + /// + /// + [AllowAnonymous] + [HttpPost] + public ApiResponse GetToken(GetTokenRequest request) + { + if ((serverOptionsMonitor.CurrentValue?.Api?.Accounts?.Length ?? 0) == 0) + { + ApiResponse.errorCode = ErrorCodeEnum.NoAccount; + ApiResponse.errorMessage = "账号或密码错误"; + return ApiResponse; + } + + var account = serverOptionsMonitor.CurrentValue.Api.Accounts.FirstOrDefault((x) => + { + return x.Name.Equals(request.name) && x.Password.Equals(request.password); + }); + + if (account == null) + { + ApiResponse.errorCode = ErrorCodeEnum.NoAccount; + ApiResponse.errorMessage = "账号或密码错误"; + return ApiResponse; + } + + // 生成Token + var claims = new[] { + new Claim("Name", account.Name) + }; + + ApiResponse.data = GenerateToken( + claims, + serverOptionsMonitor.CurrentValue.Api.JWT.IssuerSigningKey, + serverOptionsMonitor.CurrentValue.Api.JWT.Expires, + serverOptionsMonitor.CurrentValue.Api.JWT.ValidIssuer, + serverOptionsMonitor.CurrentValue.Api.JWT.ValidAudience); + + return ApiResponse; + } + + public static string GenerateToken( + IEnumerable claims, string Secret, int expiresMinutes = 60, string issuer = null, string audience = null) + { + var key = new SymmetricSecurityKey(System.Text.Encoding.UTF8.GetBytes(Secret)); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var securityToken = new JwtSecurityToken( + issuer: issuer, + audience: audience, + claims: claims, + expires: DateTime.Now.AddMinutes(expiresMinutes), + signingCredentials: creds); + + return new JwtSecurityTokenHandler().WriteToken(securityToken); + } + } +} diff --git a/FastTunnel.Server/Controllers/BaseController.cs b/FastTunnel.Server/Controllers/BaseController.cs new file mode 100644 index 0000000..083af95 --- /dev/null +++ b/FastTunnel.Server/Controllers/BaseController.cs @@ -0,0 +1,22 @@ +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE +// Copyright (c) 2019 Gui.H + +using FastTunnel.Api.Filters; +using FastTunnel.Server.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace FastTunnel.Api.Controllers +{ + [Authorize] + [Route("api/[controller]/[action]")] + [ApiController] + [ServiceFilter(typeof(CustomExceptionFilterAttribute))] + public class BaseController : ControllerBase + { + protected ApiResponse ApiResponse = new ApiResponse(); + } +} diff --git a/FastTunnel.Server/Controllers/SystemController.cs b/FastTunnel.Server/Controllers/SystemController.cs new file mode 100644 index 0000000..0f6c9f5 --- /dev/null +++ b/FastTunnel.Server/Controllers/SystemController.cs @@ -0,0 +1,100 @@ +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE +// Copyright (c) 2019 Gui.H + +using FastTunnel.Core.Client; +using FastTunnel.Server.Models; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace FastTunnel.Api.Controllers; + +public class SystemController : BaseController +{ + readonly FastTunnelServer fastTunnelServer; + + public SystemController(FastTunnelServer fastTunnelServer) + { + this.fastTunnelServer = fastTunnelServer; + } + + /// + /// 获取当前等待响应的请求 + /// + /// + [HttpGet] + public ApiResponse GetResponseTempList() + { + ApiResponse.data = new + { + Count = fastTunnelServer.ResponseTasks.Count, + Rows = fastTunnelServer.ResponseTasks.Select(x => new + { + x.Key + }) + }; + + return ApiResponse; + } + + /// + /// 获取当前映射的所有站点信息 + /// + /// + [HttpGet] + public ApiResponse GetAllWebList() + { + ApiResponse.data = new + { + Count = fastTunnelServer.WebList.Count, + Rows = fastTunnelServer.WebList.Select(x => new { x.Key, x.Value.WebConfig.LocalIp, x.Value.WebConfig.LocalPort }) + }; + + return ApiResponse; + } + + /// + /// 获取服务端配置信息 + /// + /// + [HttpGet] + public ApiResponse GetServerOption() + { + ApiResponse.data = fastTunnelServer.ServerOption; + return ApiResponse; + } + + /// + /// 获取所有端口转发映射列表 + /// + /// + [HttpGet] + public ApiResponse GetAllForwardList() + { + ApiResponse.data = new + { + Count = fastTunnelServer.ForwardList.Count, + Rows = fastTunnelServer.ForwardList.Select(x => new { x.Key, x.Value.SSHConfig.LocalIp, x.Value.SSHConfig.LocalPort, x.Value.SSHConfig.RemotePort }) + + }; + + return ApiResponse; + } + + /// + /// 获取当前客户端在线数量 + /// + /// + [HttpGet] + public ApiResponse GetOnlineClientCount() + { + ApiResponse.data = fastTunnelServer.ConnectedClientCount; + return ApiResponse; + } +} diff --git a/FastTunnel.Server/FastTunnel.Server.csproj b/FastTunnel.Server/FastTunnel.Server.csproj index b5935ff..024800a 100644 --- a/FastTunnel.Server/FastTunnel.Server.csproj +++ b/FastTunnel.Server/FastTunnel.Server.csproj @@ -12,19 +12,18 @@ - - - - - + + + + + - - + + - diff --git a/FastTunnel.Server/Filters/CustomExceptionFilterAttribute.cs b/FastTunnel.Server/Filters/CustomExceptionFilterAttribute.cs new file mode 100644 index 0000000..14262d8 --- /dev/null +++ b/FastTunnel.Server/Filters/CustomExceptionFilterAttribute.cs @@ -0,0 +1,39 @@ +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE +// Copyright (c) 2019 Gui.H + +using FastTunnel.Server.Models; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Logging; + +namespace FastTunnel.Api.Filters +{ + public class CustomExceptionFilterAttribute : ExceptionFilterAttribute + { + readonly ILogger _logger; + + public CustomExceptionFilterAttribute(ILogger logger) + { + _logger = logger; + } + + public override void OnException(ExceptionContext context) + { + _logger.LogError(context.Exception, "【全局异常捕获】"); + var res = new ApiResponse() + { + errorCode = ErrorCodeEnum.Exception, + data = null, + errorMessage = context.Exception.Message, + }; + + var result = new JsonResult(res) { StatusCode = 200 }; + + context.Result = result; + context.ExceptionHandled = true; + } + } +} diff --git a/FastTunnel.Server/Models/ApiResponse.cs b/FastTunnel.Server/Models/ApiResponse.cs new file mode 100644 index 0000000..8062309 --- /dev/null +++ b/FastTunnel.Server/Models/ApiResponse.cs @@ -0,0 +1,32 @@ +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE +// Copyright (c) 2019 Gui.H + +namespace FastTunnel.Server.Models +{ + public class ApiResponse + { + /// + /// 错误码 + /// 0 成功,其他为失败 + /// + public ErrorCodeEnum errorCode { get; set; } + + public string errorMessage { get; set; } + + public object data { get; set; } + } + + public enum ErrorCodeEnum + { + NONE = 0, + + AuthError = 1, + + Exception = 2, + + NoAccount = 3, + } +} diff --git a/FastTunnel.Server/Models/GetTokenRequest.cs b/FastTunnel.Server/Models/GetTokenRequest.cs new file mode 100644 index 0000000..8617030 --- /dev/null +++ b/FastTunnel.Server/Models/GetTokenRequest.cs @@ -0,0 +1,19 @@ +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE +// Copyright (c) 2019 Gui.H + +using System.ComponentModel.DataAnnotations; + +namespace FastTunnel.Api.Models +{ + public class GetTokenRequest + { + [Required] + public string name { get; set; } + + [Required] + public string password { get; set; } + } +} diff --git a/FastTunnel.Server/Program.cs b/FastTunnel.Server/Program.cs index 1f9574e..e380476 100644 --- a/FastTunnel.Server/Program.cs +++ b/FastTunnel.Server/Program.cs @@ -6,8 +6,11 @@ using System.Net.Http; using System.Net.Sockets; +using FastTunnel.Core.Extensions; +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 Serilog; @@ -27,8 +30,58 @@ public class Program try { - CreateHostBuilder(args).Build().Run(); + var builder = WebApplication.CreateBuilder(new WebApplicationOptions + { + Args = args + }); + // Add services to the container. + + builder.Services.AddControllers(); + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + builder.Services.AddEndpointsApiExplorer(); + builder.Services.AddSwaggerGen(); + + builder.Host.UseSerilog((context, services, configuration) => configuration + .ReadFrom.Configuration(context.Configuration) + .ReadFrom.Services(services) + .Enrich.FromLogContext() + .WriteTo.Console()); + + (builder.Configuration as IConfigurationBuilder).AddJsonFile("config/appsettings.json", optional: false, reloadOnChange: true); + (builder.Configuration as IConfigurationBuilder).AddJsonFile($"config/appsettings.{builder.Environment.EnvironmentName}.json", optional: true, reloadOnChange: true); ; + + // -------------------FastTunnel STEP1 OF 3------------------ + builder.Services.AddFastTunnelServer(builder.Configuration.GetSection("FastTunnel")); + // -------------------FastTunnel STEP1 END------------------- + + builder.Host.UseWindowsService(); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. + if (app.Environment.IsDevelopment()) + { + app.UseSwagger(); + app.UseSwaggerUI(); + } + + app.UseHttpsRedirection(); + + app.UseStaticFiles(); + app.UseAuthentication(); + + app.UseAuthorization(); + + app.MapControllers(); + + // -------------------FastTunnel STEP2 OF 3------------------ + app.UseFastTunnelServer(); + // -------------------FastTunnel STEP2 END------------------- + + app.MapFastTunnelServer(); + + app.Run(); } catch (System.Exception ex) { @@ -36,31 +89,4 @@ public class Program throw; } } - - public static IHostBuilder CreateHostBuilder(string[] args) - { - return Host.CreateDefaultBuilder(args) - .UseSerilog((context, services, configuration) => configuration - .ReadFrom.Configuration(context.Configuration) - .ReadFrom.Services(services) - .Enrich.FromLogContext() - .WriteTo.Console()) - .UseWindowsService() - .ConfigureWebHost(webHostBuilder => - { - // Use FastTunnelHostingStartup - webHostBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "FastTunnel.Api"); - - webHostBuilder.ConfigureAppConfiguration((hostingContext, config) => - { - var env = hostingContext.HostingEnvironment; - config.AddJsonFile("config/appsettings.json", optional: false, reloadOnChange: true) - .AddJsonFile($"config/appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); - }); - }) - .ConfigureWebHostDefaults(webBuilder => - { - webBuilder.UseStartup(); - }); - } } diff --git a/FastTunnel.sln b/FastTunnel.sln index 75e9a92..77d2918 100644 --- a/FastTunnel.sln +++ b/FastTunnel.sln @@ -11,15 +11,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastTunnel.Server", "FastTu EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastTunnel.Api", "FastTunnel.Api\FastTunnel.Api.csproj", "{7D560A9A-E480-40F4-AAF7-398447438255}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{54494A47-33E6-488B-B7D4-DBE8FD34916A}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig .github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastTunnel.Core.Client", "FastTunnel.Core.Client\FastTunnel.Core.Client.csproj", "{67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastTunnel.Core.Client", "FastTunnel.Core.Client\FastTunnel.Core.Client.csproj", "{67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -39,10 +37,6 @@ Global {DEF2E322-9075-4C3F-9967-7EAF0EE28CEB}.Debug|Any CPU.Build.0 = Debug|Any CPU {DEF2E322-9075-4C3F-9967-7EAF0EE28CEB}.Release|Any CPU.ActiveCfg = Release|Any CPU {DEF2E322-9075-4C3F-9967-7EAF0EE28CEB}.Release|Any CPU.Build.0 = Release|Any CPU - {7D560A9A-E480-40F4-AAF7-398447438255}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7D560A9A-E480-40F4-AAF7-398447438255}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7D560A9A-E480-40F4-AAF7-398447438255}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7D560A9A-E480-40F4-AAF7-398447438255}.Release|Any CPU.Build.0 = Release|Any CPU {67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}.Debug|Any CPU.Build.0 = Debug|Any CPU {67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -53,7 +47,6 @@ Global EndGlobalSection GlobalSection(NestedProjects) = preSolution {C8ADFEB1-59DB-4CE3-8D04-5B547107BCCB} = {0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C} - {7D560A9A-E480-40F4-AAF7-398447438255} = {0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C} {67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B} = {0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution