.NET Core中的鉴权授权正确方式(.NET5)
一、简介
前后端分离的站点一般都会用jwt或IdentityServer4之类的生成token的方式进行登录鉴权。这里要说的是小项目没有做前后端分离的时站点登录授权的正确方式。
一、传统的授权方式
这里说一下传统授权方式,传统授权方式用session或cookies来完成。
1.在请求某个Action之前去做校验,验证当前操作者是否登录过,登录过就有权限
2.如果没有权限就跳转到登录页中去
3.传统登录授权用的AOP-Filter:ActionFilter。
具体实现为:
1.增加一个类CurrentUser.cs 保存用户登录信息
/// <summary>
/// 登录用户的信息
/// </summary>
public class CurrentUser
{
/// <summary>
/// 用户Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 用户名称
/// </summary>
public string Name { get; set; }
/// <summary>
/// 账号
/// </summary>
public string Account { get; set; }
}
2.建一个Cookice/Session帮助类CookieSessionHelper.cs
public static class CookieSessionHelper
{
public static void SetCookies(this HttpContext httpContext, string key, string value, int minutes = 30)
{
httpContext.Response.Cookies.Append(key, value, new CookieOptions
{
Expires = DateTime.Now.AddMinutes(minutes)
});
}
public static void DeleteCookies(this HttpContext httpContext, string key)
{
httpContext.Response.Cookies.Delete(key);
} public static string GetCookiesValue(this HttpContext httpContext, string key)
{
httpContext.Request.Cookies.TryGetValue(key, out string value);
return value;
}
public static CurrentUser GetCurrentUserByCookie(this HttpContext httpContext)
{
httpContext.Request.Cookies.TryGetValue("CurrentUser", out string sUser);
if (sUser == null)
{
return null;
}
else
{
CurrentUser currentUser = Newtonsoft.Json.JsonConvert.DeserializeObject<CurrentUser>(sUser);
return currentUser;
}
} public static CurrentUser GetCurrentUserBySession(this HttpContext context)
{
string sUser = context.Session.GetString("CurrentUser");
if (sUser == null)
{
return null;
}
else
{
CurrentUser currentUser = Newtonsoft.Json.JsonConvert.DeserializeObject<CurrentUser>(sUser);
return currentUser;
}
}
}
3.建一个登录控制器AccountController.cs
public class AccountController : Controller
{
//登录页面
public IActionResult Login()
{
return View();
}
//登录提交
[HttpPost]
public IActionResult LoginSub(IFormCollection fromData)
{
string userName = fromData["userName"].ToString();
string passWord = fromData["password"].ToString();
//真正写法是读数据库验证
if (userName == "test" && passWord == "123456")
{
#region 传统session/cookies
//登录成功,记录用户登录信息
CurrentUser currentUser = new CurrentUser()
{
Id = 123,
Name = "测试账号",
Account = userName
}; //写sessin
// HttpContext.Session.SetString("CurrentUser", JsonConvert.SerializeObject(currentUser));
//写cookies
HttpContext.SetCookies("CurrentUser", JsonConvert.SerializeObject(currentUser));
#endregion //跳转到首页
return RedirectToAction("Index", "Home"); }
else
{
TempData["err"] = "账号或密码不正确";
//账号密码不对,跳回登录页
return RedirectToAction("Login", "Account");
}
}
/// <summary>
/// 退出登录
/// </summary>
/// <returns></returns>
public IActionResult LogOut()
{
HttpContext.DeleteCookies("CurrentUser");
//Session方式
// HttpContext.Session.Remove("CurrentUser");
return RedirectToAction("Login", "Account");
}
}
4.登录页Login.cshtml 内容
<form action="/Account/LoginSub" method="post">
<div>
账号:<input type="text" name="userName" />
</div>
<div>
账号:<input type="password" name="passWord" />
</div>
<div>
<input type="submit" value="登录" /> <span style="color:#ff0000">@TempData["err"]</span>
</div>
</form>
5.建一个登录成功跳转到主页控制器HomeController.cs
public class HomeController : Controller
{
public IActionResult Index()
{
//从cookie获取用户信息
CurrentUser user = HttpContext.GetCurrentUserByCookie();
//CurrentUser user = HttpContext.GetCurrentUserBySession();
return View(user);
}
}
6.页面 Index.cshtml
@{
ViewData["Title"] = "Index";
} @model SessionAuthorized.Demo.Models.CurrentUser <h1>欢迎 @Model.Name 来到主页</h1>
<div><a href="/Account/Logout">退出登录</a></div>
7.增加鉴权过滤器MyActionAuthrizaFilterAttribute.cs,实现IActinFilter,在OnActionExecuting中写鉴权逻辑
public class MyActionAuthrizaFilterAttribute : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
//throw new NotImplementedException();
}
/// <summary>
/// 进入action前
/// </summary>
/// <param name="context"></param>
public void OnActionExecuting(ActionExecutingContext context)
{
//throw new NotImplementedException();
Console.WriteLine("开始验证权限...");
// CurrentUser currentUser = context.HttpContext.GetCurrentUserBySession();
CurrentUser currentUser = context.HttpContext.GetCurrentUserByCookie();
if (currentUser == null)
{
Console.WriteLine("没有权限...");
if (this.IsAjaxRequest(context.HttpContext.Request))
{
context.Result = new JsonResult(new
{
Success = false,
Message = "没有权限"
});
}
context.Result = new RedirectResult("/Account/Login");
return;
}
Console.WriteLine("权限验证成功...");
}
private bool IsAjaxRequest(HttpRequest request)
{
string header = request.Headers["X-Requested-With"];
return "XMLHttpRequest".Equals(header);
}
}
在需要鉴权的控制器或方法上加上这个Filter即可完成鉴权,这里在主页中加入鉴权,登录成功的用户才能访问
8.如果要用Session,还要在startup.cs中加入Session
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddSession(); }
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
} app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSession();
app.UseRouting(); app.UseAuthorization(); app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
到这里,传统的鉴权就完成了,下面验证一下效果。
三、 .NET5中正确的鉴权方式
传统的授权方式是通过Action Filter(before)来完成的,上图.Net Core的filter顺序可以发现,Action filter(befre)之前还有很多个filter,如果可以在前把鉴权做了,就能少跑了几步冤枉路,所以,正确的鉴权应该是在Authorization filter中做,Authorization filter是.NET5里面专门做鉴权授权用的。
怎么做呢,鉴权授权通过中间件支持。
1.在staup.cs的Configure方法里面的app.UseRouting();之后,在app.UseEndpoints()之前,增加鉴权授权;
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
} app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseSession();
app.UseRouting();
app.UseAuthentication();//检测用户是否登录
app.UseAuthorization(); //授权,检测有没有权限,是否能够访问功能 app.UseEndpoints(endpoints =>
{
endpoints.MapDefaultControllerRoute();
});
}
2.在ConfigureServices中增加
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
//services.AddSession(); 传统鉴权 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
options.LoginPath = new PathString("/Account/Login");//没登录跳到这个路径
}); }
3.标记哪些控制器或方法需要登录认证,在控制器或方法头标记特性[Authorize],如果里面有方法不需要登录验证的,加上匿名访问标识 [AllowAnonymousAttribute]
// [MyActionAuthrizaFilterAttribute] 传统授权
[Authorize]
public class HomeController : Controller
{
public IActionResult Index()
{
//从cookie获取用户信息
// CurrentUser user = HttpContext.GetCurrentUserByCookie();
//CurrentUser user = HttpContext.GetCurrentUserBySession(); var userInfo = HttpContext.User;
CurrentUser user = new CurrentUser()
{
Id = Convert.ToInt32(userInfo.FindFirst("id").Value),
Name = userInfo.Identity.Name,
Account=userInfo.FindFirst("account").Value
};
return View(user);
} /// <summary>
/// 无需登录,匿名访问
/// </summary>
/// <returns></returns>
[AllowAnonymousAttribute]
public IActionResult About()
{
return Content("欢迎来到关于页面");
}
}
4.登录处AccountController.cs的代码
public class AccountController : Controller
{
//登录页面
public IActionResult Login()
{
return View();
}
//登录提交
[HttpPost]
public IActionResult LoginSub(IFormCollection fromData)
{
string userName = fromData["userName"].ToString();
string passWord = fromData["password"].ToString();
//真正写法是读数据库验证
if (userName == "test" && passWord == "123456")
{
#region 传统session/cookies
//登录成功,记录用户登录信息
//CurrentUser currentUser = new CurrentUser()
//{
// Id = 123,
// Name = "测试账号",
// Account = userName
//}; //写sessin
// HttpContext.Session.SetString("CurrentUser", JsonConvert.SerializeObject(currentUser));
//写cookies
//HttpContext.SetCookies("CurrentUser", JsonConvert.SerializeObject(currentUser));
#endregion //用户角色列表,实际操作是读数据库
var roleList = new List<string>()
{
"Admin",
"Test"
};
var claims = new List<Claim>() //用Claim保存用户信息
{
new Claim(ClaimTypes.Name,"测试账号"),
new Claim("id","1"),
new Claim("account",userName),//...可以增加任意信息
};
//填充角色
foreach(var role in roleList)
{
claims.Add(new Claim(ClaimTypes.Role, role));
}
//把用户信息装到ClaimsPrincipal
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(claims, "Customer"));
//登录,把用户信息写入到cookie
HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, claimsPrincipal,
new AuthenticationProperties
{
ExpiresUtc = DateTime.Now.AddMinutes(30)//过期时间30分钟
}).Wait(); //跳转到首页
return RedirectToAction("Index", "Home"); }
else
{
TempData["err"] = "账号或密码不正确";
//账号密码不对,跳回登录页
return RedirectToAction("Login", "Account");
}
}
/// <summary>
/// 退出登录
/// </summary>
/// <returns></returns>
public IActionResult LogOut()
{
// HttpContext.DeleteCookies("CurrentUser");
//Session方式
// HttpContext.Session.Remove("CurrentUser"); HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return RedirectToAction("Login", "Account");
}
}
5.验证结果:
可以看到,一开始没登录状态,访问/Home/Index会跳转到登录页面,访问/Home/Index能成功访问,证明匿名访问ok,
后面的登录,显示用户信息,退出登录也没问题,证明功能没问题,鉴权到这里就完成了。
四、.NET5中角色授权
上面的claims中已经记录了用户角色,这个角色就可以用来做授权了。
在startup.cs中修改没权限时跳转页面路径
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
//services.AddSession(); 传统鉴权 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options => {
options.LoginPath = new PathString("/Account/Login");//没登录跳到这个路径
options.AccessDeniedPath = new PathString("/Account/AccessDenied");//没权限跳到这个路径
}); }
AccountController.cs增加方法
public IActionResult AccessDenied()
{
return View();
}
视图内容
没有权限访问-401
1.单个角色访问权限
在方法头加上特性 [Authorize(Roles ="角色代码")]
在HomeController.cs中增加一个方法
/// <summary>
/// 角色为Admin能访问
/// </summary>
/// <returns></returns>
[Authorize(Roles ="Admin")]
public IActionResult roleData1() {
return Content("Admin能访问");
}
验证。
开始角色为
访问roleData1数据:
访问成功,然后把角色Admin去掉
var roleList = new List<string>()
{
//"Admin",
"Test"
};
重新登录,在访问rleData1数据:
访问不成功,跳转到预设的没权限的页面了。
2.“多个角色包含一个”权限
[Authorize(Roles = "Admin,Test")]//多个角色用逗号隔开,角色包含有其中一个就能访问
public IActionResult roleData2()
{
return Content("roleData2访问成功");
}
3.“多个角色组合”权限
/// <summary>
/// 同时拥有标记的全部角色才能访问
/// </summary>
/// <returns></returns>
[Authorize(Roles = "Admin")]
[Authorize(Roles = "Test")]
public IActionResult roleData3()
{
return Content("roleData3访问成功");
}
三、自定义策略授权
上面的角色授权的缺点在哪里呢,最大的缺点就是角色要提前写死到方法上,如果要修改只能改代码,明显很麻烦,实际项目中权限都是根据配置修改的,
所以就要用到自定义策略授权了。
第一步:
增加一个CustomAuthorizatinRequirement.cs,要求实现接口:IAuthorizationRequirement
/// <summary>
/// 策略授权参数
/// </summary>
public class CustomAuthorizationRequirement: IAuthorizationRequirement
{
/// <summary>
///
/// </summary>
public CustomAuthorizationRequirement(string policyname)
{
this.Name = policyname;
} public string Name { get; set; }
}
增加CustomAuthorizationHandler.cs------专门做检验逻辑的;要求继承自AuthorizationHandler<>泛型抽象类;
/// <summary>
/// 自定义授权策略
/// </summary>
public class CustomAuthorizationHandler: AuthorizationHandler<CustomAuthorizationRequirement>
{
public CustomAuthorizationHandler()
{ }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthorizationRequirement requirement)
{ bool flag = false;
if (requirement.Name == "Policy01")
{
Console.WriteLine("进入自定义策略授权01...");
///策略1的逻辑
} if (requirement.Name == "Policy02")
{
Console.WriteLine("进入自定义策略授权02...");
///策略2的逻辑
} if(flag)
{
context.Succeed(requirement); //验证通过了
} return Task.CompletedTask; //验证不同过
}
}
第二步,让自定义的逻辑生效。
starup.cs的ConfigureServices方法中注册进来
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
//services.AddSession(); 传统鉴权
services.AddSingleton<IAuthorizationHandler, CustomAuthorizationHandler>(); services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.LoginPath = new PathString("/Account/Login");//没登录跳到这个路径
options.AccessDeniedPath = new PathString("/Account/AccessDenied");//没权限跳到这个路径
});
services.AddAuthorization(optins =>
{
//增加授权策略
optins.AddPolicy("customPolicy", polic =>
{
polic.AddRequirements(new CustomAuthorizationRequirement("Policy01")
// ,new CustomAuthorizationRequirement("Policy02")
);
});
}); }
第三步,把要进授权策略的控制器或方法增加标识
HomeContrller.cs增加测试方法
/// <summary>
/// 进入授权策略
/// </summary>
/// <returns></returns>
[Authorize(policy: "customPolicy")]
public IActionResult roleData4()
{
return Content("自定义授权策略");
}
访问roleData4,看是否进到自定义授权策略逻辑
可以看到自定义授权策略生效了,授权策略就可以在这里做了,下面加上授权逻辑。
我这里的权限用路径和角色关联授权,加上授权逻辑后的校验代码。
/// <summary>
/// 自定义授权策略
/// </summary>
public class CustomAuthorizationHandler : AuthorizationHandler<CustomAuthorizationRequirement>
{
public CustomAuthorizationHandler()
{ }
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, CustomAuthorizationRequirement requirement)
{
bool flag = false; //把context转换到httpConext,方便取上下文
HttpContext httpContext = context.Resource as HttpContext;
string path = httpContext.Request.Path;//当前访问路径,例:"/Home/roleData4" var user = httpContext.User;
//用户id
string userId = user.FindFirst("id").Value; //登录成功时根据角色查出来这个用户的权限存到redis,这里实际是根据用户id从redis查询出来
List<string> paths = new List<string>()
{
"/Home/roleData4",
"/Home/roleData3"
}; if (requirement.Name == "Policy01")
{
Console.WriteLine("进入自定义策略授权01...");
///策略1的逻辑
if (paths.Contains(path))
{
flag = true;
}
} if (requirement.Name == "Policy02")
{
Console.WriteLine("进入自定义策略授权02...");
///策略2的逻辑
} if (flag)
{
context.Succeed(requirement); //验证通过了
} return Task.CompletedTask; //验证不同过
}
}
加上逻辑后再访问。
访问成功,自定义授权策略完成。
源代码:https://github.com/weixiaolong325/SessionAuthorized.Demo
.NET Core中的鉴权授权正确方式(.NET5)的更多相关文章
- WebSocket 的鉴权授权方案
引子 WebSocket 是个好东西,为我们提供了便捷且实时的通讯能力.然而,对于 WebSocket 客户端的鉴权,协议的 RFC 是这么说的: This protocol doesn’t pres ...
- websocket之四:WebSocket 的鉴权授权方案
引子 WebSocket 是个好东西,为我们提供了便捷且实时的通讯能力.然而,对于 WebSocket 客户端的鉴权,协议的 RFC 是这么说的: This protocol doesn’t pres ...
- .Net 鉴权授权
在这里总结一下工作中遇到的鉴权和授权的方法 ① 固定token的方案 通过在nginx或者代码中写死token,或者通过在限制外网访问的方式已来达到安全授权的方式 ② session方案 分布式会话方 ...
- asp.net Core 中AuthorizationHandler 实现自定义授权
前言 ASP.NET Core 中 继承的是AuthorizationHandler ,而ASP.NET Framework 中继承的是AuthorizeAttribute. 它们都是用过重写里面的方 ...
- 第十五节:Asp.Net Core中的各种过滤器(授权、资源、操作、结果、异常)
一. 简介 1. 说明 提到过滤器,通常是指请求处理管道中特定阶段之前或之后的代码,可以处理:授权.响应缓存(对请求管道进行短路,以便返回缓存的响应). 防盗链.本地化国际化等,过滤器用于横向处理业务 ...
- ASP.NET Core 中读取 Request.Body 的正确姿势
ASP.NET Core 中的 Request.Body 虽然是一个 Stream ,但它是一个与众不同的 Stream —— 不允许 Request.Body.Position=0 ,这就意味着只能 ...
- 在.net core中数据操作的两种方式(Db first && Code first)
在开发过程中我们通常使用的是Db first这种模式,而在.net core 中推荐使用的却是 code first 反正我是很不习惯这种开发模式 于是就搜寻整个微软的官方文档,终于找到了有关.net ...
- Swift中编写单例的正确方式
在之前的帖子里聊过状态管理有多痛苦,有时这是不可避免的.一个状态管理的例子大家都很熟悉,那就是单例.使用Swift时,有许多方法实现单例,这是个麻烦事,因为我们不知道哪个最合适.这里我们来回顾一下单例 ...
- 在.NET Core 中收集数据的几种方式
APM是一种应用性能监控工具,可以帮助理解系统行为, 用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题, 通过汇聚业务系统各处理环节的实时数据,分析业务系统各事务处理的交易路径和处理 ...
随机推荐
- 记录一次基于VuePress + Github 搭建个人博客
最终效果图 网站:https://chandler712.github.io/ 一.前言 VuePress 是尤雨溪推出的支持 Vue 及其子项目的文档需求而写的一个项目,UI简洁大方,官方文档详细容 ...
- 从零入门 Serverless | SAE 的远程调试和云端联调
作者 | 弈川 阿里巴巴云原生团队 导读:本节课程包含三部分内容,前两个部分简单介绍远程调试以及端云联调的原理,最后在 Serverless 应用引擎中进行实际演示. 经过之前课程的学习,相信大家对于 ...
- Python中生成器的理解
1.生成器的定义 在Python中一边循环一边计算的机制,称为生成器 2.为什么要有生成器 列表所有的数据都存在内存中,如果有海量的数据将非常耗内存 如:仅仅需要访问前面几个元素,那后面绝大多数元素占 ...
- 用C++实现的数独解题程序 SudokuSolver 2.2 及实例分析
SudokuSolver 2.2 程序实现 根据 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析 里分析,对 2.1 版做了一些改进和尝试. CQuizDealer 类声明部 ...
- 2020.10.30--vj个人赛补题
D - D CodeForces - 743A Vladik is a competitive programmer. This year he is going to win the Interna ...
- Nginx安装及核心配置解析
安装 使用yum进行安装 yum install -y nginx 查看nginx的安装位置 whereis nginx 启动测试 nginx 核心配置文件结构 读取Nginx自带的Nginx配置文件 ...
- 从 MVC 到使用 ASP.NET Core 6.0 的最小 API
从 MVC 到使用 ASP.NET Core 6.0 的最小 API https://benfoster.io/blog/mvc-to-minimal-apis-aspnet-6/ 2007 年,随着 ...
- activiti会签 多实例例子
在实际的业务中,可能存在存在这么一种情况,当流程运行到某一个环节时,可能需要同时多个人的参与,才可以完成此环节.此时就可以用到activiti的多实例来解决此问题. 一.将一个节点设置成多实例的方法: ...
- 基于RequestBodyAdvice和ResponseBodyAdvice来实现spring中参数的加密和解密
在日常开发中,有时候我们经常需要和第三方接口打交道,有时候是我们调用别人的第三方接口,有时候是别人在调用我们的第三方接口,那么为了调用接口的安全性,一般都会对传输的数据进行加密操作,如果每个接口都由我 ...
- [BZOJ3307] 雨天的尾巴-----------------线段树进阶
虽然是个板子,但用到了差分思想. Description N个点,形成一个树状结构.有M次发放,每次选择两个点x,y对于x到y的路径上(含x,y)每个点发一袋Z类型的物品.完成所有发放后,每个点存放最 ...