【ASP.NET Core】使用最熟悉的Session验证方案
如果大伙伴们以前写过 ASP 或 PHP 之类的,相信各位对基于 Session 的身份验证很熟悉(其实在浏览器端是结合 Cookie 来处理的)。这种验证方式是比较早期的,操作起来也不复杂。
a、用户打开(或自动跳转到)登入页,输入你的大名和密码,登录。
b、提交到服务器,比较一下用户名和密码是否正确。
c、若验证成功,往 Session 里写入一个标识。实际上往Session里面写啥都行,能作为用户登录标识就行。毕竟嘛,对于每个连接来说,Session是唯一的,所以,在页面“头部”验证时,许多时候压根不用关心Session里存了啥,只要有登录标识就OK。
当然,你会说,我 K,ao,这样验证是不是问题多多?确实,跨域验证就出问题,而且单点登录也不好控制。所以现在才会衍生出许多验证方式。甚至弄得很复杂,于是咱们就知道只要涉及到验证和授权的内容就看得人头晕。很真实,是TM挺复杂的。
不过,你同时也会发现,现在很多 Web 应用还是会使用 Session 来验证的。为啥呢?因为我的项目很小,小到可能就只有五六个人登录,我用得着搞那么复杂吗?
老周不才,没做过什么大项目,小项目倒是坑了不少人。用小项目来忽悠客户一向是老周的核心竞争力,一直被模仿却从未被超越过。你不妨想想,你开了个小店,平时只卖几张不知道正不正版的有颜色的DVD,店里的员工可能就几个,做个管理系统就那么几个操作员。你说这身份验证你会选那些复杂到跳楼的方案吗。
------------------------------- 银河分界线 ------------------------------------
以前,我们在ASP中使用 Session 还是很简单的。ASP 文件中有一种类似C头文件的东西(inc文件),可以在其他ASP文件中包含。那么,这个 inc 文件里写几行代码——检查一下 Session 里是否包含登录标识。若没有,跳转到登录页。然后,需要作验证的页面就 include 这个 inc 文件。这样就以很简单但很混乱的方式实现了验证功能。
在 ASP.NET Core 里其实你也可以这样用,在服务容器中启用 Session 功能,然后写个中间件,插入到 HTTP 管道的头部,检查 Session 中的登录标识,如果没有那就 Redirect 到登录 URL。
这样做确实可行的,但又出新问题了——所有进来的请求都会进行验证了,这会导致客户端访问啥都要验证了。当然,你会想到,Map When 就行了呗,让中间件有了条件限制。
------------------------------ M77星云分界线 ----------------------------------
以上做法并不符合 ASP.NET Core 设计模型。ASP.NET Core 中为验证和授权提供了独立的功能实现的。好了,前文扯了几吨的废话,正片现在开始。
验证与授权是两个不同的过程,但它们又经常一起使用。所以很多大伙伴经常分不清,关键是这两货的单词也长得很像,不信你看:
1、验证——authentication
2、授权——authorization
怎么样?像吧,也不知道那些洋鬼子们怎么想的,把它俩弄得那么像。
老周试着用一个故事来区别这两个过程——假如你去你朋友家里玩。首先,你朋友家里得有人,而且你按门铃后他会开门让你进去(验证);之后,你进去了,但是朋友家里有很多个房间,一般大客厅你肯定可以站在那里的,但是,朋友的卧室就不见得会允许你进去(授权),除非你们特别熟。
验证是你能不能进别人家的门,授权是进了门后你被允许做什么。
------------------------- 小龙虾星人分界线 ------------------------
下面分别说说这两个过程的一些要素。
A、验证
现在的网站咱们都知道,身份验证方式很多。你可以用户名/密码登录,你可以用QQ、微博、微信等帐号登录,你可以用短信验证码登录。像QQ、微信这些是第三方授权的,为了省去每去访问都要授权的麻烦,提供验证的服务器会发给你一个 Token,下次访问你用这个 Token 就行了。当然,这个 Token 也是有时间限制的,过期了就不能用。
这种方法不会暴露用户信息,但也不是真的很安全的,别人可以不知道你是谁,他只要盗走你的 Token 也能用来登录。好比一些平台会开放给开发者 API,比如微博开放平台,会分配给你一个 App Key 和一个密钥,然后你调用 API 时要传递这些东西。如果我知道你的 App Key 和密钥,那我照样可以以你的身份去调用 API。
正因为验证的方式那么多,所以,应用程序必须要有个东东来标识它们,这就跟我们在学校有学号一样道理。于是就出了个名词叫 Authentication Scheme。验证架构,但翻译为验证方案更好听。说白了,就是你给你这种验证方式取个名字罢了。比如,邮件验证码登录的叫“Email-Auth”。像咱们常听说的什么 OAuth 2.0,也是一种验证方案。
光有了验证方案名称可不行,你得让程序知道咋去验证,这就需要为每个方案配套一个 Handler 了,这个 Handler 是一个类,但它要求你实现 IAuthenticationHandler 接口。这样便有了统一的调用标准,当你选择某方案完成验证时,就会调用与这个方案对应的 Handler 来处理。例如:
方案 | Handler | 说明 |
Email-Auth | EmailAuthenHandler | 邮件验证 |
Pwd-Auth | UserPasswordHandler | 用户名/密码验证 |
大概微软也知道在 .NET 库中集成太多验证方案太笨重,所以现在新版本的 ASP.NET Core 的默认库中只保留一些基本的验证方案——如 Cookie,这个方案是内置的,我们不需要自己写代码(在 Microsoft.AspNetCore.Authentication.Cookies 命名空间中)。
在 Microsoft.AspNetCore.Authentication 命名空间下有个抽象类 AuthenticationHandler<TOptions>,它实现了一点基本功能,我们如果想自己写验证方案,可以从这个类派生。但,老周这次要用的方案只是对 Session 的简单检查,所以,就不需要从这个抽象类派生,而是直接实现 IAuthenticationHandler 接口。
在实现验证逻辑前,咱们写个类,作为一些可设置参数的选项。
public class TestAuthenticationOptions
{
/// <summary>
/// 登录入口路径
/// </summary>
public string LoginPath { get; set; } = "/Home/Login"; /// <summary>
/// 存入Session的键名
/// </summary>
public string SessionKeyName { get; set; } = "uid"; /// <summary>
/// 返回URL参数名
/// </summary>
public string ReturnUrlKey { set; get; } = "return";
}
这里老周只按照项目需求设定了三个选项,想添加选项的话得看你的实际需求了。
LoginPath:登录入口,这个属性指定一个URL(一般是相对URL),表示用户输入名称和密码登录的页面(可以是MVC,可以是 RazorPages,这个无所谓,由URL路由和你的代码决定)。
SessionKeyName:这个属性设置 Session 里面存放登录标识时的 Key 名。其实 Session 和字典对象类似,里面每个项都有唯一的 Key。
ReturnUrlKey:指定一个字段名,这个字段名一般附加在URL的参数中,表示要跳转回去的路径。比如,设置为“return”,那么,假如我们要访问 https://localhost/admin/op,但这个路径(或页面)必须要验证,否则不能访问(其实包含授权过程),于是会自动跳转到 https://localhost/Home/login,让用户登录。但用户登录成功后要返回 /admin/op,所以,在 Login 后加个参数:
https://localhost/Home/Login?return=/admin/op
当登录并验证成功后,根据这个 return 查询字段跳转回去。如果你把 ReturnUrlKey 属性设置为“back”,那么登录的URL就是:
https://localhost/Home/Login?back=/admin/op
在实现 IAuthenticationHandler 接口时,可以同时实现 IAuthenticationSignInHandler 接口。而 IAuthenticationSignInHandler 接口是包含 IAuthenticationHandler 和 IAuthenticationSignOutHandler 接口的。这就等于,你只实现 IAuthenticationSignInHandler 接口就行,它包含三个接口的方法成员。
InitializeAsync 方法:初始化时用,一般可以从这里获取当前请求关联的 HttpContext ,以及正在被使用的验证方案信息。
AuthenticateAsync 方法:验证过程,此处老周的做法仅仅看看 Session 中有没有需要的Key就行了。
ChallengeAsync 方法:一旦验证失败,就会调用这个方法,向客户端索要验证信息。这里需要的验证信息是输入用户名和密码。所以,老周在些方法中 Redirect 到登录页面。
ForbidAsync 方法:禁止访问时用,可以直接调用 HttpContext 的 ForbidAsync 方法。
SignInAsync 方法:登入时调用,这里老周只是把用户名放入 Session 就完事了。
SignOutAsync 方法:注销时调用,这里只是把 Session 中的用户名删除即可。
这些方法都可以由 ASP.NET Core 内部自动调用,也可以通过 HttpContext 的扩展方法手动触发,如SignInAsync、AuthenticateAsync、ChallengeAsync等。
public class TestAuthenticationHandler : IAuthenticationSignInHandler
{
/// <summary>
/// 验证方案的名称,可以自行按需取名
/// </summary>
public const string TEST_SCHEM_NAME = "some_authen"; /// <summary>
/// 依赖注入获取的选项
/// </summary>
public TestAuthenticationOptions Options { get; private set; } public TestAuthenticationHandler(IOptions<TestAuthenticationOptions> opt)
{
Options = opt.Value;
} public HttpContext HttpContext { get; private set; }
public AuthenticationScheme Scheme { get; private set; } public Task<AuthenticateResult> AuthenticateAsync()
{
// 先要看看验证方案是否与当前方案匹配
if(Scheme.Name != TEST_SCHEM_NAME)
{
return Task.FromResult(AuthenticateResult.Fail("验证方案不匹配"));
}
// 再看Session
if(!HttpContext.Session.Keys.Contains(Options.SessionKeyName))
{
return Task.FromResult(AuthenticateResult.Fail("会话无效"));
}
// 验证通过
string un = HttpContext.Session.GetString(Options.SessionKeyName)??string.Empty;
ClaimsIdentity id = new(TEST_SCHEM_NAME);
id.AddClaim(new(ClaimTypes.Name, un));
ClaimsPrincipal prcp = new(id);
AuthenticationTicket ticket = new(prcp, TEST_SCHEM_NAME);
return Task.FromResult(AuthenticateResult.Success(ticket));
} public Task ChallengeAsync(AuthenticationProperties? properties)
{
// 跳转到登录入口
HttpContext.Response.Redirect($"{Options.LoginPath}?{Options.ReturnUrlKey}={HttpContext.Request.Path}");
return Task.CompletedTask;
} public async Task ForbidAsync(AuthenticationProperties? properties)
{
await HttpContext.ForbidAsync(Scheme.Name);
} public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
// 获取一些必备对象的引用
HttpContext = context;
Scheme = scheme;
return Task.CompletedTask;
} public Task SignInAsync(ClaimsPrincipal user, AuthenticationProperties? properties)
{
// 获取用户名
string uname = user.Identity?.Name ?? string.Empty;
if(!string.IsNullOrEmpty(uname))
{
HttpContext.Session.SetString(Options.SessionKeyName, uname);
}
return Task.CompletedTask;
} public Task SignOutAsync(AuthenticationProperties? properties)
{
if(HttpContext.Session.Keys.Contains(Options.SessionKeyName))
{
HttpContext.Session.Remove(Options.SessionKeyName);
}
return Task.CompletedTask;
}
}
在 AuthenticateAsync 方法中,先要检查一下,当前所使用用的验证方案是否与 TEST_SCHEM_NAME 所表示的方案名称相同。这是为了防止把 TestAuthenticationHandler 与错误的验证方案进行注册绑定。例如我这个是实现用Session来验证的,要是把它与“Email-Auth”方案绑定,就会出现逻辑错误,毕竟此类不是用电子邮件来验证的。
不管是实现验证方法AuthenticateAsync 还是登录方法SignInAsync,都不要去检查用户名和密码,而应该把用户名和密码验证放到登录的页面或 Controller 中处理。因为这个自定义的 TestAuthenticationHandler 在许多需要验证的请求中都要调用,如果你在这里去检查用户名和密码,岂不是每次都要跳转到登录页让用户去输入?
B、授权
一旦验证完成,就到了授权过程。
验证过程通过验证方案名称来标识,同样,授权过程也可包含多个策略。
比如,可以基于用户的角色进行授权,管理员的权限多一些,非管理员的少一些;
可以基于用户的年龄进行授权,哪些游戏 15 岁以下的不能玩;
或者,基于用户的信用分来授权,信用差的不能贷款;信用好的允许你贷款
……
授权过程处理是通过收集一系列的声明(Claim)来评估一下用户具有哪些权限。比如
你是管理员吗?
你几岁了?
你过去三年的信用值是多少?
你是不是VIP用户?
你的购物积分多少?
你过去一年在我店买过几次东西?
……
这些声明来源很多,可以在过去用户购买东西时存入数据库并汇总出来,也可能用户在登录验证时从数据库中查询到。处理代码要根据这些声明来综合评定一下,你是否达到授权的【要求】。
这些【要求】就可以用 IAuthorizationRequirement 接口来表示。好玩的是,这个接口没有规定任何方法成员,你只需要有个类来实现这个接口就行。比如用户积分,写个类叫 UserPoints,实现这个接口,再加个属性叫 PointValue,表示积分数。
然后,你把这个 UserPoints 类添加到某授权策略的 Requirements 集合中,在处理授权评估时,再通过代码检查一下里面的各种实现了 IAuthorizationRequirement 接口的对象,看看符不符合条件。
而自定义的授权策略处理是实现 IAuthorizationHandler 接口。你看看,是不是原理差不多,刚才验证的时候会实现自定义的 Handler,现在授权时又可以实现 Handler。
在 Session 验证这个方案中,我们不需要写自定义的授权 Handler,只需要调用现有API开启授权功能,并注册一个有效的策略名称即可。而 IAuthorizationRequirement 我们也不用实现,直接用扩展方法 RequireAuthenticatedUser 就行。意思是说只要有已登录的用户名就行,毕竟咱们前面在验证时,已经提供了一个有效的用户登录名,还记得 AuthenticateAsync 方法中的这几行吗?
// 验证通过
string un = HttpContext.Session.GetString(Options.SessionKeyName)??string.Empty;
ClaimsIdentity id = new(TEST_SCHEM_NAME);
id.AddClaim(new(ClaimTypes.Name, un));
ClaimsPrincipal prcp = new(id);
AuthenticationTicket ticket = new(prcp, TEST_SCHEM_NAME);
return Task.FromResult(AuthenticateResult.Success(ticket));
其实我们已经添加了一个声明——Name,以用户名为标识,在授权策略中,程序要查找的就是这个声明。只要找到,就能授权;否则拒绝访问。
----------------------------------- 第三宇宙分界线 -----------------------------------
在 Program.cs 文件中,我们要注册这些服务类。
var builder = WebApplication.CreateBuilder(args);
// 启用Session功能
builder.Services.AddSession(o =>
{
// 把时间缩短一些,好测试
o.IdleTimeout = TimeSpan.FromSeconds(5);
});
// 这个用来检查用户名和密码是否正确
builder.Services.AddSingleton<UserChecker>();
// 使用MVC功能
builder.Services.AddControllersWithViews();
// 注册刚刚定义的选项类,可以依赖注入
// 不要忘了,不然出大事
builder.Services.AddOptions<TestAuthenticationOptions>();
// 添加验证功能
builder.Services.AddAuthentication(opt =>
{
// 添加我们自定义的验证方案名
opt.AddScheme<TestAuthenticationHandler>(TestAuthenticationHandler.TEST_SCHEM_NAME, null);
});
// 添加授权功能
builder.Services.AddAuthorization(opt =>
{
// 注册授权策略,名为“demo2”
opt.AddPolicy("demo2", c =>
{
// 与我们前面定义的验证方案绑定
// 授权过程跟随该验证后发生
c.AddAuthenticationSchemes(TestAuthenticationHandler.TEST_SCHEM_NAME);
// 要求存在已登录用户的标识
c.RequireAuthenticatedUser();
});
});
var app = builder.Build();
把Session中的过期进间设为5秒,是为了好测试。
上面代码还注册了一个单实例模式的 UserChecker,这只是个测试,老周不使用数据库了,就用一个写“死”了的类来检查用户名和密码是否正确。
public class UserChecker
{
private class UserInfo
{
public string Name { get; init; }
public string Password { get; init; }
} // 简单粗暴的用户信息,只为测试而生
static readonly IEnumerable<UserInfo> _Users = new UserInfo[]
{
new(){Name = "lucy", Password="123456"},
new(){Name= "tom", Password="abcd"},
new() {Name="jim", Password="xyz321"}
}; /// <summary>
/// 验证用户名和密码是否有效
/// </summary>
/// <param name="name">用户名</param>
/// <param name="pwd">用户密码</param>
/// <returns></returns>
public bool CheckLogin(string name, string pwd) => _Users.Any(u => u.Name == name.ToLower() && u.Password == pwd);
}
在 App 对象 build 了之后,记得插入这些中间件到HTTP管道。
app.UseSession();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllerRoute("main", "{controller=Home}/{action=Index}");
注意顺序,授权在验证之后,验证和授权要在 Map MVC的处理之前。
测试项目中我用到了两个 Controller。第一个是 Home,可以随便访问,故不需要考虑验证和授权的问题;第二个是 Admin,只有已正确登录的用户才可以访问。
Admin 控制器很简单,只返回对应的视图。
[Authorize("demo2")]
public class AdminController : Controller
{
public IActionResult MainLoad()
{
return View();
}
}
注意在此控制器上应用了 Authorize 特性,并且指定了使用的授权策略是“demo2”。表明这个控制器里面的所有 Action 都不能匿名访问,要访问得先登录。
MainLoad 视图如下:
<h2>
这是管理后台
</h2>
--------------------------- L78分界线 ----------------------------
Home 控制器允许匿名访问,其中包含了用户登录入口 Login。
public class HomeController : Controller
{
TestAuthenticationOptions _options; public HomeController(IOptions<TestAuthenticationOptions> o)
{
_options = o.Value;
} public IActionResult Index() => View(); public IActionResult Login()
{
// 获取返回的URL
if (!HttpContext.Request.Query.TryGetValue(_options.ReturnUrlKey, out var url))
{
url = string.Empty;
}
// 用模型来传递URL
return View((object)url.ToString());
} public async Task<IActionResult> PostLogin(
string name, //用户名
string pwd, //密码
string _url, //要跳回的URL
[FromServices]UserChecker usrchecker //用来验证用户名和密码
)
{
if(string.IsNullOrEmpty(name)
|| string.IsNullOrEmpty(pwd))
{
return View("Login", _url);
}
// 如果密码不正确
if (!usrchecker.CheckLogin(name, pwd))
return View("Login", _url);
// 准备登入用的材料
// 1、声明
Claim cname = new(ClaimTypes.Name, name);
// 2、标识
ClaimsIdentity id = new(TestAuthenticationHandler.TEST_SCHEM_NAME);
id.AddClaim(cname);
// 3、主体
ClaimsPrincipal principal = new(id);
// 登入
await HttpContext.SignInAsync(TestAuthenticationHandler.TEST_SCHEM_NAME, principal); if(!string.IsNullOrEmpty(_url))
{
// 重定向回到之前的URL
return Redirect(_url);
} return View("Login", _url);
}
}
Home 控制器中只用到两个视图,一个是Index,默认主页;另一个是 Login,用于显示登录UI。
Login 视图如下:
@inject Microsoft.Extensions.Options.IOptions<DemoApp5.TestAuthenticationOptions> _opt
@model string <form method="post" asp-controller="Home" asp-action="PostLogin">
<p>
用户名:
<input name="name" type="text"/>
</p>
<p>
密 码:
<input name="pwd" type="password"/>
</p>
<button type="submit">确 定</button>
<input type="hidden" name="_url" value="@Model" />
</form>
这个视图中绑定的 Model 类型为string,实际上就是 Challenge 方法重定向到此URL时传递的回调URL参数(/Home/Login?return=/Admin/XXX)。在Login方法中,通过View方法把这个URL传给视图中的 Model 属性。
之所以要使用模型绑定,是因为HTTP两次请求间是无状态的:
第一次,GET 方式访问 /Home/Login,并用 return 参数传递了回调URL;
第二次,输入完用户名和密码,POST 方式提交时调用的是 PostLogin 方法,这时候,Login?return=xxxxx 传递的URL已经丢失了,无法再获取。只能绑定到 Model 上,再从 Model 中取值绑定到 hidden 元素上。
<input type="hidden" name="_url" value="@Model" />
POST的时候就会连同这个 hidden 一起发回给服务器,这样在 PostLogin 方法中还能够获取到这个回调URL。
----------------------------------------------------------------------------------------------------
运行示例后,先是打开默认的 Index 视图。
点击“管理页入口”链接,进入 Admin/MainLoad,此时候因为没有登录,就会跳转到 /Home/Login 。输入一个正确的用户名和密码,登录。
成功后就跳回到管理后台。
5 秒钟后就会过期,要访问就得重新登录。当然这个主要为了测试方便。实际运用可以设置 15 -20 分钟。
保存 Session 标识的 Cookie 由运行库自动完成,通过浏览器的开发人员工具能够看到生成的 Cookie。
默认的 Cookie 使用了名称 AspNetCore.Session,如果你觉得这个名字不够高大上,可以自己改。在 AddSession 时设置。
builder.Services.AddSession(o =>
{
// 把时间缩短一些,好测试
o.IdleTimeout = TimeSpan.FromSeconds(5);
o.Cookie.Name = "dyn_ssi";
});
然后,生成的用来保存Session标识的 Cookie 就会变成:
【ASP.NET Core】使用最熟悉的Session验证方案的更多相关文章
- 一个由正则表达式引发的血案 vs2017使用rdlc实现批量打印 vs2017使用rdlc [asp.net core 源码分析] 01 - Session SignalR sql for xml path用法 MemCahe C# 操作Excel图形——绘制、读取、隐藏、删除图形 IOC,DIP,DI,IoC容器
1. 血案由来 近期我在为Lazada卖家中心做一个自助注册的项目,其中的shop name校验规则较为复杂,要求:1. 英文字母大小写2. 数字3. 越南文4. 一些特殊字符,如“&”,“- ...
- 创建ASP.NET Core MVC应用程序(6)-添加验证
创建ASP.NET Core MVC应用程序(6)-添加验证 DRY原则 DRY("Don't Repeat Yourself")是MVC的设计原则之一.ASP.NET MVC鼓励 ...
- 如何为ASP.NET Core设置客户端IP白名单验证
原文链接:Client IP safelist for ASP.NET Core 作者:Damien Bowden and Tom Dykstra 译者:Lamond Lu 本篇博文中展示了如何在AS ...
- Asp.net Core, 基于 claims 实现权限验证 - 引导篇
什么是Claims? 这个直接阅读其他大神些的文章吧,解释得更好. 相关文章阅读: http://www.cnblogs.com/JustRun1983/p/4708176.html http://w ...
- 一题多解,ASP.NET Core应用启动初始化的N种方案[下篇]
[接上篇]"天下大势,分久必合,合久必分",ASP.NET应用通过GenericWebHostService这个承载服务被整合到基于IHostBuilder/IHost的服务承载系 ...
- asp.net core系列 43 Web应用 Session分布式存储(in memory与Redis)
一.概述 HTTP 是无状态的协议. 默认情况下,HTTP 请求是不保留用户值或应用状态的独立消息. 本文介绍了几种保留请求间用户数据和应用状态的方法.下面以表格形式列出这些存储方式,本篇专讲Sess ...
- [asp.net core 源码分析] 01 - Session
1.Session文档介绍 毋庸置疑学习.Net core最好的方法之一就是学习微软.Net core的官方文档:https://docs.microsoft.com/zh-cn/aspnet/cor ...
- ASP.NET Core Razor页面禁用防伪令牌验证
在这篇短文中,我将向您介绍如何ASP.NET Core Razor页面中禁用防伪令牌验证. Razor页面是ASP.NET Core 2.0中增加的一个页面控制器框架,用于构建动态的.数据驱动的网站: ...
- ASP.NET Core MVC 2.1 顶级参数验证
本文讨论ASP.NET Core 2.1中与ASP.NET Core MVC / Web API控制器中的模型绑定相关的功能.虽说这是一个功能,但从我的角度来看,它更像是一个错误修复! 请注意,我使用 ...
随机推荐
- 【LeetCode】374. Guess Number Higher or Lower 解题报告(Java & Python)
作者: 负雪明烛 id: fuxuemingzhu 个人博客: http://fuxuemingzhu.cn/ 目录 题目描述 题目大意 解题方法 日期 题目地址:https://leetcode.c ...
- 【LeetCode】522. Longest Uncommon Subsequence II 解题报告(Python)
[LeetCode]522. Longest Uncommon Subsequence II 解题报告(Python) 标签(空格分隔): LeetCode 作者: 负雪明烛 id: fuxuemin ...
- 【LeetCode】673. Number of Longest Increasing Subsequence 解题报告(Python)
[LeetCode]673. Number of Longest Increasing Subsequence 解题报告(Python) 标签(空格分隔): LeetCode 题目地址:https:/ ...
- codeforce-424C. Magic Formulas(数学)
C. Magic Formulas time limit per test:2 seconds memory limit per test:256 megabytes input stan ...
- 【嵌入式AI】全志 XR806 say hello world
欢迎关注我的公众号 [极智视界],回复001获取Google编程规范 O_o >_< o_O O_o ~_~ o_O 大家好,我是极智视界,本文介绍了全志 ...
- AWS 15 年(1):从 Serverful 到 Serverless
2006年,AWS发布了其第一个Serverless存储服务S3和第一个Serverful计算服务EC2,这也是AWS正式发布的前两个服务,开启了云计算波澜壮阔的旅程.2014年,AWS发布了业界第一 ...
- Glossary Collection
目录 直接修饰用 间接强调用 (多为副词) 过渡用 特别的名词 动词 词组 各种介词 句子 摘要 引言 总结 正文 实验 直接修饰用 Word 含义 例句 近义词 nuanced adj. 微妙的:具 ...
- TensorFlow.NET机器学习入门【7】采用卷积神经网络(CNN)处理Fashion-MNIST
本文将介绍如何采用卷积神经网络(CNN)来处理Fashion-MNIST数据集. 程序流程如下: 1.准备样本数据 2.构建卷积神经网络模型 3.网络学习(训练) 4.消费.测试 除了网络模型的构建, ...
- 前端在线学习网站W3School
W3School在线学习网站 http://www.w3school.com.cn/ W3School是因特网上最大的WEB开发者资源,是完全免费的,是非营利性的, 一直在升级和更新,是W3C中国社区 ...
- Kafka基础教程(三):C#使用Kafka消息队列
接上篇Kafka的安装,我安装的Kafka集群地址:192.168.209.133:9092,192.168.209.134:9092,192.168.209.135:9092,所以这里直接使用这个集 ...