前言

上一回研究 Authenticate 和 Authorization 已经是 2 年前了.

业务需求一直没有增长, 所以也没有再去提升它了. 但最近业务开始上去了. 荒废的功夫又得拾起来了.

上一回我没有耐心写完 IdentityServer4 和 Angular. 这回希望能写完它. 不打算修改之前的了. 从头开始吧.

这 2 年微软的文档做的越来越好了. 这里就按着文档做一遍 warm up + summary 就好呗.

这个系列会涉及到的技术是:

Razor Pages,

Web API,

Identity,

OpenIDDict Core (thrid party for OIDC + OAuth, 我没有使用 IdentityServer4 了),

Angular

介绍

Authentication (authen) 主要讲的就是登入, 身份验证,

Authorization (autho) 讲的是权限, 登入不表示拥有所有权限. 可以算是 2nd level protection.

用 ASP.NET Core 做 authen 一般上都会用 Identity Framework 插件 (build-in 的), 然后在插件上做一些魔改. 比较少从 0 开始写,

但其实 ASP.NET Core 底层是有一些基础功能的, 它可以让我们从 0 开始写. 这篇主要就是讲这一块.

主要参考

参考: Use cookie authentication without ASP.NET Core Identity

Protect Page

新建一个项目

dotnet new webapp -o WithoutIdentity

在 privacy page 加上 [Authorize] 标签.

Privacy.cshtml.cs

[Authorize]
public class PrivacyModel : PageModel
{
public void OnGet()
{
}
}

这个时候, 访问这个页面的话就会报错了, 因为我们还没有做任何 authen 的配置.

Setup Config

现在去 Program.cs, 添加配置

builder.Services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);

Schemes 是方案的意思, 比如 Cookies Schemes, JWT Schemes 等等

CookieAuthenticationDefaults.AuthenticationScheme 是一个 string, 就是方案的名字. AddAuthentication 声明默认使用什么方案.

AddCookie 则是 ASP.NET Core 封装好的一个 Cookie 方案. 这也是最常见的方案, 参数是方案的名字.

另外, 还有一个常见的方案是 JWT (JSON Web Token), 采用前后端分离架构, 通常就会使用这个方案.

JWT 需要安装

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

配置

builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme);

services 配置好后就到 middleware.

添加一个 app.UseAuthentication()

注: 位置要对哦.

这时再访问 private page 出现的 error 就不一样了. 它会 redirect 到 Account/Login page.

这个是 default login page 的路径.

参考: CookieAuthenticationDefaults.cs

我们可以通过 options 来修改掉 login path, 还有其它 Cookie 相关的配置.

.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => {
options.Cookie.Name = "MyCookieName";
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
options.ReturnUrlParameter = "returnUrl";
options.LoginPath = "/login";
options.LogoutPath = "/logout";
options.AccessDeniedPath = "/access-denied";
});

注: SameSiteMode 默认是 Lax, Lax 级别已经足够我们做 OAuth 了. 所以不需要 set 成 Strict (我只是演示而已)

相关参考:

CSRF

Cookie Policy Middleware

Work with SameSite cookies in ASP.NET Core

Login Page

接下来, 我们做一个 login page.

当 user 被 redirect 到 login page 时, URL query 会附上一个 returnUrl, 它记入 user 是从哪一个路径被 redirect 过来的. 当登入成功, 我们可以让 user redirect 回去.

在这个 login page 做一个 login button post

public async Task OnPostAsync([FromQuery] string returnUrl)
{
var claimsIdentity = new ClaimsIdentity(new List<Claim> {
new Claim(ClaimTypes.Name, "UserName")
}, CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
new ClaimsPrincipal(claimsIdentity),
new AuthenticationProperties()
);
}

HttpContext.SignInAsync 是 ASP.NET Core 封装好的方法.

参数 1 是方案的名字,

参数 2 是 Principal, 里面的 claims 是用来做 authorization 的 (最少都要有一个 Name 的 claims, 而已必须是 unique)

参数 3 是 authentication properties 比如 remenber me 之类的配置.

我们不需要做额外写逻辑去 redirect, SignInAsync 会帮我们 redirect to returnUrl

完成后就成功进入 privacy page 了, 上图是 login 后 response cookie, 它的 value 是用 data protection 加密过的哦.

Logout Page

接着做一个 logout page

public async Task OnPostAsync()
{
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
}

然后在任何想 logout 的 page 里面写一个 form, post 去 logout page, 并且附上 returnUrl

<form asp-page="/Account/Logout/Index" asp-route-returnUrl="/" method="post">
<button type="submit">Click here to Logout</button>
</form>

这里有一篇对 logout path 的解释: What does the CookieAuthenticationOptions.LogoutPath property do in ASP.NET Core 2.1?

我本来以为 logout path 的意思是你 logout 完成后它会跳转到这个 page

其实不是这样的. 它有点像 login page 是用来做 logout 的. 当调用 SignOutAsync 时, 当前的 path 必须是 options.LogoutPath (我们在 AddCookie 时写进去的) 那么才会触发 redirect

参考一下 Identity Framework 的实现, 这个是 logout button 可以放在任何地方. 它会 post to /Account/Logout page 附上了 returnUrl.

然后 /Account/Logout.cs 是这样

估计 SignInManger.SignOutAsync 里头就调用了 HttpContext.SignOutAsync.

React to back-end changes

这个概念蛮重要的, 之前我也是没有搞明白.

首先要知道, cookie 保存了 user info, 通过 data protection 对称加密来保护.

在验证的时候, 直接解密成功就登入了. 这个流程是不走数据库的.

好处是快, 坏处就是无法即刻注销这个 cookie. 比如说用户换了密码, 他的预想是之前的 cookie 应该就失效了. 但是没有.

那怎么办呢? 微软把这个权衡留给我们决定。

Identity Framework 对此的方案叫 Security Stamp, 它的做法是. 设定一个检测时期, 比如默认是超过 30 分钟就得去数据库检查一次.

检查什么呢? 就是 security stamp 是否和之前一样. 如果一样就表示这段期间用户并没有做出任何会影响到授权的事. 如果有那么就应该要 update 这个 security stamp. 那么期限一到, 检查的时候就会发现.

Identity Framework 检查的时候会跑 5 个 select from table, 性能还是蛮伤的呢.

可以通过 config 去 set 检测的间隔时间

services.Configure<SecurityStampValidatorOptions>(o => {
o.ValidationInterval = TimeSpan.FromSeconds(60);
});

说太多 Identity Framework 了, 讲回 without Identity Framework 的情况下, 它是怎样 work 的

首先是做 principal 的时候放多一个 security stamp claim

然后在 AddCookie 的时候放入一个 Events 拦截

继承 CookieAuthenticationEvents 然后 override 掉 ValidatePrincipal, 里头就可以检查然后 reject 之类的

或者像 identity 这样配置也是可以的

具体 validation 是这样的

判断时间 > 数据库检查 > 不通过就 signout > 通过就 replace principal, replace principal 不是 identity 的功能, 而是 ASP.NET Core 的功能.

Cookie Expires

refer: How to set asp.net Identity cookies expires time

有几个东西跟 expires 息息相关

SlidingExpiration = 这个是说 keep renew cookie, 每次 request 来的时候如果 cookie 是 ok 的 then 就续命.

Persistent = 如果是 false 的话, 那么就是没有 remember me, cookie expires 是 session, 如果是 true 那么 expires 就 base on cookie option 的 ExpireTimeSpan (默认 14 天)

在 SignIn 的时候还可以调绝对过期 ExpiresUtc 它会覆盖掉 option 的 ExpireTimeSpan, 也不会理会 SlidingExpiration 了 (set 绝对的时候要 Persistent true 哦, 因为 false 就是跑 session)

Cookie expires session 就是说当 browser close 它就被清掉. 需要留意的是 chrome, 一但你 set 了 continue where you left off 它就不会清除掉 expires session cookie 了. 即使是你 off pc 也不会...

Identity – Without Identity Framework的更多相关文章

  1. 第63章 ASP.NET Identity 支持 - Identity Server 4 中文文档(v1.0.0)

    提供了基于ASP.NET身份的实现,用于管理IdentityServer用户的身份数据库.此实现是IdentityServer中的扩展点,以便为用户加载身份数据以将声明发送到令牌. 这个支持的仓储位于 ...

  2. Asp.Net.Identity认证不依赖Entity Framework实现方式

    Asp.Net.Identity为何物请自行搜索,也可转向此文章http://www.cnblogs.com/shanyou/p/3918178.html 本来微软已经帮我们将授权.认证以及数据库存储 ...

  3. VS2013中web项目中自动生成的ASP.NET Identity代码思考

    vs2013没有再分webform.mvc.api项目,使用vs2013创建一个web项目模板选MVC,身份验证选个人用户账户.项目会生成ASP.NET Identity的一些代码.这些代码主要在Ac ...

  4. ASP.NET Identity 简介

    翻译自:http://www.asp.net/identity/overview/getting-started/introduction-to-aspnet-identity ,略有改动. 背景:A ...

  5. ASP.NET Identity系列教程(目录)

    $(document).ready(function(){ $("#hide").click(function(){ $(".en").hide(); }); ...

  6. ASP.NET MVC 随想录——开始使用ASP.NET Identity,初级篇

    在之前的文章中,我为大家介绍了OWIN和Katana,有了对它们的基本了解后,才能更好的去学习ASP.NET Identity,因为它已经对OWIN 有了良好的集成. 在这篇文章中,我主要关注ASP. ...

  7. 【ASP.NET Identity系列教程(一)】ASP.NET Identity入门

    注:本文是[ASP.NET Identity系列教程]的第一篇.本系列教程详细.完整.深入地介绍了微软的ASP.NET Identity技术,描述了如何运用ASP.NET Identity实现应用程序 ...

  8. 自定义表并实现Identity登录(一)

    注意,Microsoft.AspNet.Identity.Core.1.0.0和Microsoft.AspNet.Identity.Core.2.2.1差别太大,需考虑实际项目中用的是哪种,本文是基于 ...

  9. ASP.NET MVC 随想录——开始使用ASP.NET Identity,初级篇(转)

    ASP.NET MVC 随想录——开始使用ASP.NET Identity,初级篇   阅读目录 ASP.NET Identity 前世今生 建立 ASP.NET Identity 使用ASP.NET ...

  10. 转载 ASP.NET MVC中使用ASP.NET Identity

    转载原地址: http://blog.jobbole.com/90695/ 在之前的文章中,我为大家介绍了OWIN和Katana,有了对它们的基本了解后,才能更好的去学习ASP.NET Identit ...

随机推荐

  1. 【解决方案】智能UI自动化测试

    你的UI自动化追得上业务的变更和UI更迭吗?当今瞬息万变的时代,成千上万的App围绕着现代人生活的点点滴滴.为了满足用户的好的体验和时刻的新鲜感,这些App需要时刻保持变化,也给 UI自动化落地实施带 ...

  2. Python threading实现多线程 基础篇

    讲多线程前,先要了解什么是进程,什么是线程,已经知道的请略过. 一.进程与线程: 进程是资源分配的最小单位,一个程序至少有一个进程. 线程是程序执行的最小单位,一个进程至少有一个线程. 进程都有自己独 ...

  3. .NET TCP、UDP、Socket、WebSocket

    做.NET应用开发肯定会用到网络通信,而进程间通信是客户端开发使用频率较高的场景. 进程间通信方式主要有命名管道.消息队列.共享内存.Socket通信,个人使用最多的是Sokcet相关. 而Socke ...

  4. 图灵课堂netty 仿微信开发

    通信的图文示例 以下是需要实现的前端界面, 准备工作:开始实现前需要技术关健字解释 第一步,这儿直接建一个maven 项目 就好,只要是可能用maven 管理包的环境就行,课程使用的版本是 java ...

  5. SpringSecurity:hasAuthority与自定义权限校验

    springsecurity中有两种权限控制方法 1.基于注解 @PreAuthorize("hasAuthority('syst:add')") 他的作用是在controller ...

  6. python私有变量和方法

    python私有变量和方法 1,私有变量和私有方法无法被继承 2,私有变量和私有方法可以放在普通方法(实例方法)里面被子类继承 class A: def __init__(self, name): s ...

  7. 【SpringMVC】08 Post请求乱码

    这是以Post请求方式接受来的中文字符乱码 就像之前JavaWeb阶段的情况一样 所以,按照JavaWeb的方法,我们应该只需要在获取之前设置好编码处理就行了 可以先从这个处理方法开始试试,但是参数在 ...

  8. 【Linux】Re03

    一.软连接 语法用法 ln -s 源文件或者目录位置 链接名称 [root@localhost ~]# mkdir -p aa/bb/cc/dd [root@localhost ~]# ln -s a ...

  9. jmeter 使用非 GUI 模式运行测试脚本

    使用非 GUI 模式运行测试脚本时可以使用的一些命令:-h 帮助 -> 打印出有用的信息并退出-n 非 GUI 模式 -> 在非 GUI 模式下运行 JMeter-t 测试文件 -> ...

  10. 2018年视频,路径规划:层次化路径规划系统——hierarchical pathfinding system —— Hierarchical Dynamic Pathfinding for Large Voxel Worlds (续)

    前文: 2018年视频,路径规划:层次化路径规划系统--hierarchical pathfinding system -- Hierarchical Dynamic Pathfinding for ...