refactor: 短信验证码登录方式使用注入服务
This commit is contained in:
parent
235614e0b9
commit
f271b2402c
|
@ -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
|
|||
/// <summary>
|
||||
/// 系统锁屏界面
|
||||
/// </summary>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="userName"></param>
|
||||
/// <param name="password"></param>
|
||||
/// <param name="authType"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[IgnoreAntiforgeryToken]
|
||||
public Task<IActionResult> Lock([FromServices]IConfiguration configuration, string userName, string password, string authType)
|
||||
public Task<IActionResult> Lock([FromServices]ISMSProvider provider, string userName, string password, string authType)
|
||||
{
|
||||
// 根据不同的登陆方式
|
||||
Task<IActionResult> 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
|
|||
/// <summary>
|
||||
/// 短信验证登陆方法
|
||||
/// </summary>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="phone"></param>
|
||||
/// <param name="code"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost()]
|
||||
public async Task<IActionResult> Mobile([FromServices]IConfiguration configuration, [FromQuery]string phone, [FromQuery]string code)
|
||||
public async Task<IActionResult> Mobile([FromServices]ISMSProvider provider, string phone, string code)
|
||||
{
|
||||
var option = configuration.GetSection(nameof(SMSOptions)).Get<SMSOptions>();
|
||||
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<string, string>(), (d, v) =>
|
||||
{
|
||||
d.Add(v.Key, v.Value.ToString());
|
||||
return d;
|
||||
});
|
||||
return Redirect(QueryHelpers.AddQueryString(Request.PathBase + CookieAuthenticationDefaults.LoginPath, query));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -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 });
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
|||
/// <summary>
|
||||
/// 下发手机短信方法
|
||||
/// </summary>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="factory"></param>
|
||||
/// <param name="provider"></param>
|
||||
/// <param name="phone"></param>
|
||||
/// <returns></returns>
|
||||
[AllowAnonymous]
|
||||
[HttpPut]
|
||||
public async Task<bool> Put([FromServices]IConfiguration configuration, [FromServices]IHttpClientFactory factory, [FromQuery]string phone)
|
||||
public async Task<bool> Put([FromServices]ISMSProvider provider, [FromQuery]string phone)
|
||||
{
|
||||
if (string.IsNullOrEmpty(phone)) return false;
|
||||
|
||||
var option = configuration.GetSection(nameof(SMSOptions)).Get<SMSOptions>();
|
||||
option.Phone = phone;
|
||||
return await factory.CreateClient().SendCode(option);
|
||||
return await provider.SendCodeAsync(phone);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 短信登录扩展类
|
||||
/// </summary>
|
||||
public static class SMSExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 注入短信登录服务到容器中
|
||||
/// </summary>
|
||||
/// <param name="services"></param>
|
||||
/// <returns></returns>
|
||||
public static IServiceCollection AddSMSProvider(this IServiceCollection services)
|
||||
{
|
||||
services.AddTransient<ISMSProvider, DefaultSMSProvider>();
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 短信登录接口
|
||||
/// </summary>
|
||||
public interface ISMSProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// 手机下发验证码方法
|
||||
/// </summary>
|
||||
/// <param name="phoneNumber">手机号</param>
|
||||
/// <returns></returns>
|
||||
Task<bool> SendCodeAsync(string phoneNumber);
|
||||
|
||||
/// <summary>
|
||||
/// 验证手机验证码是否正确方法
|
||||
/// </summary>
|
||||
/// <param name="phoneNumber">手机号</param>
|
||||
/// <param name="code">验证码</param>
|
||||
/// <returns></returns>
|
||||
bool Validate(string phoneNumber, string code);
|
||||
|
||||
/// <summary>
|
||||
/// 获得 配置信息
|
||||
/// </summary>
|
||||
SMSOptions Option { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 手机号登陆帮助类
|
||||
/// </summary>
|
||||
public static class SMSHelper
|
||||
internal class DefaultSMSProvider : ISMSProvider
|
||||
{
|
||||
private static ConcurrentDictionary<string, AutoExpireValidateCode> _pool = new ConcurrentDictionary<string, AutoExpireValidateCode>();
|
||||
|
||||
/// <summary>
|
||||
/// 获得 短信配置信息
|
||||
/// </summary>
|
||||
public SMSOptions Option { get; protected set; }
|
||||
|
||||
private HttpClient _client;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="configuration"></param>
|
||||
/// <param name="factory"></param>
|
||||
public DefaultSMSProvider(IConfiguration configuration, IHttpClientFactory factory)
|
||||
{
|
||||
Option = configuration.GetSection<SMSOptions>().Get<SMSOptions>();
|
||||
_client = factory.CreateClient();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 下发验证码方法
|
||||
/// </summary>
|
||||
/// <param name="client"></param>
|
||||
/// <param name="option"></param>
|
||||
/// <param name="phoneNumber"></param>
|
||||
/// <returns></returns>
|
||||
public static async System.Threading.Tasks.Task<bool> SendCode(this HttpClient client, SMSOptions option)
|
||||
public async Task<bool> SendCodeAsync(string phoneNumber)
|
||||
{
|
||||
option.Timestamp = (DateTimeOffset.UtcNow.Ticks - 621355968000000000) / 10000000;
|
||||
Option.Timestamp = (DateTimeOffset.UtcNow.Ticks - 621355968000000000) / 10000000;
|
||||
Option.Phone = phoneNumber;
|
||||
var requestParameters = new Dictionary<string, string>()
|
||||
{
|
||||
{ "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<SMSResult>(content, new JsonSerializerOptions() { PropertyNameCaseInsensitive = true });
|
||||
#else
|
||||
var result = JsonConvert.DeserializeObject<SMSResult>(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
|
|||
/// </summary>
|
||||
/// <param name="phone">手机号</param>
|
||||
/// <param name="code">验证码</param>
|
||||
/// <param name="secret">密钥</param>
|
||||
/// <returns></returns>
|
||||
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
|
||||
/// </summary>
|
||||
public string App { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获得/设置 短信下发网关地址
|
||||
/// </summary>
|
||||
public string RequestUrl { get; set; } = "http://open.bluegoon.com/api/sms/sendcode";
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -75,7 +75,7 @@
|
|||
<span class="fa fa-user"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="txtPhone" class="form-control digits" data-toggle="tooltip" placeholder="手机号码" minlength="11" maxlength="11" data-required-msg="请输入手机号码" value="" data-valid="true" />
|
||||
<input type="text" id="phone" name="phone" class="form-control digits" data-toggle="tooltip" placeholder="手机号码" minlength="11" maxlength="11" data-required-msg="请输入手机号码" value="" data-valid="true" />
|
||||
</div>
|
||||
</div>
|
||||
<div id="loginSMS" class="form-group d-none">
|
||||
|
@ -85,7 +85,7 @@
|
|||
<span class="fa fa-lock"></span>
|
||||
</div>
|
||||
</div>
|
||||
<input type="text" id="smscode" class="form-control digits" data-toggle="tooltip" disabled value="" placeholder="验证码" maxlength="4" data-required-msg="请输入验证码" data-valid="true" />
|
||||
<input type="text" id="code" name="code" class="form-control digits" data-toggle="tooltip" disabled value="" placeholder="验证码" maxlength="4" data-required-msg="请输入验证码" data-valid="true" />
|
||||
<div class="input-group-append">
|
||||
<button type="button" id="btnSendCode" class="btn btn-sms" data-toggle="tooltip" title="点击发送验证码">发送验证码</button>
|
||||
</div>
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue