diff --git a/src/admin/Bootstrap.Admin/Controllers/AccountController.cs b/src/admin/Bootstrap.Admin/Controllers/AccountController.cs index e81a8dc1..0af1882b 100644 --- a/src/admin/Bootstrap.Admin/Controllers/AccountController.cs +++ b/src/admin/Bootstrap.Admin/Controllers/AccountController.cs @@ -11,7 +11,9 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using System; +using System.Collections.Generic; using System.Linq; using System.Net; using System.Security.Claims; @@ -49,18 +51,18 @@ namespace Bootstrap.Admin.Controllers /// /// 系统锁屏界面 /// - /// + /// /// /// /// /// [HttpPost] [IgnoreAntiforgeryToken] - public Task Lock([FromServices]IConfiguration configuration, string userName, string password, string authType) + public Task Lock([FromServices]ISMSProvider provider, string userName, string password, string authType) { // 根据不同的登陆方式 Task ret; - if (authType == MobileSchema) ret = Mobile(configuration, userName, password); + if (authType == MobileSchema) ret = Mobile(provider, userName, password); else ret = Login(userName, password, string.Empty); return ret; } @@ -84,15 +86,14 @@ namespace Bootstrap.Admin.Controllers /// /// 短信验证登陆方法 /// - /// + /// /// /// /// [HttpPost()] - public async Task Mobile([FromServices]IConfiguration configuration, [FromQuery]string phone, [FromQuery]string code) + public async Task Mobile([FromServices]ISMSProvider provider, string phone, string code) { - var option = configuration.GetSection(nameof(SMSOptions)).Get(); - var auth = SMSHelper.Validate(phone, code, option.MD5Key); + var auth = provider.Validate(phone, code); HttpContext.Log(phone, auth); if (auth) { @@ -108,16 +109,26 @@ namespace Bootstrap.Admin.Controllers Password = code, Icon = "default.jpg", Description = "手机用户", - App = option.App + App = provider.Option.App }; UserHelper.Save(user); // 根据配置文件设置默认角色 - var roles = RoleHelper.Retrieves().Where(r => option.Roles.Any(rl => rl.Equals(r.RoleName, StringComparison.OrdinalIgnoreCase))).Select(r => r.Id); + var roles = RoleHelper.Retrieves().Where(r => provider.Option.Roles.Any(rl => rl.Equals(r.RoleName, StringComparison.OrdinalIgnoreCase))).Select(r => r.Id); RoleHelper.SaveByUserId(user.Id, roles); } } - return auth ? await SignInAsync(phone, true, MobileSchema) : View("Login", new LoginModel() { AuthFailed = true }); + return auth ? await SignInAsync(phone, true, MobileSchema) : RedirectLogin(); + } + + private IActionResult RedirectLogin() + { + var query = Request.Query.Aggregate(new Dictionary(), (d, v) => + { + d.Add(v.Key, v.Value.ToString()); + return d; + }); + return Redirect(QueryHelpers.AddQueryString(Request.PathBase + CookieAuthenticationDefaults.LoginPath, query)); } /// diff --git a/src/admin/Bootstrap.Admin/Controllers/Api/GiteeController.cs b/src/admin/Bootstrap.Admin/Controllers/Api/GiteeController.cs index c09aebd9..731284dd 100644 --- a/src/admin/Bootstrap.Admin/Controllers/Api/GiteeController.cs +++ b/src/admin/Bootstrap.Admin/Controllers/Api/GiteeController.cs @@ -36,7 +36,7 @@ namespace Bootstrap.Admin.Controllers.Api var result = string.IsNullOrEmpty(content) ? new string[] { "unknown" } : regex.Select((m, i) => $"{labels[i]} {m.Groups[1].Value}"); return string.Join(" ", result); }); - color = ret.StartsWith("open 0 progressing 0") ? "success" : color; + color = ret.StartsWith("open 0 progressing 0", StringComparison.OrdinalIgnoreCase) ? "success" : color; return new JsonResult(new { schemaVersion = 1, label, message = ret, color }); } diff --git a/src/admin/Bootstrap.Admin/Controllers/Api/LoginController.cs b/src/admin/Bootstrap.Admin/Controllers/Api/LoginController.cs index d86420cb..4976e1bd 100644 --- a/src/admin/Bootstrap.Admin/Controllers/Api/LoginController.cs +++ b/src/admin/Bootstrap.Admin/Controllers/Api/LoginController.cs @@ -4,9 +4,7 @@ using Bootstrap.Security.Authentication; using Longbow.Web.Mvc; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Configuration; -using Newtonsoft.Json.Linq; -using System.Net.Http; +using Microsoft.Extensions.DependencyInjection; using System.Threading.Tasks; namespace Bootstrap.Admin.Controllers.Api @@ -49,19 +47,15 @@ namespace Bootstrap.Admin.Controllers.Api /// /// 下发手机短信方法 /// - /// - /// + /// /// /// [AllowAnonymous] [HttpPut] - public async Task Put([FromServices]IConfiguration configuration, [FromServices]IHttpClientFactory factory, [FromQuery]string phone) + public async Task Put([FromServices]ISMSProvider provider, [FromQuery]string phone) { if (string.IsNullOrEmpty(phone)) return false; - - var option = configuration.GetSection(nameof(SMSOptions)).Get(); - option.Phone = phone; - return await factory.CreateClient().SendCode(option); + return await provider.SendCodeAsync(phone); } /// diff --git a/src/admin/Bootstrap.DataAccess/Helper/SMSHelper.cs b/src/admin/Bootstrap.Admin/Extensions/SMSExtensions.cs similarity index 58% rename from src/admin/Bootstrap.DataAccess/Helper/SMSHelper.cs rename to src/admin/Bootstrap.Admin/Extensions/SMSExtensions.cs index c9d4279e..33b31aac 100644 --- a/src/admin/Bootstrap.DataAccess/Helper/SMSHelper.cs +++ b/src/admin/Bootstrap.Admin/Extensions/SMSExtensions.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.WebUtilities; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; using System; using System.Collections.Concurrent; using System.Collections.Generic; @@ -6,59 +8,112 @@ using System.Collections.Specialized; using System.Net.Http; using System.Security.Cryptography; using System.Text; -using System.Threading; -#if NETCOREAPP3_0 using System.Text.Json; -#else -using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; -#endif +using System.Threading; +using System.Threading.Tasks; -namespace Bootstrap.DataAccess +namespace Microsoft.Extensions.DependencyInjection { + /// + /// 短信登录扩展类 + /// + public static class SMSExtensions + { + /// + /// 注入短信登录服务到容器中 + /// + /// + /// + public static IServiceCollection AddSMSProvider(this IServiceCollection services) + { + services.AddTransient(); + return services; + } + } + + /// + /// 短信登录接口 + /// + public interface ISMSProvider + { + /// + /// 手机下发验证码方法 + /// + /// 手机号 + /// + Task SendCodeAsync(string phoneNumber); + + /// + /// 验证手机验证码是否正确方法 + /// + /// 手机号 + /// 验证码 + /// + bool Validate(string phoneNumber, string code); + + /// + /// 获得 配置信息 + /// + SMSOptions Option { get; } + } + /// /// 手机号登陆帮助类 /// - public static class SMSHelper + internal class DefaultSMSProvider : ISMSProvider { private static ConcurrentDictionary _pool = new ConcurrentDictionary(); + /// + /// 获得 短信配置信息 + /// + public SMSOptions Option { get; protected set; } + + private HttpClient _client; + + /// + /// 构造函数 + /// + /// + /// + public DefaultSMSProvider(IConfiguration configuration, IHttpClientFactory factory) + { + Option = configuration.GetSection().Get(); + _client = factory.CreateClient(); + } + /// /// 下发验证码方法 /// - /// - /// + /// /// - public static async System.Threading.Tasks.Task SendCode(this HttpClient client, SMSOptions option) + public async Task SendCodeAsync(string phoneNumber) { - option.Timestamp = (DateTimeOffset.UtcNow.Ticks - 621355968000000000) / 10000000; + Option.Timestamp = (DateTimeOffset.UtcNow.Ticks - 621355968000000000) / 10000000; + Option.Phone = phoneNumber; var requestParameters = new Dictionary() { - { "CompanyCode", option.CompanyCode }, - { "Phone", option.Phone }, - { "TimeStamp", option.Timestamp.ToString() }, - { "Sign", option.Sign() } + { "CompanyCode", Option.CompanyCode }, + { "Phone", Option.Phone }, + { "TimeStamp", Option.Timestamp.ToString() }, + { "Sign", Sign() } }; - var url = QueryHelpers.AddQueryString("http://open.bluegoon.com/api/sms/sendcode", requestParameters); - var req = await client.GetAsync(url); + var url = QueryHelpers.AddQueryString(Option.RequestUrl, requestParameters); + var req = await _client.GetAsync(url); var content = await req.Content.ReadAsStringAsync(); -#if NETCOREAPP3_0 var result = JsonSerializer.Deserialize(content, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true }); -#else - var result = JsonConvert.DeserializeObject(content, new JsonSerializerSettings() { ContractResolver = new DefaultContractResolver() }); -#endif var ret = false; if (result.Code == 1) { - _pool.AddOrUpdate(option.Phone, key => new AutoExpireValidateCode(option.Phone, result.Data, option.Expires), (key, v) => v.Reset(result.Data)); + _pool.AddOrUpdate(Option.Phone, key => new AutoExpireValidateCode(Option.Phone, result.Data, Option.Expires), (key, v) => v.Reset(result.Data)); ret = true; } else { new Exception("SMS Send Fail").Log(new NameValueCollection() { - ["UserId"] = option.Phone, + ["UserId"] = Option.Phone, ["url"] = url, ["content"] = content }); @@ -71,13 +126,12 @@ namespace Bootstrap.DataAccess /// /// 手机号 /// 验证码 - /// 密钥 /// - public static bool Validate(string phone, string code, string secret) => _pool.TryGetValue(phone, out var signKey) && Hash($"{code}{secret}") == signKey.Code; + public bool Validate(string phone, string code) => _pool.TryGetValue(phone, out var signKey) && Hash($"{code}{Option.MD5Key}") == signKey.Code; - private static string Sign(this SMSOptions option) + private string Sign() { - return Hash($"{option.CompanyCode}{option.Phone}{option.Timestamp}{option.MD5Key}"); + return Hash($"{Option.CompanyCode}{Option.Phone}{Option.Timestamp}{Option.MD5Key}"); } private static string Hash(string data) @@ -124,7 +178,7 @@ namespace Bootstrap.DataAccess private CancellationTokenSource _tokenSource; - private System.Threading.Tasks.Task RunAsync() => System.Threading.Tasks.Task.Run(() => + private Task RunAsync() => Task.Run(() => { _tokenSource = new CancellationTokenSource(); if (!_tokenSource.Token.WaitHandle.WaitOne(Expires)) _pool.TryRemove(Phone, out var _); @@ -188,5 +242,10 @@ namespace Bootstrap.DataAccess /// 获得/设置 默认授权 App /// public string App { get; set; } + + /// + /// 获得/设置 短信下发网关地址 + /// + public string RequestUrl { get; set; } = "http://open.bluegoon.com/api/sms/sendcode"; } } diff --git a/src/admin/Bootstrap.Admin/Startup.cs b/src/admin/Bootstrap.Admin/Startup.cs index 5a31d069..00f42993 100644 --- a/src/admin/Bootstrap.Admin/Startup.cs +++ b/src/admin/Bootstrap.Admin/Startup.cs @@ -79,6 +79,7 @@ namespace Bootstrap.Admin option.AssumeDefaultVersionWhenUnspecified = true; option.ApiVersionReader = ApiVersionReader.Combine(new HeaderApiVersionReader("api-version"), new QueryStringApiVersionReader("api-version")); }); + services.AddSMSProvider(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/admin/Bootstrap.Admin/Views/Account/Login.cshtml b/src/admin/Bootstrap.Admin/Views/Account/Login.cshtml index ed626aae..52bc062c 100644 --- a/src/admin/Bootstrap.Admin/Views/Account/Login.cshtml +++ b/src/admin/Bootstrap.Admin/Views/Account/Login.cshtml @@ -75,7 +75,7 @@ - +
@@ -85,7 +85,7 @@
- +
diff --git a/src/admin/Bootstrap.Admin/wwwroot/js/login.js b/src/admin/Bootstrap.Admin/wwwroot/js/login.js index 37db41d7..707bbcca 100644 --- a/src/admin/Bootstrap.Admin/wwwroot/js/login.js +++ b/src/admin/Bootstrap.Admin/wwwroot/js/login.js @@ -132,23 +132,24 @@ var $loginSMS = $('#loginSMS'); if ($login.attr('data-demo') === 'True') { $login.find('[name="userName"], [name="password"]').attr('data-valid', 'false'); - $login.on('submit', function (e) { - var model = $loginType.attr('data-value'); - if (model === 'username') { - if ($username.val() === '' && $password.val() === '') { - e.preventDefault(); - location.href = "Gitee"; - } - } - else { - // sms - var url = $.format('Account/Mobile?phone={0}&code={1}', $('#txtPhone').val(), $('#smscode').val()); - $login.attr('action', $.formatUrl(url)); - return true; - } - }); } + $login.on('submit', function (e) { + var model = $loginType.attr('data-value'); + if (model === 'username') { + if ($username.val() === '' && $password.val() === '') { + e.preventDefault(); + location.href = "Gitee"; + } + } + else { + // sms + var url = $.format('Account/Mobile{0}', location.search); + $login.attr('action', $.formatUrl(url)); + return true; + } + }); + // login type var $loginType = $('#loginType').on('click', function (e) { e.preventDefault(); @@ -177,7 +178,7 @@ var timeHanlder = null; $('#btnSendCode').on('click', function () { // validate mobile phone - var $phone = $('#txtPhone'); + var $phone = $('#phone'); var validator = $login.find('[data-toggle="LgbValidate"]').lgbValidator(); if (!validator.validElement($phone.get(0))) { $phone.tooltip('show'); @@ -200,7 +201,7 @@ if (result) { // send success $this.text('已发送').attr('disabled', true); - $('#smscode').removeAttr('disabled'); + $('#code').removeAttr('disabled'); timeHanlder = setTimeout(function () { clearTimeout(timeHanlder); var count = 299;