From 3ce6de64e127cf61831e6c86c8c2fabaed5e193c Mon Sep 17 00:00:00 2001 From: wanggang <76527413@qq.com> Date: Fri, 3 Jan 2020 17:21:15 +0800 Subject: [PATCH] update Former-commit-id: 627d14884a1871f6d9322ac991f2fd6c5f4318ee --- docs/研发/网关权限验证.xmind | Bin 0 -> 39874 bytes .../Extensions/HttpContextExtensions.cs | 27 +++++ .../Extensions/HttpRequestExtensions.cs | 6 + projects/Infrastructure/Infrastructure.csproj | 3 +- projects/Infrastructure/Web/BaseStartup.cs | 106 +++++++++++++++--- .../Controllers/AccountController.cs | 4 +- projects/UserCenter/Program.cs | 2 + projects/UserCenter/UserCenter.csproj | 4 +- 8 files changed, 133 insertions(+), 19 deletions(-) create mode 100644 docs/研发/网关权限验证.xmind diff --git a/docs/研发/网关权限验证.xmind b/docs/研发/网关权限验证.xmind new file mode 100644 index 0000000000000000000000000000000000000000..8072a2138242303300b02c6476cb84f6adb652ba GIT binary patch literal 39874 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 literal 0 HcmV?d00001 diff --git a/projects/Infrastructure/Extensions/HttpContextExtensions.cs b/projects/Infrastructure/Extensions/HttpContextExtensions.cs index df99a400..96f0a6b5 100644 --- a/projects/Infrastructure/Extensions/HttpContextExtensions.cs +++ b/projects/Infrastructure/Extensions/HttpContextExtensions.cs @@ -1,9 +1,14 @@ +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 { @@ -16,5 +21,27 @@ namespace Infrastructure.Extensions var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme, "Name", "Role")); httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, new AuthenticationProperties { IsPersistent = rememberMe }); } + + public static void SignIn(this HttpContext httpContext, string userName, IEnumerable roles, bool rememberMe, IConfiguration cfg) + { + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(cfg["jwt:key"])); + var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); + + var claims = new List { new Claim("Name", userName) }; + claims.AddRange(roles.Select(o => new Claim("Role", o)).ToList()); + var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme, "Name", "Role")); + var token = new JwtSecurityToken( + issuer: cfg["jwt:issuer"], + audience: cfg["jwt:audience"], + claims: claims, + expires: DateTime.Now.AddMinutes(rememberMe ? 3600 : 3), + signingCredentials: creds); + + var tokenText = new JwtSecurityTokenHandler().WriteToken(token); + var newBearerToken = "Bearer " + tokenText; + httpContext.Response.Cookies.Delete("jwt"); + httpContext.Response.Cookies.Append("jwt", tokenText); + //httpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal, new AuthenticationProperties { IsPersistent = rememberMe }); + } } } \ No newline at end of file diff --git a/projects/Infrastructure/Extensions/HttpRequestExtensions.cs b/projects/Infrastructure/Extensions/HttpRequestExtensions.cs index c4587a8a..64a694d1 100644 --- a/projects/Infrastructure/Extensions/HttpRequestExtensions.cs +++ b/projects/Infrastructure/Extensions/HttpRequestExtensions.cs @@ -5,6 +5,12 @@ namespace Infrastructure.Extensions { public static class HttpRequestExtensions { + public static bool IsAjax(this HttpRequest request) + { + var key = "x-requested-with"; + return request.Headers.ContainsKey(key) && request.Headers[key] == "XMLHttpRequest"; + } + public static string GetUrl(this HttpRequest request) { return UriHelper.GetEncodedUrl(request); diff --git a/projects/Infrastructure/Infrastructure.csproj b/projects/Infrastructure/Infrastructure.csproj index 34b0b33f..821d2e2d 100644 --- a/projects/Infrastructure/Infrastructure.csproj +++ b/projects/Infrastructure/Infrastructure.csproj @@ -8,7 +8,8 @@ - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/projects/Infrastructure/Web/BaseStartup.cs b/projects/Infrastructure/Web/BaseStartup.cs index de2ef5d8..35386796 100644 --- a/projects/Infrastructure/Web/BaseStartup.cs +++ b/projects/Infrastructure/Web/BaseStartup.cs @@ -2,6 +2,7 @@ using Hangfire; using Hangfire.MemoryStorage; using Infrastructure.Data; using Infrastructure.Events; +using Infrastructure.Extensions; using Infrastructure.Jwt; using Infrastructure.Office; using Infrastructure.Security; @@ -10,6 +11,7 @@ using Infrastructure.Web.Authentication.Cookies; using Infrastructure.Web.Mvc.ModelBinding.Metadata; using Infrastructure.Web.SignalR; using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http.Features; @@ -26,13 +28,17 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; +using Microsoft.IdentityModel.Tokens; using Microsoft.OpenApi.Models; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using System; 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; using System.Threading.Tasks; @@ -174,27 +180,60 @@ namespace Infrastructure.Web public virtual void AddAuthentication(IServiceCollection services) { - services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) - .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opts => + services.AddAuthentication(x => { - opts.Cookie.Name = this.GetType().FullName; - opts.LoginPath = "/Account/Login"; - opts.LogoutPath = "/Account/Logout"; - opts.AccessDeniedPath = "/Account/AccessDenied"; - //不配置SessionStore则存储到cookie - var useCookieSessionStore = this._cfg.GetSection("AppSettings").GetValue("UseCookieSessionStore"); - if (!useCookieSessionStore) + x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; + x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; + }) + .AddJwtBearer(o => + { + o.TokenValidationParameters = new TokenValidationParameters { - opts.SessionStore = services.BuildServiceProvider().GetService(); - } - opts.Events = new CookieAuthenticationEvents + ValidateIssuerSigningKey = true, + IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_cfg["jwt:key"])), + ValidateIssuer = false, + ValidIssuer = _cfg["jwt:issuer"], + ValidateAudience = false, + ValidAudience = _cfg["jwt:audience"] + }; + o.Events = new JwtBearerEvents { - //OnRedirectToLogin = RedirectToLogin(), - OnValidatePrincipal = ValidatePrincipal + OnTokenValidated = TokenValidated, + OnForbidden = OnForbidden, + OnAuthenticationFailed = OnAuthenticationFailed, + OnChallenge = context => + { + if (!context.Request.IsAjax()) + { + context.Response.Redirect("/Account/Login"); + context.HandleResponse(); + } + return Task.CompletedTask; + }, + OnMessageReceived = context => + { + if (context.Request.Cookies.Keys.Contains("jwt")) + { + context.Token = context.Request.Cookies["jwt"]; + } + return Task.CompletedTask; + } }; + o.SecurityTokenValidators.Clear(); + o.SecurityTokenValidators.Insert(0, new InvalidTokenValidator()); }); } + private Task OnAuthenticationFailed(AuthenticationFailedContext arg) + { + return Task.CompletedTask; + } + + private Task OnForbidden(ForbiddenContext arg) + { + return Task.CompletedTask; + } + public virtual void AddSwagger(IServiceCollection services) { services.AddSwaggerGen(c => @@ -319,5 +358,44 @@ namespace Infrastructure.Web { return Task.CompletedTask; } + + public virtual Task TokenValidated(TokenValidatedContext arg) + { + return Task.CompletedTask; + } + } + + internal class InvalidTokenValidator : ISecurityTokenValidator + { + public InvalidTokenValidator() + { + ExceptionType = typeof(SecurityTokenException); + } + + public InvalidTokenValidator(Type exceptionType) + { + ExceptionType = exceptionType; + } + + public Type ExceptionType { get; set; } + + public bool CanValidateToken => true; + + public int MaximumTokenSizeInBytes + { + get { throw new NotImplementedException(); } + set { throw new NotImplementedException(); } + } + + public bool CanReadToken(string securityToken) => true; + + public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken) + { + var constructor = ExceptionType.GetTypeInfo().GetConstructor(new[] { typeof(string) }); + var exception = (Exception)constructor.Invoke(new[] { ExceptionType.Name }); + var token = new JwtSecurityTokenHandler().ReadJwtToken(securityToken); + validatedToken = token; + return new ClaimsPrincipal(new ClaimsIdentity(token.Claims, JwtBearerDefaults.AuthenticationScheme, "Name", "Role")); + } } } \ No newline at end of file diff --git a/projects/UserCenter/Controllers/AccountController.cs b/projects/UserCenter/Controllers/AccountController.cs index 07174df1..61ff81a0 100644 --- a/projects/UserCenter/Controllers/AccountController.cs +++ b/projects/UserCenter/Controllers/AccountController.cs @@ -210,7 +210,7 @@ namespace UserCenter.Controllers .SelectMany(o => o.RolePermissions) .Select(o => o.Permission.Number) .ToList(); - HttpContext.SignIn(model.UserName, userPermissions, model.RememberMe); + HttpContext.SignIn(model.UserName, userPermissions, model.RememberMe, _cfg); if (string.IsNullOrEmpty(returnUrl)) { returnUrl = Url.Action("Index", "Home"); @@ -940,7 +940,7 @@ namespace UserCenter.Controllers public IActionResult HasLogin() { Response.Headers["Content-Type"] = "application/javascript"; - return Content($"var hasLogin={(User.Identity.IsAuthenticated?"true":"false")}"); + return Content($"var hasLogin={(User.Identity.IsAuthenticated ? "true" : "false")}"); } #region tools diff --git a/projects/UserCenter/Program.cs b/projects/UserCenter/Program.cs index a97418ba..5858a59d 100644 --- a/projects/UserCenter/Program.cs +++ b/projects/UserCenter/Program.cs @@ -17,6 +17,8 @@ namespace UserCenter new EFConfigurationValue { Id = "server.urls", Value= "http://*:8010" }, new EFConfigurationValue { Id = "security:key", Value= "111111111111111111111111"}, new EFConfigurationValue { Id = "jwt:key", Value= "111111111111111111111111"}, + new EFConfigurationValue { Id = "jwt:issuer", Value= "111111111111111111111111"}, + new EFConfigurationValue { Id = "jwt:audience", Value= "111111111111111111111111"}, new EFConfigurationValue { Id = "email:host", Value= "nbaxp.com"}, new EFConfigurationValue { Id = "email:port", Value= "25"}, new EFConfigurationValue { Id = "email:user", Value= "admin@nbaxp.com"}, diff --git a/projects/UserCenter/UserCenter.csproj b/projects/UserCenter/UserCenter.csproj index f32ca769..3412ec11 100644 --- a/projects/UserCenter/UserCenter.csproj +++ b/projects/UserCenter/UserCenter.csproj @@ -12,8 +12,8 @@ - - + +