diff --git a/docs/研发/网关权限验证.xmind b/docs/研发/网关权限验证.xmind new file mode 100644 index 00000000..8072a213 Binary files /dev/null and b/docs/研发/网关权限验证.xmind differ 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 @@ - - + +