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

有的时候还会触发别的题目,这种验证方式固然好;但不符合国情。
国内用的最多的还是图形验证码。
解决方案
登录验证码是防机器人登录的一种方式,这是一种系统安全手段;因此你的验证码值是不能告诉前端的,那怎么才能判断用户输入的验证码对不对呢?
我的解决方法是Session
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 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 => { options.CheckConsentNeeded = context => false; options.MinimumSameSitePolicy = SameSiteMode.None; }); } public void Configure(IApplicationBuilder app) { app.UseSession(); app.UseCookiePolicy(); }
|
图形验证码的生成我使用了一个开源的库:SixLaborsCaptcha
1 2 3 4 5 6 7 8 9 10
| using SixLaborsCaptcha.Mvc.Core; ... public void ConfigureServices(IServiceCollection services) { services.AddSixLabCaptcha(x => { x.DrawLines = 4; }); } ...
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
[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"); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| [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
可以实现无感登录。
参考资料