From 28f26eceed76ddc887a4657f29efefb18bc2bf4b Mon Sep 17 00:00:00 2001 From: wanggang <76527413@qq.com> Date: Mon, 6 Jan 2020 17:56:56 +0800 Subject: [PATCH] update Former-commit-id: 260302b5cd23f4450e86c1560f29061a85c0d6ce --- .../Extensions/HttpContextExtensions.cs | 30 ++-- projects/Infrastructure/Infrastructure.csproj | 1 + projects/Infrastructure/Web/BaseStartup.cs | 31 +++- .../Controllers/AccountController.cs | 10 +- .../UserCenter/Controllers/TokenController.cs | 143 ++++++++++++++++++ projects/UserCenter/Program.cs | 2 + .../src/main/resources/static/favicon.ico | Bin 21662 -> 0 bytes .../src/main/resources/static/index.html | 11 -- 8 files changed, 192 insertions(+), 36 deletions(-) create mode 100644 projects/UserCenter/Controllers/TokenController.cs delete mode 100644 projects/gateway/src/main/resources/static/favicon.ico delete mode 100644 projects/gateway/src/main/resources/static/index.html diff --git a/projects/Infrastructure/Extensions/HttpContextExtensions.cs b/projects/Infrastructure/Extensions/HttpContextExtensions.cs index 40cd41b9..28d8e61d 100644 --- a/projects/Infrastructure/Extensions/HttpContextExtensions.cs +++ b/projects/Infrastructure/Extensions/HttpContextExtensions.cs @@ -1,14 +1,14 @@ +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; +using Microsoft.IdentityModel.Tokens; using System; using System.Collections.Generic; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using System.Text; -using Microsoft.AspNetCore.Authentication; -using Microsoft.AspNetCore.Authentication.Cookies; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.Configuration; -using Microsoft.IdentityModel.Tokens; namespace Infrastructure.Extensions { @@ -22,23 +22,33 @@ namespace Infrastructure.Extensions httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, new AuthenticationProperties { IsPersistent = rememberMe }); } - public static void SignIn(this HttpContext httpContext, string userName, bool rememberMe, IConfiguration cfg) + public static void SignIn(this HttpContext httpContext, string userName, bool rememberMe, IConfiguration cfg, DateTime expires) + { + var token = httpContext.GetToken(userName, rememberMe, cfg, expires); + httpContext.Response.Cookies.Delete("jwt"); + httpContext.Response.Cookies.Append("jwt", token); + } + + public static string GetToken(this HttpContext httpContext, string userName, bool rememberMe, IConfiguration cfg, DateTime expires) { var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(cfg["jwt:key"])); var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); var claims = new List { new Claim(ClaimTypes.Name, userName) }; - //claims.AddRange(roles.Select(o => new Claim(ClaimTypes.Role, o)).ToList()); var token = new JwtSecurityToken( issuer: cfg["jwt:issuer"], audience: cfg["jwt:audience"], claims: claims, - expires: DateTime.Now.AddMinutes(rememberMe ? 3600 : 3), + expires: expires, signingCredentials: creds); var tokenText = new JwtSecurityTokenHandler().WriteToken(token); - httpContext.Response.Cookies.Delete("jwt"); - httpContext.Response.Cookies.Append("jwt", tokenText); + return tokenText; + } + + public static JwtSecurityToken ReadToken(this HttpContext httpContext, string token) + { + return new JwtSecurityTokenHandler().ReadJwtToken(token); } } } \ No newline at end of file diff --git a/projects/Infrastructure/Infrastructure.csproj b/projects/Infrastructure/Infrastructure.csproj index 73fecc90..eceb11b1 100644 --- a/projects/Infrastructure/Infrastructure.csproj +++ b/projects/Infrastructure/Infrastructure.csproj @@ -10,6 +10,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/projects/Infrastructure/Web/BaseStartup.cs b/projects/Infrastructure/Web/BaseStartup.cs index c1263eb5..4fde99c6 100644 --- a/projects/Infrastructure/Web/BaseStartup.cs +++ b/projects/Infrastructure/Web/BaseStartup.cs @@ -19,6 +19,7 @@ using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Versioning; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.SignalR; @@ -31,14 +32,11 @@ using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Newtonsoft.Json; -using Newtonsoft.Json.Serialization; using System; using System.Diagnostics; using System.Globalization; -using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Reflection; -using System.Security.Claims; using System.Text; using System.Text.Encodings.Web; using System.Text.Unicode; @@ -117,11 +115,30 @@ namespace Infrastructure.Web return localizer; }; }); - services.AddControllers().AddNewtonsoftJson(o => + services.AddApiVersioning(o => { - o.SerializerSettings.ContractResolver = new DefaultContractResolver(); - o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + o.ReportApiVersions = true; + o.AssumeDefaultVersionWhenUnspecified = true; + o.DefaultApiVersion = new ApiVersion(1, 0); + o.ApiVersionReader = ApiVersionReader.Combine( + new UrlSegmentApiVersionReader() + //,new QueryStringApiVersionReader() + //,new HeaderApiVersionReader() + ); }); + services.AddControllers() + .ConfigureApiBehaviorOptions(options => + { + options.SuppressConsumesConstraintForFormFileParameters = true; + options.SuppressInferBindingSourcesForParameters = true; + options.SuppressModelStateInvalidFilter = true; + options.SuppressMapClientErrors = true; + //options.ClientErrorMapping[404].Link = "https://httpstatuses.com/404"; + }) + .AddNewtonsoftJson(o => + { + o.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore; + }); services.Configure(o => { var supportedCultures = new[] @@ -223,7 +240,7 @@ namespace Infrastructure.Web }, OnMessageReceived = context => { - if(!context.Request.IsStatic()) + if (!context.Request.IsStatic()) { Debug.WriteLine(context.Request.Path); if (context.Request.Query.ContainsKey("access_token")) diff --git a/projects/UserCenter/Controllers/AccountController.cs b/projects/UserCenter/Controllers/AccountController.cs index 7c08b15d..90af0709 100644 --- a/projects/UserCenter/Controllers/AccountController.cs +++ b/projects/UserCenter/Controllers/AccountController.cs @@ -205,16 +205,10 @@ namespace UserCenter.Controllers } else { - //var userPermissions = this._userRepo.ReadOnlyTable().Where(o => o.UserName == userName) - // .SelectMany(o => o.UserRoles) - // .Select(o => o.Role) - // .SelectMany(o => o.RolePermissions) - // .Select(o => o.Permission.Number) - // .ToList(); - HttpContext.SignIn(model.UserName, model.RememberMe, _cfg); + HttpContext.SignIn(model.UserName, model.RememberMe, _cfg, DateTime.Now.AddDays(1)); if (string.IsNullOrEmpty(returnUrl)) { - returnUrl = Url.Action("Index","Home"); + returnUrl = Url.Action("Index", "Home"); } ViewBag.Url = returnUrl; var urls = new List(); diff --git a/projects/UserCenter/Controllers/TokenController.cs b/projects/UserCenter/Controllers/TokenController.cs new file mode 100644 index 00000000..a8399802 --- /dev/null +++ b/projects/UserCenter/Controllers/TokenController.cs @@ -0,0 +1,143 @@ +using Application.Domain.Entities; +using Application.Models; +using Infrastructure.Data; +using Infrastructure.Extensions; +using Infrastructure.Security; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using System; +using System.Linq; +using System.Net.Mime; +using System.Security.Claims; + +namespace UserCenter.Controllers +{ + [ApiVersion("1.0")] + [Route("api/[controller]/[action]")] + [Route("api/v{version:apiVersion}/[controller]/[action]")] + [ApiController] + [Produces(MediaTypeNames.Application.Json)] + public class TokenController : ControllerBase + { + private readonly IConfiguration _cfg; + private readonly IRepository _userRepo; + private readonly IEncryptionService _encryptionService; + + public TokenController(IConfiguration cfg, + IRepository userRepo, + IEncryptionService encryptionService) + { + this._cfg = cfg; + this._userRepo = userRepo; + this._encryptionService = encryptionService; + } + + [HttpPost] + public ActionResult GetToken([FromBody]LoginModel model) + { + var success = false; + try + { + var user = this._userRepo.Table().FirstOrDefault(o => o.UserName == model.UserName); + if (user == null) + { + ModelState.AddModelError("", "用户名或密码错误"); + } + else + { + var maxAccessFailedCount = this._cfg.GetValue("MaxFailedAccessAttemptsBeforeLockout"); + var lockoutEndMinutes = this._cfg.GetValue("DefaultAccountLockoutMinutes"); + + if (user.LockoutEnabled)//对已启用登录锁定的用户,如果当前登录时间超出锁定时间,先解除锁定状态 + { + if (user.LockoutEnd.HasValue && DateTime.UtcNow > user.LockoutEnd) + { + user.LockoutEnd = null; + user.AccessFailedCount = 0; + this._userRepo.SaveChanges(); + } + } + if (user.LockoutEnabled)//对启用登录锁定的用户进行验证 + { + if (user.LockoutEnd.HasValue == false) + { + if (user.PasswordHash == this._encryptionService.CreatePasswordHash(model.Password, user.SecurityStamp)) + { + user.LockoutEnd = null; + user.AccessFailedCount = 0; + success = true; + } + else + { + user.AccessFailedCount += 1; + if (user.AccessFailedCount >= maxAccessFailedCount) + { + user.LockoutEnd = DateTime.UtcNow.AddMinutes(lockoutEndMinutes); + ModelState.AddModelError(nameof(model.UserName), $"用户被锁定,请于{user.LockoutEnd.Value.ToLocalTime().ToString("HH:mm")}后重试"); + } + else + { + ModelState.AddModelError(nameof(model.UserName), $"密码错误,再错误{maxAccessFailedCount - user.AccessFailedCount}次后将锁定用户{lockoutEndMinutes}分钟"); + } + } + this._userRepo.SaveChanges(); + } + } + else//对未启用登录锁定的用户进行验证 + { + if (user.PasswordHash == this._encryptionService.CreatePasswordHash(model.Password, user.SecurityStamp)) + { + success = true; + } + else + { + ModelState.AddModelError("", "用户名或密码错误"); + } + } + } + if (success) + { + return Ok(new + { + AccessToken = Request.HttpContext.GetToken(model.UserName, false, _cfg, DateTime.Now.AddHours(_cfg.GetValue("AccessTokenHours", 0.5))), + RefreshToken = Request.HttpContext.GetToken(model.UserName, false, _cfg, DateTime.Now.AddHours(_cfg.GetValue("AccessTokenHours", 720))), + }); + } + else + { + return BadRequest(ModelState); + } + } + catch (Exception ex) + { + ex.PrintStack(); + return Problem(ex.Message); + } + } + + [HttpPost] + public ActionResult RefreshToken([FromBody]string refreshToken) + { + try + { + var token = Request.HttpContext.ReadToken(refreshToken); + if (DateTime.UtcNow > token.ValidTo) + { + ModelState.AddModelError("", "已过期"); + return BadRequest(ModelState); + } + var userName = token.Claims.FirstOrDefault(o => o.Type == ClaimTypes.Name).Value; + return Ok(new + { + AccessToken = Request.HttpContext.GetToken(userName, false, _cfg, DateTime.Now.AddHours(_cfg.GetValue("AccessTokenHours", 0.5))), + RefreshToken = Request.HttpContext.GetToken(userName, false, _cfg, DateTime.Now.AddHours(_cfg.GetValue("AccessTokenHours", 720))), + }); + } + catch (Exception ex) + { + ex.PrintStack(); + return Problem(ex.Message); + } + } + } +} \ No newline at end of file diff --git a/projects/UserCenter/Program.cs b/projects/UserCenter/Program.cs index 15f79678..6a9d0243 100644 --- a/projects/UserCenter/Program.cs +++ b/projects/UserCenter/Program.cs @@ -12,6 +12,8 @@ namespace UserCenter { WebHost.CreateDefaultBuilder(args) .Run(new List { + new EFConfigurationValue { Id = "AccessTokenHours", Value= "0.5"}, + new EFConfigurationValue { Id = "RefreshToken", Value= "720"}, new EFConfigurationValue { Id = "MaxFailedAccessAttemptsBeforeLockout", Value= "5"}, new EFConfigurationValue { Id = "DefaultAccountLockoutMinutes", Value= "10"}, new EFConfigurationValue { Id = "CaptchaSeconds", Value= "60"}, diff --git a/projects/gateway/src/main/resources/static/favicon.ico b/projects/gateway/src/main/resources/static/favicon.ico deleted file mode 100644 index 20e937b89ec33286fcdcfb989c8104eb39c0e6d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 21662 zcmeI4S#VR=8OJZS<=wI+YnQdyvL!FFB-`>L%h<*kumP`N!0aInkg&vnag&Cmv`>BL zLuT5UKJ+Ei>02jl0tA{g&EBRn?Q|y7xt&ath9re%-?#}7lJf8WTw`kaI+@9ot31eJ zen>j^p!=PlzH@ZWxk8e(jDH3OB>wG}ezZ}NY?36^5*A4h5>c++#v~~anPs*hVb<6hg0;in|?UXMAxiYE6=)_prcG>6NZLH zE(L?3E%W^p6&1w>2G;(4e0&09V;eIT(f*;K(N9C6+R24})~p$ng`3gQ4H=7Q|N8YK zN-(JPvwHQQJnLqhelnE}ShsGNenOi3;kYD^jErMsWGsUj$J(_+i~8yBUn36>kBR=5 zv558$4z5>fYJw^G!_TkS%9X2S;fDP$V-f8i7+ANcpT53TGUrEhlbLYbQQOXWj#6D+ zvmk#8V=Gpyl-I2r;`o%=a2#ILPj7FZ%)Tf3Uq-_cT>l}F=tM_{mP8_{9`Em8rE6@W{e6A? z^i#EnpC0+!aKn5?cXtmO!(sS+UiiEo4Wc~U(4aaJ_4SOCQA1 zuk^BFCE63+sF?T_oO@q^^NyFb?EMGan;t=1q8ELvuUCws{XIRsir=sG!~HW^oR|5P zUV8h`*0CJL>mP9!oBzf`I8@u$J^v&vK#c5UOKzb)Y38QXUCf` zY=2G5jyF(3Ke2cxlATH2;$zc_D2P2}!4 zuR)ZTjGjicIib!W^@%gdc}5r5kLagD;I4z&$U z5cTzpk^M-q+Z_x1%)rG zYSZ}PHD{Ub5a#*Q3Hvo_f?-%ToJQIH4`ABIzO?V%dAy^Rmrp#7>QDrtzJBpVP*+z^ zKUR&OnwpSIcSt*R^#}y(VcB>FraM1`X+Pa8gyoh^Pp}^2V|_hiWIs|Y7Ofv%^O5OJ z_)9CbwY8|Kse_e%%m+S#`A+twg|OVR`3cr*fT*u$jKN@tek>{Z!|#;E0)c?cK8Cat z452a*f^E}dupImZmIEJXVYzkl@8GYlLns*3HAY@rR?5rEHGa4!Ewhh-$901_3c>6- z=FU|EUsaHP&cb@=5^M)Q(x8@ad6M;NQFHU(>k(%Ny!NG-&1Q`spRZD$i(ij>UQ+`1 z<-olI^<*Nb8euk@z&R?o55;Rbet3KVI5wYyW9lulvyi!(X(&P_gmD$JWPaoVaH>wj`5FMO?!&8Hjq9T~e%3!nEU~_t4ryu9h%WzIz z(!z4b)~8V6lUMEJKsn8Dbx62-pk@W)(0P)4hKBLKSTI|tLQlP7sS@h!08Rq zQz1%>X7;bMa38x0_mNMvu-v)rX}CNAUOV*Z6yt2$y@Qcp!_hQx9BDCv5&E`0jie-s7LcbL=uIj$Ps7Yp6eQ1#QP= z)E>J?4_A5Y$^tH{_1xQ^RUNzCZe3!WYd7;mDJd~(^2gzD$lO$~M@0?}um4pHVe)8G-KsfqTFFYBx8$iF%%0&%rvuuzjf zoKwr<-1<=tOSuVL`=Q($fh{?$=0w$T1=Z|#f$v>cT?teP72CLXar|S{oB(R>Uxe=izCAChe#F{D&ls5} z^iz=HCo3yE#yO*GGMT`&hyL(9CiOg}>UylqUIk0@PPoQ@iHgZni^B4HZiW!YYdG_4m{Aq@3)>0!eSy5`XeF$rb7=3e?vm!hhvh=W5{&)T@Cul z*7)J?PsntHC2|+@6EzreWR6Q%!k}bjWzEl@ERN5O9Ft^%C31wHP5ga;Jfhy{r<#54 zC4wcgFVTb<6mEDdqoIfQImUEypJUFAL@x$D|Ec(Fr>l%bXK(i);BC*&|7bL_&&0B2A%C=uh06zAkb|c*>Mq5^WXp8lTU1AsUT}v00kSA3Biy ze!q13^l52gVxoR%Xy_N+-QDlenUX0algUp82M3>K`v*vNZ*Q-}ebk%zBm4{x53A2l zo;>L}a^y(&fddEnGl9GAx~u>2;lq6=PMoNF=%I&lCMPGQ(b3VHZss0LO-)G$4<3|e zW@e;AhYm^m_wUabm - - - - - 网关 - - -

网关

- - \ No newline at end of file