优化Swagger

1.swagger登录可以用用户账号登录,如果登录成功 token存在session中 之前默认admin感觉没什么用 当然也可以扩展User 加个字段是否开发者帐户等类似的
2.优化权限校验 优先读取Header->没有读取Session 中token解析用户
This commit is contained in:
LemonNoCry 2023-05-24 11:19:36 +08:00
parent a979d36461
commit 0cea9672b5
No known key found for this signature in database
11 changed files with 774 additions and 598 deletions

View File

@ -177,7 +177,7 @@
登录管理【无权限】
</summary>
</member>
<member name="M:Blog.Core.Controllers.LoginController.#ctor(Blog.Core.IServices.ISysUserInfoServices,Blog.Core.IServices.IUserRoleServices,Blog.Core.IServices.IRoleServices,Blog.Core.AuthHelper.PermissionRequirement,Blog.Core.IServices.IRoleModulePermissionServices)">
<member name="M:Blog.Core.Controllers.LoginController.#ctor(Blog.Core.IServices.ISysUserInfoServices,Blog.Core.IServices.IUserRoleServices,Blog.Core.IServices.IRoleServices,Blog.Core.AuthHelper.PermissionRequirement,Blog.Core.IServices.IRoleModulePermissionServices,Microsoft.Extensions.Logging.ILogger{Blog.Core.Controllers.LoginController})">
<summary>
构造函数注入
</summary>

View File

@ -9,6 +9,8 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Blog.Core.Common.Swagger;
using Serilog;
namespace Blog.Core.Controllers
@ -26,7 +28,7 @@ namespace Blog.Core.Controllers
readonly IRoleServices _roleServices;
readonly PermissionRequirement _requirement;
private readonly IRoleModulePermissionServices _roleModulePermissionServices;
private readonly ILogger<LoginController> _logger;
/// <summary>
/// 构造函数注入
@ -36,13 +38,16 @@ namespace Blog.Core.Controllers
/// <param name="roleServices"></param>
/// <param name="requirement"></param>
/// <param name="roleModulePermissionServices"></param>
public LoginController(ISysUserInfoServices sysUserInfoServices, IUserRoleServices userRoleServices, IRoleServices roleServices, PermissionRequirement requirement, IRoleModulePermissionServices roleModulePermissionServices)
public LoginController(ISysUserInfoServices sysUserInfoServices, IUserRoleServices userRoleServices,
IRoleServices roleServices, PermissionRequirement requirement,
IRoleModulePermissionServices roleModulePermissionServices, ILogger<LoginController> logger)
{
this._sysUserInfoServices = sysUserInfoServices;
this._userRoleServices = userRoleServices;
this._roleServices = roleServices;
_requirement = requirement;
_roleModulePermissionServices = roleModulePermissionServices;
_logger = logger;
}
@ -139,6 +144,7 @@ namespace Blog.Core.Controllers
[HttpGet]
[Route("JWTToken3.0")]
public async Task<MessageModel<TokenInfoViewModel>> GetJwtToken3(string name = "", string pass = "")
{
string jwtStr = string.Empty;
@ -147,7 +153,8 @@ namespace Blog.Core.Controllers
pass = MD5Helper.MD5Encrypt32(pass);
var user = await _sysUserInfoServices.Query(d => d.LoginName == name && d.LoginPWD == pass && d.IsDeleted == false);
var user = await _sysUserInfoServices.Query(d =>
d.LoginName == name && d.LoginPWD == pass && d.IsDeleted == false);
if (user.Count > 0)
{
var userRoles = await _sysUserInfoServices.GetUserRoleNameStr(name, pass);
@ -158,7 +165,8 @@ namespace Blog.Core.Controllers
new Claim(JwtRegisteredClaimNames.Jti, user.FirstOrDefault().Id.ToString()),
new Claim("TenantId", user.FirstOrDefault().TenantId.ToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.ToString()),
new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString())
new Claim(ClaimTypes.Expiration,
DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString())
};
claims.AddRange(userRoles.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
@ -221,7 +229,8 @@ namespace Blog.Core.Controllers
new Claim(ClaimTypes.Name, user.LoginName),
new Claim(JwtRegisteredClaimNames.Jti, tokenModel.Uid.ObjToString()),
new Claim(JwtRegisteredClaimNames.Iat, DateTime.Now.ToString()),
new Claim(ClaimTypes.Expiration, DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString())
new Claim(ClaimTypes.Expiration,
DateTime.Now.AddSeconds(_requirement.Expiration.TotalSeconds).ToString())
};
claims.AddRange(userRoles.Split(',').Select(s => new Claim(ClaimTypes.Role, s)));
@ -248,7 +257,8 @@ namespace Blog.Core.Controllers
/// <returns></returns>
[HttpGet]
[Route("jsonp")]
public void Getjsonp(string callBack, long id = 1, string sub = "Admin", int expiresSliding = 30, int expiresAbsoulute = 30)
public void Getjsonp(string callBack, long id = 1, string sub = "Admin", int expiresSliding = 30,
int expiresAbsoulute = 30)
{
TokenModelJwt tokenModel = new TokenModelJwt
{
@ -283,14 +293,27 @@ namespace Blog.Core.Controllers
/// <returns></returns>
[HttpPost]
[Route("/api/Login/swgLogin")]
public dynamic SwgLogin([FromBody] SwaggerLoginRequest loginRequest)
public async Task<dynamic> SwgLogin([FromBody] SwaggerLoginRequest loginRequest)
{
// 这里可以查询数据库等各种校验
if (loginRequest?.name == "admin" && loginRequest?.pwd == "admin")
if (loginRequest is null)
{
HttpContext.Session.SetString("swagger-code", "success");
return new {result = false};
}
try
{
var result = await GetJwtToken3(loginRequest.name, loginRequest.pwd);
if (result.success)
{
HttpContext.SuccessSwagger();
HttpContext.SuccessSwaggerJwt(result.response.token);
return new {result = true};
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Swagger登录异常");
}
return new {result = false};
}

View File

@ -47,7 +47,7 @@
},
"LogToDb": true,
"LogAOP": {
"Enabled": true,
"Enabled": false,
"LogToFile": {
"Enabled": true
},

View File

@ -15,7 +15,7 @@
<div class="login"><img src="/logo.jpg.jpg" height="30" alt="Alternate Text"/> Blog.Core 接口文档</div>
<div class="eula">欢迎使用!</div>
<div class="eula">用户名admin密码admin</div>
<div class="eula">使用用户账号登录</div>
</div>
<div class="right">
<svg viewBox="0 0 320 300">
@ -98,6 +98,14 @@
}
});
});
function GetQueryString(name) {
var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
var r = window.location.search.substr(1).match(reg);
if (r != null) return decodeURI(r[2]);
return null;
}
function submit() {
let postdata = {
"name": $("#email").val(),
@ -115,7 +123,12 @@
dataType: 'json',
success: function (data) {
if (data?.result) {
var returnUrl = GetQueryString("returnUrl");
if (returnUrl != null && returnUrl.length > 0) {
window.location.href = returnUrl;
} else {
window.location.href = "/index.html";
}
} else {
alert('参数不正确');
}

View File

@ -0,0 +1,19 @@
using System;
using Microsoft.AspNetCore.Http;
namespace Blog.Core.Common.Extensions;
public static class HttpContextExtension
{
public static ISession GetSession(this HttpContext context)
{
try
{
return context.Session;
}
catch (Exception)
{
return default;
}
}
}

View File

@ -1,7 +1,9 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using Blog.Core.Common.Swagger;
using Blog.Core.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
@ -51,7 +53,25 @@ namespace Blog.Core.Common.HttpContextUser
public string GetToken()
{
return _accessor.HttpContext?.Request?.Headers["Authorization"].ObjToString().Replace("Bearer ", "");
var token = _accessor.HttpContext?.Request?.Headers["Authorization"].ObjToString().Replace("Bearer ", "");
if (!token.IsNullOrEmpty())
{
return token;
}
if (_accessor.HttpContext?.IsSuccessSwagger() == true)
{
token = _accessor.HttpContext.GetSuccessSwaggerJwt();
if (token.IsNotEmptyOrNull())
{
var claims = new ClaimsIdentity(GetClaimsIdentity(token));
_accessor.HttpContext.User.AddIdentity(claims);
return token;
}
}
return token;
}
public List<string> GetUserInfoFromToken(string ClaimType)
@ -77,6 +97,10 @@ namespace Blog.Core.Common.HttpContextUser
public IEnumerable<Claim> GetClaimsIdentity()
{
if (_accessor.HttpContext == null) return ArraySegment<Claim>.Empty;
if (!IsAuthenticated()) return GetClaimsIdentity(GetToken());
var claims = _accessor.HttpContext.User.Claims.ToList();
var headers = _accessor.HttpContext.Request.Headers;
foreach (var header in headers)
@ -86,6 +110,19 @@ namespace Blog.Core.Common.HttpContextUser
return claims;
}
public IEnumerable<Claim> GetClaimsIdentity(string token)
{
var jwtHandler = new JwtSecurityTokenHandler();
// token校验
if (token.IsNotEmptyOrNull() && jwtHandler.CanReadToken(token))
{
var jwtToken = jwtHandler.ReadJwtToken(token);
return jwtToken.Claims;
}
return new List<Claim>();
}
public List<string> GetClaimValueByType(string ClaimType)
{

View File

@ -0,0 +1,48 @@
using Blog.Core.Common.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Extensions;
namespace Blog.Core.Common.Swagger;
public static class SwaggerContextExtension
{
public const string SwaggerCodeKey = "swagger-code";
public const string SwaggerJwt = "swagger-jwt";
public static bool IsSuccessSwagger()
{
return App.HttpContext?.GetSession()?.GetString(SwaggerCodeKey) == "success";
}
public static bool IsSuccessSwagger(this HttpContext context)
{
return context.GetSession()?.GetString(SwaggerCodeKey) == "success";
}
public static void SuccessSwagger()
{
App.HttpContext?.GetSession()?.SetString(SwaggerCodeKey, "success");
}
public static void SuccessSwagger(this HttpContext context)
{
context.GetSession()?.SetString(SwaggerCodeKey, "success");
}
public static void SuccessSwaggerJwt(this HttpContext context, string token)
{
context.GetSession()?.SetString(SwaggerJwt, token);
}
public static string GetSuccessSwaggerJwt(this HttpContext context)
{
return context.GetSession()?.GetString(SwaggerJwt);
}
public static void RedirectSwaggerLogin(this HttpContext context)
{
var returnUrl = context.Request.GetDisplayUrl(); //获取当前url地址
context.Response.Redirect("/swg-login.html?returnUrl=" + returnUrl);
}
}

View File

@ -14,6 +14,7 @@ using System.Linq;
using System.Security.Claims;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Blog.Core.Common.Swagger;
using Blog.Core.Model.Models;
namespace Blog.Core.AuthHelper
@ -127,10 +128,79 @@ namespace Blog.Core.AuthHelper
var isTestCurrent = AppSettings.app(new string[] {"AppSettings", "UseLoadTest"}).ObjToBool();
//result?.Principal不为空即登录成功
if (result?.Principal != null || isTestCurrent)
if (result?.Principal != null || isTestCurrent || httpContext.IsSuccessSwagger())
{
if (!isTestCurrent) httpContext.User = result.Principal;
//应该要先校验用户的信息 再校验菜单权限相关的
//校验用户
var user = await _userServices.QueryById(_user.ID, true);
if (user == null)
{
_user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户不存在或已被删除").MessageModel;
context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
return;
}
if (user.IsDeleted)
{
_user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被删除,禁止登陆!").MessageModel;
context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
return;
}
if (!user.Enable)
{
_user.MessageModel = new ApiResponse(StatusCode.CODE401, "用户已被禁用!禁止登陆!").MessageModel;
context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
return;
}
// 判断token是否过期过期则重新登录
var isExp = false;
// ids4和jwt切换
// ids4
if (Permissions.IsUseIds4)
{
isExp = (httpContext.User.Claims.FirstOrDefault(s => s.Type == "exp")?.Value) != null &&
DateHelper.StampToDateTime(httpContext.User.Claims
.FirstOrDefault(s => s.Type == "exp")?.Value) >= DateTime.Now;
}
else
{
// jwt
isExp =
(httpContext.User.Claims.FirstOrDefault(s => s.Type == ClaimTypes.Expiration)
?.Value) != null &&
DateTime.Parse(httpContext.User.Claims
.FirstOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.Now;
}
if (!isExp)
{
context.Fail(new AuthorizationFailureReason(this, "授权已过期,请重新授权"));
return;
}
//校验签发时间
if (!Permissions.IsUseIds4)
{
var value = httpContext.User.Claims
.FirstOrDefault(s => s.Type == JwtRegisteredClaimNames.Iat)?.Value;
if (value != null)
{
if (user.CriticalModifyTime > value.ObjToDate())
{
_user.MessageModel = new ApiResponse(StatusCode.CODE401, "很抱歉,授权已失效,请重新授权")
.MessageModel;
context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
return;
}
}
}
// 获取当前用户的角色信息
var currentUserRoles = new List<string>();
// ids4和jwt切换
@ -153,7 +223,8 @@ namespace Blog.Core.AuthHelper
if (currentUserRoles.All(s => s != "SuperAdmin"))
{
var isMatchRole = false;
var permisssionRoles = requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role));
var permisssionRoles =
requirement.Permissions.Where(w => currentUserRoles.Contains(w.Role));
foreach (var item in permisssionRoles)
{
try
@ -178,49 +249,6 @@ namespace Blog.Core.AuthHelper
}
}
// 判断token是否过期过期则重新登录
var isExp = false;
// ids4和jwt切换
// ids4
if (Permissions.IsUseIds4)
{
isExp = (httpContext.User.Claims.SingleOrDefault(s => s.Type == "exp")?.Value) != null &&
DateHelper.StampToDateTime(httpContext.User.Claims
.SingleOrDefault(s => s.Type == "exp")?.Value) >= DateTime.Now;
}
else
{
// jwt
isExp =
(httpContext.User.Claims.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)
?.Value) != null &&
DateTime.Parse(httpContext.User.Claims
.SingleOrDefault(s => s.Type == ClaimTypes.Expiration)?.Value) >= DateTime.Now;
}
if (!isExp)
{
context.Fail(new AuthorizationFailureReason(this, "授权已过期,请重新授权"));
return;
}
//校验签发时间
if (!Permissions.IsUseIds4)
{
var value = httpContext.User.Claims
.SingleOrDefault(s => s.Type == JwtRegisteredClaimNames.Iat)?.Value;
if (value != null)
{
var user = await _userServices.QueryById(_user.ID, true);
if (user.CriticalModifyTime > value.ObjToDate())
{
_user.MessageModel = new ApiResponse(StatusCode.CODE401, "很抱歉,授权已失效,请重新授权")
.MessageModel;
context.Fail(new AuthorizationFailureReason(this, _user.MessageModel.msg));
return;
}
}
}
context.Succeed(requirement);
return;

View File

@ -1,5 +1,6 @@
using System.Net;
using System.Threading.Tasks;
using Blog.Core.Common.Swagger;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http;
@ -28,7 +29,7 @@ namespace Blog.Core.Extensions.Middlewares
}
// 无权限跳转swagger登录页
context.Response.Redirect("/swg-login.html");
context.RedirectSwaggerLogin();
}
else
{
@ -40,7 +41,7 @@ namespace Blog.Core.Extensions.Middlewares
{
// 使用session模式
// 可以使用其他的
return context.Session.GetString("swagger-code") == "success";
return context.IsSuccessSwagger();
}
/// <summary>

View File

@ -117,6 +117,9 @@ namespace Blog.Core.Model.Models
[SugarColumn(Length = 200, IsNullable = true)]
public string Address { get; set; }
[SugarColumn(DefaultValue = "1")]
public bool Enable { get; set; } = true;
[SugarColumn(IsNullable = true)]
public bool IsDeleted { get; set; }

View File

@ -3,6 +3,7 @@ using Blog.Core.IServices;
using Xunit;
using Autofac;
using Blog.Core.AuthHelper;
using Microsoft.Extensions.Logging;
namespace Blog.Core.Tests
{
@ -15,11 +16,11 @@ namespace Blog.Core.Tests
private readonly IRoleServices _roleServices;
private readonly PermissionRequirement _requirement;
private readonly IRoleModulePermissionServices _roleModulePermissionServices;
private readonly ILogger<LoginController> _logger;
DI_Test dI_Test = new DI_Test();
public LoginController_Should()
{
var container = dI_Test.DICollections();
@ -28,7 +29,9 @@ namespace Blog.Core.Tests
_roleServices = container.Resolve<IRoleServices>();
_requirement = container.Resolve<PermissionRequirement>();
_roleModulePermissionServices = container.Resolve<IRoleModulePermissionServices>();
loginController = new LoginController(_sysUserInfoServices,_userRoleServices,_roleServices,_requirement, _roleModulePermissionServices);
_logger = container.Resolve<ILogger<LoginController>>();
loginController = new LoginController(_sysUserInfoServices, _userRoleServices, _roleServices, _requirement,
_roleModulePermissionServices, _logger);
}
[Fact]
@ -38,6 +41,7 @@ namespace Blog.Core.Tests
Assert.NotNull(data);
}
[Fact]
public void GetJwtStrForNuxtTest()
{
@ -49,7 +53,6 @@ namespace Blog.Core.Tests
[Fact]
public async void GetJwtToken3Test()
{
var res = await loginController.GetJwtToken3("test", "test");
Assert.NotNull(res);
@ -58,7 +61,8 @@ namespace Blog.Core.Tests
[Fact]
public async void RefreshTokenTest()
{
var res = await loginController.RefreshToken("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdCIsImp0aSI6IjgiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2V4cGlyYXRpb24iOiIyMDE5LzEwLzE4IDIzOjI2OjQ5IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5UZXN0IiwibmJmIjoxNTcxNDA4ODA5LCJleHAiOjE1NzE0MTI0MDksImlzcyI6IkJsb2cuQ29yZSIsImF1ZCI6IndyIn0.oz-SPz6UCL78fM09bUecw5rmjcNYEY9dWGtuPs2gdBg");
var res = await loginController.RefreshToken(
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1lIjoidGVzdCIsImp0aSI6IjgiLCJodHRwOi8vc2NoZW1hcy5taWNyb3NvZnQuY29tL3dzLzIwMDgvMDYvaWRlbnRpdHkvY2xhaW1zL2V4cGlyYXRpb24iOiIyMDE5LzEwLzE4IDIzOjI2OjQ5IiwiaHR0cDovL3NjaGVtYXMubWljcm9zb2Z0LmNvbS93cy8yMDA4LzA2L2lkZW50aXR5L2NsYWltcy9yb2xlIjoiQWRtaW5UZXN0IiwibmJmIjoxNTcxNDA4ODA5LCJleHAiOjE1NzE0MTI0MDksImlzcyI6IkJsb2cuQ29yZSIsImF1ZCI6IndyIn0.oz-SPz6UCL78fM09bUecw5rmjcNYEY9dWGtuPs2gdBg");
Assert.NotNull(res);
}