├── .dockerignore
├── .gitignore
├── Components
└── vue2
│ ├── .gitignore
│ ├── README.md
│ ├── babel.config.js
│ ├── jsconfig.json
│ ├── package-lock.json
│ ├── package.json
│ ├── public
│ ├── favicon.ico
│ └── index.html
│ ├── src
│ ├── App.vue
│ ├── assets
│ │ ├── 1.png
│ │ ├── 2.png
│ │ └── logo.png
│ ├── main.js
│ └── package
│ │ ├── index.js
│ │ └── lazy-slide-captcha
│ │ ├── index.js
│ │ └── index.vue
│ └── vue.config.js
├── Images
├── .keep
├── image.png
└── show.gif
├── LICENSE
├── Lazy.SlideCaptcha.Core
├── CaptchaBuilder.cs
├── CaptchaBuilderExtensions.cs
├── CaptchaData.cs
├── CaptchaOptions.cs
├── CaptchaServiceCollectionExtensions.cs
├── CaptchaValidateData.cs
├── DefaultCaptcha.cs
├── Exceptions
│ ├── SlideCaptchaException.cs
│ └── SlideCaptchaTimeoutException.cs
├── Generator
│ ├── CaptchaImageData.cs
│ ├── DefaultCaptchaImageGenerator.cs
│ └── ICaptchaImageGenerator.cs
├── ICaptcha.cs
├── Lazy.SlideCaptcha.Core.csproj
├── Resources
│ ├── DefaultResourceManager.cs
│ ├── Handler
│ │ ├── CachedResourceHandlerManager.cs
│ │ ├── EmbeddedResourceHandler.cs
│ │ ├── FileResourceHandler.cs
│ │ ├── IResourceHandler.cs
│ │ └── IResourceHandlerManager.cs
│ ├── IResourceManager.cs
│ ├── Provider
│ │ ├── EmbeddedResourceProvider.cs
│ │ ├── IResourceProvider.cs
│ │ └── OptionsResourceProvider.cs
│ ├── Resource.cs
│ └── TemplatePair.cs
├── ServiceCollectionExtensions.cs
├── Storage
│ ├── DefaultStorage.cs
│ └── IStorage.cs
├── ValidateResult.cs
├── Validator
│ ├── BaseValidator.cs
│ ├── BasicValidator.cs
│ ├── IValidator.cs
│ ├── SimpleValidator.cs
│ └── SlideTrack.cs
└── templates
│ ├── 1
│ ├── hole.png
│ └── slider.png
│ ├── 2
│ ├── hole.png
│ └── slider.png
│ ├── 3
│ ├── hole.png
│ └── slider.png
│ ├── 4
│ ├── hole.png
│ └── slider.png
│ └── 5
│ ├── hole.png
│ └── slider.png
├── Lazy.SlideCaptcha.Demo
├── Controllers
│ ├── CaptchaController.cs
│ └── HomeController.cs
├── Dockerfile
├── Extensions
│ ├── CustomResourceProvider.cs
│ ├── CustomValidator.cs
│ └── UrlResourceHandler.cs
├── Lazy.SlideCaptcha.Demo.csproj
├── Lazy.SlideCaptcha.Demo.csproj.user
├── Models
│ └── ErrorViewModel.cs
├── Program.cs
├── Properties
│ └── launchSettings.json
├── Views
│ ├── Home
│ │ └── Index.cshtml
│ ├── Shared
│ │ ├── Error.cshtml
│ │ ├── _Layout.cshtml
│ │ ├── _Layout.cshtml.css
│ │ └── _ValidationScriptsPartial.cshtml
│ ├── _ViewImports.cshtml
│ └── _ViewStart.cshtml
├── appsettings.Development.json
├── appsettings.json
└── wwwroot
│ ├── cropper
│ ├── FileSaver.min.js
│ ├── cropper.min.css
│ ├── cropper.min.js
│ ├── index.html
│ └── style.css
│ ├── css
│ └── site.css
│ ├── favicon.ico
│ ├── images
│ ├── background
│ │ ├── 1.jpg
│ │ ├── 10.jpg
│ │ ├── 2.jpg
│ │ ├── 3.jpg
│ │ ├── 4.jpg
│ │ ├── 5.jpg
│ │ ├── 6.jpg
│ │ ├── 7.jpg
│ │ ├── 8.jpg
│ │ └── 9.jpg
│ └── template
│ │ └── 1
│ │ ├── hole.png
│ │ └── slider.png
│ ├── js
│ └── site.js
│ └── lib
│ ├── bootstrap
│ ├── LICENSE
│ └── dist
│ │ ├── css
│ │ ├── bootstrap-grid.css
│ │ ├── bootstrap-grid.css.map
│ │ ├── bootstrap-grid.min.css
│ │ ├── bootstrap-grid.min.css.map
│ │ ├── bootstrap-grid.rtl.css
│ │ ├── bootstrap-grid.rtl.css.map
│ │ ├── bootstrap-grid.rtl.min.css
│ │ ├── bootstrap-grid.rtl.min.css.map
│ │ ├── bootstrap-reboot.css
│ │ ├── bootstrap-reboot.css.map
│ │ ├── bootstrap-reboot.min.css
│ │ ├── bootstrap-reboot.min.css.map
│ │ ├── bootstrap-reboot.rtl.css
│ │ ├── bootstrap-reboot.rtl.css.map
│ │ ├── bootstrap-reboot.rtl.min.css
│ │ ├── bootstrap-reboot.rtl.min.css.map
│ │ ├── bootstrap-utilities.css
│ │ ├── bootstrap-utilities.css.map
│ │ ├── bootstrap-utilities.min.css
│ │ ├── bootstrap-utilities.min.css.map
│ │ ├── bootstrap-utilities.rtl.css
│ │ ├── bootstrap-utilities.rtl.css.map
│ │ ├── bootstrap-utilities.rtl.min.css
│ │ ├── bootstrap-utilities.rtl.min.css.map
│ │ ├── bootstrap.css
│ │ ├── bootstrap.css.map
│ │ ├── bootstrap.min.css
│ │ ├── bootstrap.min.css.map
│ │ ├── bootstrap.rtl.css
│ │ ├── bootstrap.rtl.css.map
│ │ ├── bootstrap.rtl.min.css
│ │ └── bootstrap.rtl.min.css.map
│ │ └── js
│ │ ├── bootstrap.bundle.js
│ │ ├── bootstrap.bundle.js.map
│ │ ├── bootstrap.bundle.min.js
│ │ ├── bootstrap.bundle.min.js.map
│ │ ├── bootstrap.esm.js
│ │ ├── bootstrap.esm.js.map
│ │ ├── bootstrap.esm.min.js
│ │ ├── bootstrap.esm.min.js.map
│ │ ├── bootstrap.js
│ │ ├── bootstrap.js.map
│ │ ├── bootstrap.min.js
│ │ └── bootstrap.min.js.map
│ ├── jquery-validation-unobtrusive
│ ├── LICENSE.txt
│ ├── jquery.validate.unobtrusive.js
│ └── jquery.validate.unobtrusive.min.js
│ ├── jquery-validation
│ ├── LICENSE.md
│ └── dist
│ │ ├── additional-methods.js
│ │ ├── additional-methods.min.js
│ │ ├── jquery.validate.js
│ │ └── jquery.validate.min.js
│ ├── jquery
│ ├── LICENSE.txt
│ └── dist
│ │ ├── jquery.js
│ │ ├── jquery.min.js
│ │ └── jquery.min.map
│ ├── lazy-slide-captcha
│ └── dist
│ │ ├── demo.html
│ │ ├── lazy-slide-captcha.common.js
│ │ ├── lazy-slide-captcha.common.js.map
│ │ ├── lazy-slide-captcha.css
│ │ ├── lazy-slide-captcha.umd.js
│ │ ├── lazy-slide-captcha.umd.js.map
│ │ ├── lazy-slide-captcha.umd.min.js
│ │ └── lazy-slide-captcha.umd.min.js.map
│ └── vue
│ ├── axios.min.js
│ └── vue.min.js
├── LazySlideCaptcha.sln
└── README.md
/.dockerignore:
--------------------------------------------------------------------------------
1 | **/.classpath
2 | **/.dockerignore
3 | **/.env
4 | **/.git
5 | **/.gitignore
6 | **/.project
7 | **/.settings
8 | **/.toolstarget
9 | **/.vs
10 | **/.vscode
11 | **/*.*proj.user
12 | **/*.dbmdl
13 | **/*.jfm
14 | **/azds.yaml
15 | **/bin
16 | **/charts
17 | **/docker-compose*
18 | **/Dockerfile*
19 | **/node_modules
20 | **/npm-debug.log
21 | **/obj
22 | **/secrets.dev.yaml
23 | **/values.dev.yaml
24 | LICENSE
25 | README.md
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ################################################################################
2 | # 此 .gitignore 文件已由 Microsoft(R) Visual Studio 自动创建。
3 | ################################################################################
4 |
5 | /.vs
6 | /Lazy.SlideCaptcha.Core/bin/Debug/netstandard2.1
7 | /Lazy.SlideCaptcha.Core/obj
8 | /Lazy.SlideCaptcha.Console/bin/Debug/net6.0
9 | /Lazy.SlideCaptcha.Console/obj
10 | /Lazy.SlideCaptcha.Demo/bin/Debug/net6.0
11 | /Lazy.SlideCaptcha.Demo/obj
12 | /packages
13 | /Lazy.SlideCaptcha.Demo/bin/Release/net6.0
14 | /Lazy.SlideCaptcha.Core/bin
15 |
--------------------------------------------------------------------------------
/Components/vue2/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | /dist
4 |
5 |
6 | # local env files
7 | .env.local
8 | .env.*.local
9 |
10 | # Log files
11 | npm-debug.log*
12 | yarn-debug.log*
13 | yarn-error.log*
14 | pnpm-debug.log*
15 |
16 | # Editor directories and files
17 | .idea
18 | .vscode
19 | *.suo
20 | *.ntvs*
21 | *.njsproj
22 | *.sln
23 | *.sw?
24 |
--------------------------------------------------------------------------------
/Components/vue2/README.md:
--------------------------------------------------------------------------------
1 | # LazySlideCaptcha前端组件使用
2 |
3 | ### 安装
4 | ```
5 | npm install lazy-slide-captcha
6 | ```
7 |
8 | ### 项目引入
9 | 
10 |
11 | ```
12 | import LazySlideCaptcha from 'lazy-slide-captcha'
13 | import 'lazy-slide-captcha/dist/lazy-slide-captcha.css';
14 |
15 | Vue.use(LazySlideCaptcha);
16 | ```
17 |
18 | ### 页面使用
19 | ```
20 |
21 |
22 |
32 |
33 |
34 |
35 |
36 |
104 |
105 |
106 |
108 |
109 | ```
110 |
111 |
--------------------------------------------------------------------------------
/Components/vue2/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | presets: [
3 | '@vue/cli-plugin-babel/preset'
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/Components/vue2/jsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es5",
4 | "module": "esnext",
5 | "baseUrl": "./",
6 | "moduleResolution": "node",
7 | "paths": {
8 | "@/*": [
9 | "src/*"
10 | ]
11 | },
12 | "lib": [
13 | "esnext",
14 | "dom",
15 | "dom.iterable",
16 | "scripthost"
17 | ]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Components/vue2/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lazy-slide-captcha",
3 | "version": "1.0.3",
4 | "private": false,
5 | "description": "slide captcha",
6 | "license": "MIT",
7 | "author": "pojianbing",
8 | "main": "./dist/lazy-slide-captcha.common.js",
9 | "files": [
10 | "dist/*",
11 | "src/*",
12 | "public/*",
13 | "*.json",
14 | "*.js"
15 | ],
16 | "scripts": {
17 | "serve": "vue-cli-service serve",
18 | "build": "vue-cli-service build",
19 | "lint": "vue-cli-service lint",
20 | "build-lib": "vue-cli-service build --target lib --name lazy-slide-captcha ./src/package/index.js"
21 | },
22 | "dependencies": {
23 | "core-js": "^3.8.3",
24 | "vue": "^2.6.14"
25 | },
26 | "devDependencies": {
27 | "@babel/core": "^7.12.16",
28 | "@babel/eslint-parser": "^7.12.16",
29 | "@vue/cli-plugin-babel": "~5.0.0",
30 | "@vue/cli-plugin-eslint": "~5.0.0",
31 | "@vue/cli-service": "~5.0.0",
32 | "eslint": "^7.32.0",
33 | "eslint-plugin-vue": "^8.0.3",
34 | "vue-template-compiler": "^2.6.14"
35 | },
36 | "eslintConfig": {
37 | "root": true,
38 | "env": {
39 | "node": true
40 | },
41 | "extends": [
42 | "plugin:vue/essential",
43 | "eslint:recommended"
44 | ],
45 | "parserOptions": {
46 | "parser": "@babel/eslint-parser"
47 | },
48 | "rules": {}
49 | },
50 | "browserslist": [
51 | "> 1%",
52 | "last 2 versions",
53 | "not dead"
54 | ]
55 | }
56 |
--------------------------------------------------------------------------------
/Components/vue2/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojianbing/LazySlideCaptcha/958ea3304e6613944f99a93f419e5a989c3f5d86/Components/vue2/public/favicon.ico
--------------------------------------------------------------------------------
/Components/vue2/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | <%= htmlWebpackPlugin.options.title %>
9 |
10 |
11 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/Components/vue2/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
15 |
57 |
58 |
82 |
--------------------------------------------------------------------------------
/Components/vue2/src/assets/1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojianbing/LazySlideCaptcha/958ea3304e6613944f99a93f419e5a989c3f5d86/Components/vue2/src/assets/1.png
--------------------------------------------------------------------------------
/Components/vue2/src/assets/2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojianbing/LazySlideCaptcha/958ea3304e6613944f99a93f419e5a989c3f5d86/Components/vue2/src/assets/2.png
--------------------------------------------------------------------------------
/Components/vue2/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojianbing/LazySlideCaptcha/958ea3304e6613944f99a93f419e5a989c3f5d86/Components/vue2/src/assets/logo.png
--------------------------------------------------------------------------------
/Components/vue2/src/main.js:
--------------------------------------------------------------------------------
1 | import Vue from 'vue'
2 | import App from './App.vue'
3 |
4 | Vue.config.productionTip = false
5 |
6 | new Vue({
7 | render: h => h(App),
8 | }).$mount('#app')
9 |
--------------------------------------------------------------------------------
/Components/vue2/src/package/index.js:
--------------------------------------------------------------------------------
1 | import LazySlideCaptcha from './lazy-slide-captcha/index';
2 |
3 | const components = [
4 | LazySlideCaptcha
5 | ];
6 |
7 | const install = function(Vue) {
8 | components.forEach(component => {
9 | Vue.component(component.name, component);
10 | });
11 | };
12 |
13 | /* istanbul ignore if */
14 | if (typeof window !== 'undefined' && window.Vue) {
15 | install(window.Vue);
16 | }
17 |
18 | export default {
19 | install
20 | };
--------------------------------------------------------------------------------
/Components/vue2/src/package/lazy-slide-captcha/index.js:
--------------------------------------------------------------------------------
1 | import LazySlideCaptcha from './index.vue';
2 |
3 | /* istanbul ignore next */
4 | LazySlideCaptcha.install = function(Vue) {
5 | Vue.component(LazySlideCaptcha.name, LazySlideCaptcha);
6 | };
7 |
8 | export default LazySlideCaptcha;
--------------------------------------------------------------------------------
/Components/vue2/src/package/lazy-slide-captcha/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
![background]()
11 |
![slider]()
19 |
20 |
24 |
{{ isPassing ? successTip : failTip }}
25 |
26 |
30 |
34 |
35 |
40 |
46 |
{{ text }}
47 |
57 |
58 |
64 |
65 |
66 |
67 |
296 |
297 |
298 |
483 |
--------------------------------------------------------------------------------
/Components/vue2/vue.config.js:
--------------------------------------------------------------------------------
1 | const { defineConfig } = require('@vue/cli-service')
2 | module.exports = defineConfig({
3 | transpileDependencies: true
4 | })
5 |
--------------------------------------------------------------------------------
/Images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojianbing/LazySlideCaptcha/958ea3304e6613944f99a93f419e5a989c3f5d86/Images/.keep
--------------------------------------------------------------------------------
/Images/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojianbing/LazySlideCaptcha/958ea3304e6613944f99a93f419e5a989c3f5d86/Images/image.png
--------------------------------------------------------------------------------
/Images/show.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pojianbing/LazySlideCaptcha/958ea3304e6613944f99a93f419e5a989c3f5d86/Images/show.gif
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 pojianbing
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/CaptchaBuilder.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Lazy.SlideCaptcha.Core
7 | {
8 | public class CaptchaBuilder
9 | {
10 | public IServiceCollection Services { get; set; }
11 |
12 | public CaptchaBuilder(IServiceCollection serviceCollection)
13 | {
14 | Services = serviceCollection;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/CaptchaBuilderExtensions.cs:
--------------------------------------------------------------------------------
1 | using Lazy.SlideCaptcha.Core;
2 | using Lazy.SlideCaptcha.Core.Resources.Handler;
3 | using Lazy.SlideCaptcha.Core.Resources.Provider;
4 | using Lazy.SlideCaptcha.Core.Validator;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 |
10 | namespace Microsoft.Extensions.DependencyInjection
11 | {
12 | public static class CaptchaBuilderExtensions
13 | {
14 | public static CaptchaBuilder AddResourceProvider(this CaptchaBuilder builder) where TProvider : class, IResourceProvider
15 | {
16 | builder.Services.AddSingleton();
17 | return builder;
18 | }
19 |
20 | public static CaptchaBuilder AddResourceHandler(this CaptchaBuilder builder) where THandler : class, IResourceHandler
21 | {
22 | builder.Services.AddSingleton();
23 | return builder;
24 | }
25 |
26 | public static CaptchaBuilder ReplaceValidator(this CaptchaBuilder builder) where TValidator : class, IValidator
27 | {
28 | builder.Services.Replace();
29 | return builder;
30 | }
31 |
32 | public static CaptchaBuilder DisableDefaultTemplates(this CaptchaBuilder builder)
33 | {
34 | var serviceDescriptor = builder.Services.FirstOrDefault(e => e.ImplementationType == typeof(EmbeddedResourceProvider));
35 | if (serviceDescriptor != null)
36 | {
37 | builder.Services.Remove(serviceDescriptor);
38 | }
39 |
40 | return builder;
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/CaptchaData.cs:
--------------------------------------------------------------------------------
1 | namespace Lazy.SlideCaptcha.Core
2 | {
3 | public class CaptchaData
4 | {
5 | public CaptchaData(string id, string backgroundImage, string sliderImage)
6 | {
7 | Id = id;
8 | BackgroundImage = backgroundImage;
9 | SliderImage = sliderImage;
10 | }
11 |
12 | ///
13 | /// id
14 | ///
15 | public string Id { get; set; }
16 | ///
17 | /// 背景图(含凹槽)
18 | ///
19 | public string BackgroundImage { get; set; }
20 | ///
21 | /// 滑动块图
22 | ///
23 | public string SliderImage { get; set; }
24 | }
25 | }
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/CaptchaOptions.cs:
--------------------------------------------------------------------------------
1 | using Lazy.SlideCaptcha.Core.Resources;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Lazy.SlideCaptcha.Core
7 | {
8 | public class CaptchaOptions
9 | {
10 | ///
11 | /// 过期时长
12 | ///
13 | public int ExpirySeconds { get; set; } = 60;
14 |
15 | ///
16 | /// 存储键前缀
17 | ///
18 | public string StoreageKeyPrefix { get; set; } = "slide-captcha";
19 |
20 | ///
21 | /// 容错值(校验时用,缺口位置与实际滑动位置匹配容错范围)
22 | ///
23 | public float Tolerant { get; set; } = 0.02f;
24 |
25 | ///
26 | /// 干扰块数量
27 | ///
28 | public int InterferenceCount { get; set; } = 0;
29 |
30 | ///
31 | /// 背景图
32 | ///
33 | public List Backgrounds { get; set; } = new List();
34 |
35 | ///
36 | /// 模板图(必须是slider,hole的顺序依次出现)
37 | ///
38 | public List Templates { get; set; } = new List();
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/CaptchaServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Lazy.SlideCaptcha.Core;
2 | using Lazy.SlideCaptcha.Core.Generator;
3 | using Lazy.SlideCaptcha.Core.Resources;
4 | using Lazy.SlideCaptcha.Core.Resources.Handler;
5 | using Lazy.SlideCaptcha.Core.Resources.Provider;
6 | using Lazy.SlideCaptcha.Core.Storage;
7 | using Lazy.SlideCaptcha.Core.Validator;
8 | using Microsoft.Extensions.Configuration;
9 | using System;
10 | using System.Collections.Generic;
11 | using System.Text;
12 |
13 | namespace Microsoft.Extensions.DependencyInjection
14 | {
15 | public static class CaptchaServiceCollectionExtensions
16 | {
17 | public static CaptchaBuilder AddSlideCaptcha(this IServiceCollection services, IConfiguration configuration, Action optionsAction = default)
18 | {
19 | services.Configure(configuration?.GetSection("SlideCaptcha"));
20 | if (optionsAction != null) services.PostConfigure(optionsAction);
21 |
22 | var builder = new CaptchaBuilder(services);
23 | services.AddSingleton();
24 | services.AddSingleton();
25 | services.AddSingleton();
26 | services.AddSingleton();
27 | services.AddSingleton();
28 | services.AddSingleton();
29 | services.AddScoped();
30 | services.AddScoped();
31 | services.AddScoped();
32 | services.AddScoped();
33 | return builder;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/CaptchaValidateData.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Lazy.SlideCaptcha.Core
6 | {
7 | public class CaptchaValidateData
8 | {
9 | public CaptchaValidateData(float percent, float tolerant)
10 | {
11 | Percent = percent;
12 | Tolerant = tolerant;
13 | }
14 |
15 | public float Percent { get; set; }
16 | public float Tolerant { get; set; }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/DefaultCaptcha.cs:
--------------------------------------------------------------------------------
1 |
2 | using Lazy.SlideCaptcha.Core.Exceptions;
3 | using Lazy.SlideCaptcha.Core.Generator;
4 | using Lazy.SlideCaptcha.Core.Storage;
5 | using Lazy.SlideCaptcha.Core.Validator;
6 | using Microsoft.Extensions.Options;
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Text;
10 |
11 | namespace Lazy.SlideCaptcha.Core
12 | {
13 | public class DefaultCaptcha : ICaptcha
14 | {
15 | private CaptchaOptions _options;
16 | private ICaptchaImageGenerator _captchaImageGenerator;
17 | private IValidator _validator;
18 | private IStorage _storage;
19 |
20 | public DefaultCaptcha(ICaptchaImageGenerator captchaImageGenerator, IValidator validator, IStorage storage, IOptionsSnapshot options)
21 | {
22 | _options = options.Value;
23 | _captchaImageGenerator = captchaImageGenerator;
24 | _storage = storage;
25 | _validator = validator;
26 | }
27 |
28 | public CaptchaData Generate(string captchaId = null)
29 | {
30 | captchaId = string.IsNullOrWhiteSpace(captchaId) ? Guid.NewGuid().ToString() : captchaId;
31 | var captchImageInfo = _captchaImageGenerator.Generate();
32 | captchImageInfo.Check();
33 |
34 | var captchaValidateData = new CaptchaValidateData(captchImageInfo.Percent, _options.Tolerant);
35 | _storage.Set(captchaId, captchaValidateData, DateTime.Now.AddSeconds(_options.ExpirySeconds).ToUniversalTime());
36 |
37 | return new CaptchaData(captchaId, captchImageInfo.BackgroundImageBase64, captchImageInfo.SliderImageBase64);
38 | }
39 |
40 | public ValidateResult Validate(string captchaId, SlideTrack slideTrack)
41 | {
42 | try
43 | {
44 | var captchaValidateData = _storage.Get(captchaId);
45 | if (captchaValidateData == null) return ValidateResult.Timeout();
46 | var success = _validator.Validate(slideTrack, captchaValidateData);
47 | return success ? ValidateResult.Success() : ValidateResult.Fail();
48 | }
49 | finally
50 | {
51 | _storage.Remove(captchaId);
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Exceptions/SlideCaptchaException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Lazy.SlideCaptcha.Core.Exceptions
6 | {
7 | public class SlideCaptchaException : Exception
8 | {
9 | public SlideCaptchaException() : base()
10 | {
11 | }
12 |
13 | public SlideCaptchaException(string message) : base(message)
14 | {
15 | }
16 |
17 | public SlideCaptchaException(string message, Exception innerException) : base(message, innerException)
18 | {
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Exceptions/SlideCaptchaTimeoutException.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Lazy.SlideCaptcha.Core.Exceptions
6 | {
7 | public class SlideCaptchaTimeoutException : Exception
8 | {
9 | public SlideCaptchaTimeoutException() : base()
10 | {
11 | }
12 |
13 | public SlideCaptchaTimeoutException(string message) : base(message)
14 | {
15 | }
16 |
17 | public SlideCaptchaTimeoutException(string message, Exception innerException) : base(message, innerException)
18 | {
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Generator/CaptchaImageData.cs:
--------------------------------------------------------------------------------
1 | using Lazy.SlideCaptcha.Core.Exceptions;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Lazy.SlideCaptcha.Core.Generator
7 | {
8 | public class CaptchaImageData
9 | {
10 | ///
11 | /// 凹槽x坐标
12 | ///
13 | public int X { get; set; }
14 | ///
15 | /// 凹槽y坐标
16 | ///
17 | public int Y { get; set; }
18 | ///
19 | /// 背景图宽
20 | ///
21 | public int BackgroundImageWidth { get; set; }
22 | ///
23 | /// 背景图高
24 | ///
25 | public int BackgroundImageHeight { get; set; }
26 | ///
27 | /// 背景图
28 | ///
29 | public string BackgroundImageBase64 { get; set; }
30 | ///
31 | /// 滑动块图宽
32 | ///
33 | public int SliderImageWidth { get; set; }
34 | ///
35 | /// 滑动块图高
36 | ///
37 | public int SliderImageHeight { get; set; }
38 | ///
39 | /// 滑动块图
40 | ///
41 | public string SliderImageBase64 { get; set; }
42 |
43 | ///
44 | /// 凹槽位置百分比
45 | ///
46 | public float Percent
47 | {
48 | get
49 | {
50 | if (BackgroundImageWidth <= 0) return 0;
51 | return 1.0f * X / BackgroundImageWidth;
52 | }
53 | }
54 |
55 | public void Check()
56 | {
57 | // 校验
58 | if (this.X <= 0) throw new SlideCaptchaException($"CaptchaImageData数据异常: {nameof(X)}小于等于0");
59 | if (this.Y <= 0) throw new SlideCaptchaException($"CaptchaImageData数据异常: {nameof(Y)}小于等于0");
60 | if (this.BackgroundImageWidth <= 0) throw new SlideCaptchaException($"CaptchaImageData数据异常: {nameof(BackgroundImageWidth)}小于等于0");
61 | if (this.BackgroundImageHeight <= 0) throw new SlideCaptchaException($"CaptchaImageData数据异常: {nameof(BackgroundImageHeight)}小于等于0");
62 | if (this.SliderImageWidth <= 0) throw new SlideCaptchaException($"CaptchaImageData数据异常: {nameof(SliderImageWidth)}小于等于0");
63 | if (this.SliderImageHeight <= 0) throw new SlideCaptchaException($"CaptchaImageData数据异常: {nameof(SliderImageHeight)}小于等于0");
64 | if(string.IsNullOrWhiteSpace(this.BackgroundImageBase64)) throw new SlideCaptchaException($"CaptchaImageData数据异常: {nameof(BackgroundImageBase64)}为空");
65 | if (string.IsNullOrWhiteSpace(this.SliderImageBase64)) throw new SlideCaptchaException($"CaptchaImageData数据异常: {nameof(SliderImageBase64)}为空");
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Generator/DefaultCaptchaImageGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Lazy.SlideCaptcha.Core.Resources;
5 | using Microsoft.Extensions.Options;
6 | using SkiaSharp;
7 |
8 | namespace Lazy.SlideCaptcha.Core.Generator
9 | {
10 | public class DefaultCaptchaImageGenerator : ICaptchaImageGenerator
11 | {
12 | private IResourceManager _resourceManager;
13 | private Random _random = new Random();
14 | private CaptchaOptions _options;
15 |
16 | public DefaultCaptchaImageGenerator(IResourceManager resourceManager, IOptionsSnapshot options)
17 | {
18 | _resourceManager = resourceManager;
19 | _options = options.Value;
20 | }
21 |
22 | ///
23 | /// 计算凹槽轮廓
24 | /// 原理: 一行一行扫描,每行不透明小方块连接形成数个小长方形
25 | ///
26 | ///
27 | ///
28 | private static SKPath CalcHoleShape(SKBitmap holeTemplateImage)
29 | {
30 | int temp = 0;
31 | var path = new SKPath();
32 | for (int y = 0; y < holeTemplateImage.Height; y++)
33 | {
34 | for (int x = 0; x < holeTemplateImage.Width; x++)
35 | {
36 | var pixel = holeTemplateImage.GetPixel(x, y);
37 | if (pixel.Alpha != 0)
38 | {
39 | if (temp == 0)
40 | {
41 | temp = x;
42 | }
43 | }
44 | else
45 | {
46 | if (temp != 0)
47 | {
48 | path.AddRect(new SKRect(temp, y, x, y + 1));
49 | temp = 0;
50 | }
51 | }
52 | }
53 | }
54 | return path;
55 | }
56 |
57 | public CaptchaImageData Generate()
58 | {
59 | var background = _resourceManager.RandomBackground();
60 | (var silder, var hole) = _resourceManager.RandomTemplate();
61 |
62 | using var backgroundImage = SKBitmap.FromImage(SKImage.FromEncodedData(background));
63 | using var sliderTemplateImage = SKBitmap.FromImage(SKImage.FromEncodedData(silder));
64 | using var holeTemplateImage = SKBitmap.FromImage(SKImage.FromEncodedData(hole));
65 | using var holeMattingImage = new SKBitmap(sliderTemplateImage.Width, sliderTemplateImage.Height);
66 | using var sliderBarImage = new SKBitmap(sliderTemplateImage.Width, backgroundImage.Height);
67 |
68 | // 凹槽位置
69 | int randomX = _random.Next(holeTemplateImage.Width + 5, backgroundImage.Width - holeTemplateImage.Width - 10);
70 | int randomY = _random.Next(5, backgroundImage.Height - holeTemplateImage.Height - 5);
71 |
72 | // 根据透明度计算凹槽图轮廓形状(形状由不透明区域形成)
73 | var holeShape = CalcHoleShape(holeTemplateImage);
74 | // 生成凹槽抠图
75 | using var slideCanvas = new SKCanvas(holeMattingImage);
76 | slideCanvas.ClipPath(holeShape, SKClipOperation.Intersect, true);
77 | slideCanvas.DrawBitmap(backgroundImage, -randomX, -randomY);
78 | // 叠加拖块模板
79 | slideCanvas.DrawBitmap(sliderTemplateImage, 0, 0);
80 | // 绘制拖块条
81 | var slideBarCanvas = new SKCanvas(sliderBarImage);
82 | slideBarCanvas.DrawBitmap(holeMattingImage, 0, randomY);
83 |
84 | // 生成背景
85 | using var bgCanvas = new SKCanvas(backgroundImage);
86 | bgCanvas.DrawBitmap(holeTemplateImage, randomX, randomY);
87 | // 绘制干扰块
88 | DrawInterferences(backgroundImage, holeTemplateImage, randomX, randomY, _options.InterferenceCount);
89 |
90 | return new CaptchaImageData
91 | {
92 | X = randomX,
93 | Y = randomY,
94 | BackgroundImageWidth = backgroundImage.Width,
95 | BackgroundImageHeight = backgroundImage.Height,
96 | BackgroundImageBase64 = ToBase64(backgroundImage),
97 | SliderImageWidth = holeMattingImage.Width,
98 | SliderImageHeight = holeMattingImage.Height,
99 | SliderImageBase64 = ToBase64(sliderBarImage)
100 | };
101 | }
102 |
103 | ///
104 | /// 生成干扰块
105 | /// 要求: 所有缺块不能重叠,
106 | ///
107 | ///
108 | ///
109 | ///
110 | ///
111 | ///
112 | private void DrawInterferences(SKBitmap backgroundImage, SKBitmap holeTemplateImage, int holeX, int holeY, int count)
113 | {
114 | if (count <= 0) return;
115 |
116 | var holdeWidth = holeTemplateImage.Width;
117 | var holdeHeight = holeTemplateImage.Height;
118 | var minX = holdeWidth + 5;
119 | var maxX = backgroundImage.Width - holdeWidth - 10;
120 | var minY = 5;
121 | var maxY = backgroundImage.Height - holdeHeight - 5;
122 |
123 | using var canvas = new SKCanvas(backgroundImage);
124 |
125 | var excludeRegions = new List
126 | {
127 | new SKRect(holeX, holeY, holeX + holdeWidth, holeY + holdeHeight)
128 | };
129 | for (var i = 0; i < count; i++)
130 | {
131 | (var x, var y) = GenerateInterferencePosition(minX, maxX, minY, maxY, holeX, holeY, holdeWidth, holdeHeight, excludeRegions);
132 | if (x == 0) continue;
133 |
134 | canvas.DrawBitmap(holeTemplateImage, x, y);
135 | excludeRegions.Add(new SKRect(x, y, x + holdeWidth, y + holdeHeight));
136 | }
137 | }
138 |
139 | ///
140 | /// 生成干扰块位置
141 | ///
142 | /// 限定范围最小x
143 | /// 限定范围最大x
144 | /// 限定范围最小y
145 | /// 限定范围最大y
146 | /// 缺块X
147 | /// 缺块Y
148 | /// 缺块宽度
149 | /// 缺块高度
150 | /// 排除的范围
151 | ///
152 | private (int x, int y) GenerateInterferencePosition(int minX, int maxX, int minY, int maxY, int holeX, int holeY, int holeWidth, int holeHeight, List excludeRegions)
153 | {
154 | var random = new Random();
155 |
156 | for (var i = 0; i < 100; i++)
157 | {
158 | var x = random.Next(minX, maxX);
159 | var y = random.Next(minY, maxY);
160 |
161 | // 要与答案缺块的Y有一定间距
162 | if (Math.Abs(y - holeY) < holeHeight / 2) continue;
163 |
164 | var region = new SKRect(x, y, x + holeWidth, y + holeHeight);
165 | var isIntersect = excludeRegions.Any(x => x.IntersectsWith(region));
166 | if (isIntersect) continue;
167 |
168 | return (x, y);
169 | }
170 |
171 | // 代表未找到
172 | return (0, 0);
173 | }
174 |
175 | private static string ToBase64(SKBitmap bitmap)
176 | {
177 | var bytes = bitmap.Encode(SKEncodedImageFormat.Png, 100).ToArray();
178 | return "data:image/png;base64," + Convert.ToBase64String(bytes);
179 | }
180 | }
181 | }
182 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Generator/ICaptchaImageGenerator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Lazy.SlideCaptcha.Core.Generator
6 | {
7 | public interface ICaptchaImageGenerator
8 | {
9 | CaptchaImageData Generate();
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/ICaptcha.cs:
--------------------------------------------------------------------------------
1 | using Lazy.SlideCaptcha.Core.Validator;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Lazy.SlideCaptcha.Core
7 | {
8 | public interface ICaptcha
9 | {
10 | ///
11 | /// 生成验证码
12 | ///
13 | /// 验证码id
14 | ///
15 | CaptchaData Generate(string captchaId = null);
16 |
17 | ///
18 | /// 校验
19 | ///
20 | /// 验证码id
21 | /// 滑动轨迹
22 | ///
23 | ValidateResult Validate(string captchaId, SlideTrack slideTrack);
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Lazy.SlideCaptcha.Core.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.1
5 | 基于.Net Standard 2.1的滑动验证码模块。从2.0.0起绘图由ImageSharp调整为SkiaSharp。
6 | https://gitee.com/pojianbing/lazy-slide-captcha
7 | https://gitee.com/pojianbing/lazy-slide-captcha
8 | git
9 | 2.0.0
10 | 2.0.0
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/DefaultResourceManager.cs:
--------------------------------------------------------------------------------
1 | using Lazy.SlideCaptcha.Core.Exceptions;
2 | using Lazy.SlideCaptcha.Core.Resources.Handler;
3 | using Lazy.SlideCaptcha.Core.Resources.Provider;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace Lazy.SlideCaptcha.Core.Resources
11 | {
12 | public class DefaultResourceManager : IResourceManager
13 | {
14 | private readonly Random _random = new Random();
15 | private readonly IResourceHandlerManager _resourceProviderManager;
16 | private readonly List _backgrounds = new List();
17 | private readonly List _templates = new List();
18 |
19 | public DefaultResourceManager(IEnumerable resourceProviders, IResourceHandlerManager resourceProviderManager)
20 | {
21 | _resourceProviderManager = resourceProviderManager;
22 |
23 | foreach (var provider in resourceProviders)
24 | {
25 | _backgrounds.AddRange(provider.Backgrounds());
26 | _templates.AddRange(provider.Templates());
27 | }
28 | }
29 |
30 | public byte[] RandomBackground()
31 | {
32 | if (_backgrounds.Count == 0) throw new SlideCaptchaException("背景图不能为空");
33 |
34 | var background = _backgrounds[_random.Next(_backgrounds.Count)];
35 | return _resourceProviderManager.Handle(background);
36 | }
37 |
38 | public (byte[], byte[]) RandomTemplate()
39 | {
40 | if (_templates.Count == 0) throw new SlideCaptchaException("模板不能为空");
41 |
42 | var template = _templates[_random.Next(_templates.Count)];
43 | var hole = _resourceProviderManager.Handle(template.Hole);
44 | var slider = _resourceProviderManager.Handle(template.Slider);
45 | return (slider, hole);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/Handler/CachedResourceHandlerManager.cs:
--------------------------------------------------------------------------------
1 | using Lazy.SlideCaptcha.Core.Exceptions;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Lazy.SlideCaptcha.Core.Resources.Handler
9 | {
10 | public class CachedResourceHandlerManager : IResourceHandlerManager
11 | {
12 | private IEnumerable _resourceHandlers;
13 | private Dictionary _cache = new Dictionary();
14 |
15 | public CachedResourceHandlerManager(IEnumerable resourceHandlers)
16 | {
17 | _resourceHandlers = resourceHandlers;
18 | }
19 |
20 | public byte[] Handle(Resource resource)
21 | {
22 | if (resource == null) throw new ArgumentNullException(nameof(resource));
23 |
24 | if (_cache.ContainsKey(resource))
25 | {
26 | return _cache[resource];
27 | }
28 |
29 | foreach (var provider in _resourceHandlers)
30 | {
31 | if (provider.CanHandle(resource.Type))
32 | {
33 | var bytes = provider.Handle(resource);
34 | _cache.Add(resource, bytes);
35 | return bytes;
36 | }
37 | }
38 |
39 | throw new SlideCaptchaException("没有可用的资源提供者!");
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/Handler/EmbeddedResourceHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Reflection;
5 | using System.Text;
6 |
7 | namespace Lazy.SlideCaptcha.Core.Resources.Handler
8 | {
9 | public class EmbeddedResourceHandler : IResourceHandler
10 | {
11 | public const string TYPE = "embedded";
12 |
13 | public bool CanHandle(string handlerType)
14 | {
15 | return handlerType == TYPE;
16 | }
17 |
18 | public byte[] Handle(Resource resource)
19 | {
20 | var assembly = Assembly.GetExecutingAssembly();
21 | var stream = assembly.GetManifestResourceStream(resource.Data);
22 | return StreamToBytes(stream);
23 | }
24 |
25 | private static byte[] StreamToBytes(Stream stream)
26 | {
27 | byte[] bytes = new byte[stream.Length];
28 | stream.Read(bytes, 0, bytes.Length);
29 |
30 | // 设置当前流的位置为流的开始
31 | stream.Seek(0, SeekOrigin.Begin);
32 | return bytes;
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/Handler/FileResourceHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Lazy.SlideCaptcha.Core.Resources.Handler
8 | {
9 | public class FileResourceHandler : IResourceHandler
10 | {
11 | public const string TYPE = "file";
12 |
13 | public bool CanHandle(string handlerType)
14 | {
15 | return handlerType == TYPE;
16 | }
17 |
18 | public byte[] Handle(Resource resource)
19 | {
20 | if (resource == null) throw new ArgumentNullException(nameof(resource));
21 | return File.ReadAllBytes(resource.Data);
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/Handler/IResourceHandler.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Lazy.SlideCaptcha.Core.Resources.Handler
8 | {
9 | public interface IResourceHandler
10 | {
11 | bool CanHandle(string handlerType);
12 | byte[] Handle(Resource resource);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/Handler/IResourceHandlerManager.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace Lazy.SlideCaptcha.Core.Resources.Handler
8 | {
9 | public interface IResourceHandlerManager
10 | {
11 | byte[] Handle(Resource resource);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/IResourceManager.cs:
--------------------------------------------------------------------------------
1 |
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace Lazy.SlideCaptcha.Core.Resources
9 | {
10 | public interface IResourceManager
11 | {
12 | byte[] RandomBackground();
13 | (byte[], byte[]) RandomTemplate();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/Provider/EmbeddedResourceProvider.cs:
--------------------------------------------------------------------------------
1 | using Lazy.SlideCaptcha.Core.Resources.Handler;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.IO;
5 | using System.Linq;
6 | using System.Reflection;
7 | using System.Text;
8 |
9 | namespace Lazy.SlideCaptcha.Core.Resources.Provider
10 | {
11 | public class EmbeddedResourceProvider : IResourceProvider
12 | {
13 | private const int DEFAULT_TEMPLATE_COUNT = 5;
14 |
15 | public List Backgrounds()
16 | {
17 | return new List();
18 | }
19 |
20 | public List Templates()
21 | {
22 | var templatePairs = new List();
23 |
24 | var assembly = Assembly.GetExecutingAssembly();
25 | var names = assembly.GetManifestResourceNames().ToList() ;
26 |
27 | for (var i = 0; i < DEFAULT_TEMPLATE_COUNT; i++)
28 | {
29 | var sliderResourceName = $"Lazy.SlideCaptcha.Core.templates._{i + 1}.slider.png";
30 | var holeResourceName = $"Lazy.SlideCaptcha.Core.templates._{i + 1}.hole.png";
31 |
32 | var sliderResource = new Resource(EmbeddedResourceHandler.TYPE, sliderResourceName);
33 | var holeResource = new Resource(EmbeddedResourceHandler.TYPE, holeResourceName);
34 | templatePairs.Add(TemplatePair.Create(sliderResource, holeResource));
35 | }
36 |
37 | return templatePairs;
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/Provider/IResourceProvider.cs:
--------------------------------------------------------------------------------
1 |
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Lazy.SlideCaptcha.Core.Resources.Provider
7 | {
8 | public interface IResourceProvider
9 | {
10 | List Backgrounds();
11 | List Templates();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/Provider/OptionsResourceProvider.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Options;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Lazy.SlideCaptcha.Core.Resources.Provider
7 | {
8 | public class OptionsResourceProvider : IResourceProvider
9 | {
10 | private readonly List _backgrounds = new List();
11 | private readonly List _templates = new List();
12 |
13 | public OptionsResourceProvider(IOptionsMonitor optionAccessor)
14 | {
15 | var options = optionAccessor.CurrentValue;
16 |
17 | if (options.Backgrounds != null)
18 | {
19 | _backgrounds.AddRange(options.Backgrounds);
20 | }
21 |
22 | if (options.Templates != null)
23 | {
24 | _templates.AddRange(options.Templates);
25 | }
26 | }
27 |
28 | public List Backgrounds()
29 | {
30 | return _backgrounds; // ?克隆
31 | }
32 |
33 | public List Templates()
34 | {
35 | return _templates;
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/Resource.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 |
6 | namespace Lazy.SlideCaptcha.Core.Resources
7 | {
8 | public class Resource
9 | {
10 | public Resource()
11 | {
12 |
13 | }
14 |
15 | public Resource(string type, string data)
16 | {
17 | Data = data;
18 | Type = type;
19 | }
20 |
21 | public string Data { get; set; }
22 | public string Type { get; set; }
23 | public Dictionary Extras { get; set; }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Resources/TemplatePair.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Lazy.SlideCaptcha.Core.Resources
6 | {
7 | public class TemplatePair
8 | {
9 | public TemplatePair() { }
10 |
11 | public TemplatePair(Resource slider, Resource hole)
12 | {
13 | Slider = slider;
14 | Hole = hole;
15 | }
16 |
17 | public Resource Slider { get; set; }
18 | public Resource Hole { get; set; }
19 |
20 | public static TemplatePair Create(Resource slider, Resource hole)
21 | {
22 | return new TemplatePair(slider, hole);
23 | }
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/ServiceCollectionExtensions.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.DependencyInjection;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 |
7 | namespace Microsoft.Extensions.DependencyInjection
8 | {
9 | public static class ServiceCollectionExtensions
10 | {
11 | public static IServiceCollection Replace(this IServiceCollection services)
12 | where TImplementation : TService
13 | {
14 | return services.Replace(typeof(TImplementation));
15 | }
16 |
17 | public static IServiceCollection Replace(this IServiceCollection services, Type implementationType)
18 | {
19 | return services.Replace(typeof(TService), implementationType);
20 | }
21 |
22 | public static IServiceCollection Replace(this IServiceCollection services, Type serviceType, Type implementationType)
23 | {
24 | if (services == null)
25 | {
26 | throw new ArgumentNullException(nameof(services));
27 | }
28 |
29 | if (serviceType == null)
30 | {
31 | throw new ArgumentNullException(nameof(serviceType));
32 | }
33 |
34 | if (implementationType == null)
35 | {
36 | throw new ArgumentNullException(nameof(implementationType));
37 | }
38 |
39 | if (!services.TryGetDescriptors(serviceType, out var descriptors))
40 | {
41 | throw new ArgumentException($"No services found for {serviceType.FullName}.", nameof(serviceType));
42 | }
43 |
44 | foreach (var descriptor in descriptors)
45 | {
46 | var index = services.IndexOf(descriptor);
47 |
48 | services.Insert(index, descriptor.WithImplementationType(implementationType));
49 |
50 | services.Remove(descriptor);
51 | }
52 |
53 | return services;
54 | }
55 |
56 | private static bool TryGetDescriptors(this IServiceCollection services, Type serviceType, out ICollection descriptors)
57 | {
58 | return (descriptors = services.Where(service => service.ServiceType == serviceType).ToArray()).Any();
59 | }
60 |
61 | private static ServiceDescriptor WithImplementationType(this ServiceDescriptor descriptor, Type implementationType)
62 | {
63 | return new ServiceDescriptor(descriptor.ServiceType, implementationType, descriptor.Lifetime);
64 | }
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Storage/DefaultStorage.cs:
--------------------------------------------------------------------------------
1 | using Microsoft.Extensions.Caching.Distributed;
2 | using Microsoft.Extensions.Options;
3 | using Newtonsoft.Json;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Text;
7 |
8 | namespace Lazy.SlideCaptcha.Core.Storage
9 | {
10 | public class DefaultStorage : IStorage
11 | {
12 | private readonly IDistributedCache _cache;
13 | private readonly IOptionsMonitor _options;
14 |
15 | public DefaultStorage(IOptionsMonitor options, IDistributedCache cache)
16 | {
17 | _options = options;
18 | _cache = cache;
19 | }
20 |
21 | private string WrapKey(string key)
22 | {
23 | return $"{this._options.CurrentValue.StoreageKeyPrefix}{key}";
24 | }
25 |
26 | public T Get(string key)
27 | {
28 | var bytes = _cache.Get(WrapKey(key));
29 | if (bytes == null) return default(T);
30 | var json = Encoding.UTF8.GetString(bytes, 0, bytes.Length);
31 | return JsonConvert.DeserializeObject(json);
32 | }
33 |
34 | public void Remove(string key)
35 | {
36 | _cache.Remove(WrapKey(key));
37 | }
38 |
39 | public void Set(string key, T value, DateTimeOffset absoluteExpiration)
40 | {
41 | string json = JsonConvert.SerializeObject(value);
42 | byte[] bytes = Encoding.UTF8.GetBytes(json);
43 |
44 | _cache.Set(WrapKey(key), bytes, new DistributedCacheEntryOptions
45 | {
46 | AbsoluteExpiration = absoluteExpiration
47 | });
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Storage/IStorage.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Lazy.SlideCaptcha.Core.Storage
6 | {
7 | public interface IStorage
8 | {
9 | void Set(string key, T value, DateTimeOffset absoluteExpiration);
10 |
11 | T Get(string key);
12 |
13 | void Remove(string key);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/ValidateResult.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Lazy.SlideCaptcha.Core
6 | {
7 | public class ValidateResult
8 | {
9 | public ValidateResultType Result { get; set; }
10 | public string Message { get; set; }
11 |
12 | public static ValidateResult Success()
13 | {
14 | return new ValidateResult { Result = ValidateResultType.Success, Message = "成功" };
15 | }
16 |
17 | public static ValidateResult Fail()
18 | {
19 | return new ValidateResult { Result = ValidateResultType.ValidateFail, Message = "验证失败" };
20 | }
21 |
22 | public static ValidateResult Timeout()
23 | {
24 | return new ValidateResult { Result = ValidateResultType.Timeout, Message = "验证超时" };
25 | }
26 |
27 | public enum ValidateResultType
28 | {
29 | Success = 0,
30 | ValidateFail = 1,
31 | Timeout = 2
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Validator/BaseValidator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Lazy.SlideCaptcha.Core.Validator
6 | {
7 | public abstract class BaseValidator : IValidator
8 | {
9 | public bool Validate(SlideTrack slideTrack, CaptchaValidateData captchaValidateData)
10 | {
11 | if (slideTrack == null) throw new ArgumentNullException(nameof(slideTrack));
12 | if (captchaValidateData == null) throw new ArgumentNullException(nameof(captchaValidateData));
13 |
14 | slideTrack.Check();
15 |
16 | var min = captchaValidateData.Percent - captchaValidateData.Tolerant;
17 | var max = captchaValidateData.Percent + captchaValidateData.Tolerant;
18 |
19 | if (slideTrack.Percent < min || slideTrack.Percent > max)
20 | {
21 | return false;
22 | }
23 |
24 | return ValidateCore(slideTrack, captchaValidateData);
25 | }
26 |
27 | public abstract bool ValidateCore(SlideTrack slideTrack, CaptchaValidateData captchaValidateData);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Validator/BasicValidator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Lazy.SlideCaptcha.Core.Validator
6 | {
7 | public class BasicValidator : BaseValidator, IValidator
8 | {
9 | public override bool ValidateCore(SlideTrack slideTrack, CaptchaValidateData captchaValidateData)
10 | {
11 | slideTrack.CheckTracks();
12 |
13 | // 进行行为轨迹检测
14 | var startSlidingTime = slideTrack.StartTime.ToFileTimeUtc();
15 | long endSlidingTime = slideTrack.EndTime.ToFileTimeUtc();
16 | var bgImageWidth = slideTrack.BackgroundImageWidth;
17 | var trackList = slideTrack.Tracks;
18 |
19 | // check1: 滑动时间如果小于300毫秒 返回false
20 | if (startSlidingTime + 300 > endSlidingTime)
21 | {
22 | return false;
23 | }
24 |
25 | // check2:轨迹数据要是少于10,或者大于背景宽度的五倍 返回false
26 | if (trackList.Count < 10 || trackList.Count > bgImageWidth * 5)
27 | {
28 | return false;
29 | }
30 |
31 | // check3:x轴和y轴应该是从0开始的,要是一开始x轴和y轴乱跑,返回false
32 | var firstTrack = trackList[0];
33 | if (firstTrack.X > 10 || firstTrack.X < -10 || firstTrack.Y > 10 || firstTrack.Y < -10)
34 | {
35 | return false;
36 | }
37 |
38 | // check4: 如果y轴是相同的,必然是机器操作,直接返回false (暂时去掉,容易失败)
39 | // check5:x轴或者y轴直接的区间跳跃过大的话返回 false (暂时去掉,容易失败)
40 | // check6: 如果x轴超过图片宽度的频率过高,返回false
41 | int check4 = 1;
42 | int check6 = 0;
43 | for (int i = 1; i < trackList.Count; i++)
44 | {
45 | var track = trackList[i];
46 | // check4
47 | if (firstTrack.Y == track.Y) check4++;
48 |
49 | // check7
50 | if (track.X >= bgImageWidth) check6++;
51 |
52 | // check5
53 | var preTrack = trackList[i - 1];
54 | if ((track.X - preTrack.X) > 50 || (track.Y - preTrack.Y) > 50) return false; // 快速来回拖动可能导致这里验证不通过
55 | }
56 | if (check4 == trackList.Count || check6 > 200)
57 | {
58 | return false;
59 | }
60 |
61 | return true;
62 |
63 | //// check7: x轴应该是由快到慢的, 要是速率一致,返回false
64 | //int splitPos = (int)(trackList.Count * 0.7);
65 | //var splitPostTrack = trackList[splitPos - 1];
66 | //int posTime = splitPostTrack.T;
67 | //float startAvgPosTime = posTime / (float)splitPos;
68 |
69 | //var lastTrack = trackList[trackList.Count - 1];
70 | //float endAvgPosTime = (lastTrack.T - posTime) / (float)(trackList.Count - splitPos);
71 |
72 | //return endAvgPosTime > startAvgPosTime;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Validator/IValidator.cs:
--------------------------------------------------------------------------------
1 |
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Lazy.SlideCaptcha.Core.Validator
7 | {
8 | public interface IValidator
9 | {
10 | bool Validate(SlideTrack slideTrack, CaptchaValidateData captchaValidateData);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Validator/SimpleValidator.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Text;
4 |
5 | namespace Lazy.SlideCaptcha.Core.Validator
6 | {
7 | ///
8 | /// 仅使用BaseValidator的位置校验
9 | ///
10 | public class SimpleValidator : BaseValidator, IValidator
11 | {
12 | public override bool ValidateCore(SlideTrack slideTrack, CaptchaValidateData captchaValidateData)
13 | {
14 | return true;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Lazy.SlideCaptcha.Core/Validator/SlideTrack.cs:
--------------------------------------------------------------------------------
1 | using Lazy.SlideCaptcha.Core.Exceptions;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Text;
5 |
6 | namespace Lazy.SlideCaptcha.Core.Validator
7 | {
8 | ///
9 | /// 滑动轨迹
10 | ///
11 | public class SlideTrack
12 | {
13 | ///
14 | /// 背景图片宽度(可能经过缩放,不是原始图片宽高)
15 | ///
16 | public int BackgroundImageWidth { get; set; }
17 | ///
18 | /// 背景图片高度(可能经过缩放,不是原始图片宽高)
19 | ///
20 | public int BackgroundImageHeight { get; set; }
21 | ///
22 | /// 滑块图片宽度(可能经过缩放,不是原始图片宽高)
23 | ///
24 | public int SliderImageWidth { get; set; }
25 | ///
26 | /// 滑块图片高度(可能经过缩放,不是原始图片宽高)
27 | ///
28 | public int SliderImageHeight { get; set; }
29 | ///
30 | /// 滑动开始时间(可能经过缩放,不是原始图片宽高)
31 | ///
32 | public DateTime StartTime { get; set; }
33 | ///
34 | /// 滑动结束时间
35 | ///
36 | public DateTime EndTime { get; set; }
37 | ///
38 | /// 轨迹
39 | ///
40 | public List