引言
之前项目紧的时候,非功能要求都是能省则省,现在项目完成了;验收时有了安全性要求,自然需要登录验证码。
国外的登录安全验证(captcha)一般用的reCAPTCHA ,也就是经常见到的打勾;

有的时候还会触发别的题目,这种验证方式固然好;但不符合国情。
国内用的最多的还是图形验证码。
解决方案
登录验证码是防机器人登录的一种方式,这是一种系统安全手段;因此你的验证码值是不能告诉前端的,那怎么才能判断用户输入的验证码对不对呢?
我的解决方法是Session
- 启用
Session的代码如下:
public void ConfigureServices(IServiceCollection services){ //...
services.AddDistributedMemoryCache();
services.AddSession(options => { options.IdleTimeout = TimeSpan.FromSeconds(60); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => false; options.MinimumSameSitePolicy = SameSiteMode.None; });}public void Configure(IApplicationBuilder app){ //... app.UseSession(); app.UseCookiePolicy();}图形验证码的生成我使用了一个开源的库:SixLaborsCaptcha
- 验证码的使用可查看官网示例,我的代码如下:
using SixLaborsCaptcha.Mvc.Core;... public void ConfigureServices(IServiceCollection services) { services.AddSixLabCaptcha(x => { x.DrawLines = 4; }); }.../// <summary>/// 获得验证码图片/// </summary>/// <param name="sixLaborsCaptcha"></param>/// <param name="now">时间戳,前端使用Date.now()生成</param>/// <returns></returns>[HttpGet][Route("[action]")]public async Task<FileResult> GetCaptchaImage([FromServices] ISixLaborsCaptchaModule sixLaborsCaptcha, long? now){ now ??= DateTime.Now.Ticks;
string key = Extensions.GetUniqueKey(6); HttpContext.Session.SetString("CaptchaTime", now.ToString()); HttpContext.Session.SetString("CaptchaKey", key); await HttpContext.Session.CommitAsync();
_log.LogInformation("current session is {session}", HttpContext.Session.Id); _log.LogInformation("key is {key}", key); var imgText = sixLaborsCaptcha.Generate(key); return File(imgText, "Image/Png");}- 验证码校验:
[HttpPost("[Action]")]public async Task<UserInfo> Login(LoginInfo loginInfo){ _log.LogInformation("current session is {session}", HttpContext.Session.Id);
await HttpContext.Session.LoadAsync(); _log.LogInformation("after load current session is {session}", HttpContext.Session.Id);
if (string.IsNullOrEmpty(loginInfo.Captcha)) { return BadRequest("请输入验证码!"); }
var timeStr = HttpContext.Session.GetString("CaptchaTime"); if (string.IsNullOrEmpty(timeStr) || !long.TryParse(timeStr, out long result)) { return BadRequest("登陆无效,请刷新后重试"); }
var time = new DateTime(result); if (time.AddMilliseconds(1) > DateTime.Now) { return BadRequest("验证码无效,请刷新后重试"); }
if (loginInfo.Captcha.ToLower() != HttpContext.Session.GetString("CaptchaKey")?.ToLower()) { return BadRequest("验证码错误"); }
var info = Login(loginInfo.Account, loginInfo.Password);
return info;}验证码校验不区分大小写,同时我设置了一分钟超时时间
常见问题
HttpContext.Session.Id数据不断变化(登陆无效,请刷新后重试)的解决方法
session依赖客户端的cookie,在请求图片的时候服务端会在返回头中添加Set-Cookie: .AspNetCore.Session=xxxx,在登录时依据此cookie取得session然后才能判断验证码的正误;因此应首先在Chrome的Network中查看登录请求是否携带.AspNetCore.Session的cookie;若无,可检查以下几个方向:
- 跨域问题
生成图片的API和登录的API最好属于一个域,若存在跨域问题;一般情况下不会发送cookie,需在前端发起请求时添加withCredentials头,具体可查阅使用的http库文档。
- 分布式缓存配置问题
session默认使用基于内存的缓存(AddDistributedMemoryCache),若项目本身使用了基于redis的缓存(AddStackExchangeRedisCache),则默认使用了redis缓存,在redis缓存配置有误时,session无法正常工作,因此不会向前端添加Set-Cookie: .AspNetCore.Session=xxxx,同时系统无任何异常,此时可尝试去掉验证码登录逻辑确认配置正常
-
自动登录方法
在启用验证码登录后每次测试都需要手输验证码,这让自动化测试变得麻烦;因此我添加了一种额外的基于时间的OTP验证码。使用Otp.NET实现,在验证验证码之前验证OTP code即可。
配合
Vaultwarden可以实现无感登录。