一、简介

前后端分离的站点一般都会用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)的更多相关文章

  1. WebSocket 的鉴权授权方案

    引子 WebSocket 是个好东西,为我们提供了便捷且实时的通讯能力.然而,对于 WebSocket 客户端的鉴权,协议的 RFC 是这么说的: This protocol doesn’t pres ...

  2. websocket之四:WebSocket 的鉴权授权方案

    引子 WebSocket 是个好东西,为我们提供了便捷且实时的通讯能力.然而,对于 WebSocket 客户端的鉴权,协议的 RFC 是这么说的: This protocol doesn’t pres ...

  3. .Net 鉴权授权

    在这里总结一下工作中遇到的鉴权和授权的方法 ① 固定token的方案 通过在nginx或者代码中写死token,或者通过在限制外网访问的方式已来达到安全授权的方式 ② session方案 分布式会话方 ...

  4. asp.net Core 中AuthorizationHandler 实现自定义授权

    前言 ASP.NET Core 中 继承的是AuthorizationHandler ,而ASP.NET Framework 中继承的是AuthorizeAttribute. 它们都是用过重写里面的方 ...

  5. 第十五节:Asp.Net Core中的各种过滤器(授权、资源、操作、结果、异常)

    一. 简介 1. 说明 提到过滤器,通常是指请求处理管道中特定阶段之前或之后的代码,可以处理:授权.响应缓存(对请求管道进行短路,以便返回缓存的响应). 防盗链.本地化国际化等,过滤器用于横向处理业务 ...

  6. ASP.NET Core 中读取 Request.Body 的正确姿势

    ASP.NET Core 中的 Request.Body 虽然是一个 Stream ,但它是一个与众不同的 Stream —— 不允许 Request.Body.Position=0 ,这就意味着只能 ...

  7. 在.net core中数据操作的两种方式(Db first && Code first)

    在开发过程中我们通常使用的是Db first这种模式,而在.net core 中推荐使用的却是 code first 反正我是很不习惯这种开发模式 于是就搜寻整个微软的官方文档,终于找到了有关.net ...

  8. Swift中编写单例的正确方式

    在之前的帖子里聊过状态管理有多痛苦,有时这是不可避免的.一个状态管理的例子大家都很熟悉,那就是单例.使用Swift时,有许多方法实现单例,这是个麻烦事,因为我们不知道哪个最合适.这里我们来回顾一下单例 ...

  9. 在.NET Core 中收集数据的几种方式

    APM是一种应用性能监控工具,可以帮助理解系统行为, 用于分析性能问题的工具,以便发生故障的时候,能够快速定位和解决问题, 通过汇聚业务系统各处理环节的实时数据,分析业务系统各事务处理的交易路径和处理 ...

随机推荐

  1. RA-28000 账号被锁定的解决办法

    ORA-28000 账号被锁定的解决办法 错误场景:当使用sqlplus进行登录时报错:ORA-28000 账号被锁定.错误原因:由于oracle 11g 在默认在default概要文件中设置了密码最 ...

  2. 用 @Value("${xxxx}")注解从配置文件读取值的用法

    1.  用法: 从配置properties文件中读取init.password 的值. @Value("${init.password}") private String init ...

  3. GIS应用|快速开发REST数据服务

    随着计算机的快速发展,GIS已经在各大领域得到应用,和我们的生活息息相关, 但是基于GIS几大厂商搭建服务,都会有一定的门槛,尤其是需要server,成本高,难度大,这里介绍一种在线GIS云平台,帮你 ...

  4. 盘点一下Redis中常用的Java客户端,或者咱们手写一个?

    Java中那些Redis的客户端 前面我们的所有操作都是基于redis-cli来完成的,那么我们要在Java中操作Redis,怎么做呢?首先我们先来了解一下Redis Serialization Pr ...

  5. Linux常用命令,查看树形结构、删除目录(文件夹)、创建文件、删除文件或目录、复制文件或目录(文件夹)、移动、查看文件内容、权限操作

    5.查看树结构(tree) 通常情况下系统未安装该命令,需要yum install -y tree安装 直接使⽤tree显示深度太多,⼀般会使⽤ -L选项⼿⼯设定⽬录深度 格式:tree -L n [ ...

  6. 时间轮机制在Redisson分布式锁中的实际应用以及时间轮源码分析

    本篇文章主要基于Redisson中实现的分布式锁机制继续进行展开,分析Redisson中的时间轮机制. 在前面分析的Redisson的分布式锁实现中,有一个Watch Dog机制来对锁键进行续约,代码 ...

  7. [对对子队]会议记录5.21(Scrum Meeting8)

    今天已完成的工作 吴昭邦 ​ 工作内容:调整快进按钮 ​ 相关issue:优化流水线加入物品的动画 ​ 相关签入:feat: 快进图标更换,更改第四关材料位置 朱俊豪 ​ 工作内容:调整场景高度和视角 ...

  8. (数据科学学习手札129)geopandas 0.10版本重要新特性一览

    本文示例代码及文件已上传至我的Github仓库https://github.com/CNFeffery/DataScienceStudyNotes 1 简介 就在前不久,我们非常熟悉的Python地理 ...

  9. Asp.Net mvc4 +Spring

    添加相应的引用对象.(以下全部) 修改mvc的Global.asax文件内容 需要将控制器中原来需要new出来的对象改成属性成员 添加这个属性的注入对象 再去修改spring对web.config的一 ...

  10. python re:正向肯定预查(?=)和反向肯定预查(?<=)

    参考资料:https://tool.oschina.net/uploads/apidocs/jquery/regexp.html (?=pattern) 正向肯定预查,在任何匹配pattern的字符串 ...