如果您没有找到您要寻找的答案,请向我们发送您的问题:http://cksource.com/contact
实现身份验证器
默认情况下,CKFinder 会拒绝所有人访问其界面。
要添加您的身份验证器,请实现 IAuthenticator 接口,并在 ConnectorBuilder.SetAuthenticator 方法中设置它。
身份验证器应确定执行请求的用户是否可以访问 CKFinder,并且应为该用户分配角色。
最简单的实现可能如下所示
using System.Linq;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
public class MyAuthenticator : IAuthenticator
{
public Task<IUser> AuthenticateAsync(ICommandRequest commandRequest, CancellationToken cancellationToken)
{
var claimsPrincipal = commandRequest.Principal as ClaimsPrincipal;
var roles = claimsPrincipal?.Claims?.Where(x => x.Type == ClaimTypes.Role).Select(x => x.Value).ToArray();
var isAuthenticated = true;
var user = new User(isAuthenticated, roles);
return Task.FromResult((IUser)user);
}
}
每个实例使用不同的文件夹
如果您使用多个 CKFinder 实例,则可以使用不同的 id
属性并将它们传递给服务器连接器请求。
CKFinder.start( { id: 'instanceNo1', pass: 'id' } );
CKFinder.start( { id: 'instanceNo2', pass: 'id' } );
在连接器端,您可以在传递给 SetRequestConfiguration 的操作内部使用 request.QueryParameters["id"].FirstOrDefault()
获取当前实例的名称,并将其用于动态配置修改。这样,您可以使每个实例使用自己的根文件夹作为本地文件系统后端。
connectorBuilder
.SetRequestConfiguration(
(request, config) =>
{
var instanceId = request.QueryParameters["id"].FirstOrDefault() ?? string.Empty;
var root = GetRootByInstanceId(instanceId);
var baseUrl = GetBaseUrlByInstanceId(instanceId);
config.AddProxyBackend("default", new LocalStorage(root));
});
出于安全原因,您应该避免在目录路径中直接使用实例名称,并使用某种白名单。上面配置示例中使用的 GetRootByInstanceId()
方法可能如下所示
private static string GetRootByInstanceId(string instanceId)
{
var pathMap = new Dictionary<string, string>
{
{ "instanceNo1", @"C:\Files\No1" },
{ "instanceNo2", @"C:\Files\No2" }
};
string root;
if (pathMap.TryGetValue(instanceId, out root))
{
return root;
}
throw new CustomErrorException("Invalid instance Id");
}
每个用户使用私有文件夹
要为用户创建单独的目录,您需要创建一个简单的机制来将当前用户映射到相应的目录路径。
在构建目录路径时,您应该记住以下内容,这些内容可能会导致路径遍历攻击
- 不要泄露任何敏感信息。
- 不要使用任何不安全的数据。
在此示例中,使用当前用户名的 sha1
哈希值。
注意:在为用户创建私有目录时,您还应该记住内部设置,例如缩略图和键值存储提供程序,它们也应该分开。
connectorBuilder.SetRequestConfiguration(
(request, config) =>
{
var userName = request.Principal?.Identity?.Name;
if (userName != null)
{
var sha = new SHA1CryptoServiceProvider();
var hash = sha.ComputeHash(Encoding.UTF8.GetBytes(userName));
var folderName = BitConverter.ToString(hash).Replace("-", string.Empty);
config.AddResourceType("private", builder => builder.SetBackend("default", folderName));
config.SetThumbnailBackend("default", $"App_Data/{folderName}");
config.SetKeyValueStoreProvider(new EntityFrameworkKeyValueStoreProvider(
"CacheConnectionString", string.Empty, folderName));
}
})
磁盘配额
在此示例中,假设您已使用 IsQuotaAvailable()
方法实现了您自己的检查用户存储配额的逻辑。您可以将此逻辑附加到 CKFinder 中您要检查的命令的 命令前事件(在检查配额的情况下:例如 FileUpload
、CopyFiles
、ImageResize
、CreateFolder
等命令)。
有关实现此功能的源代码,请参阅 DiskQuota 插件示例。
记录用户操作
在此示例中,目标是创建一个用于记录用户操作的插件。这可以通过使用 事件 系统来实现。为了便于此示例,让我们假设所有与 中间事件 相对应的用户操作都应记录在案。为此,需要创建简单的事件监听器并将它们附加到应记录的事件。
有关实现此功能的完整源代码,请参阅 UserActionsLogger 插件示例。
如果插件已正确注册,您应该在日志文件中看到类似于以下内容的输出。
2016-02-24 11:24:40.0063 | INFO | dummyUser1 - Folder create: Files://folder/
2016-02-24 11:24:51.5327 | INFO | dummyUser1 - File upload: Files://folder/image.jpg
2016-02-24 11:25:10.1064 | INFO | dummyUser1 - File rename: Files://folder/image.jpg -> Files://folder/image2.jpg
2016-02-24 11:25:25.7100 | INFO | dummyUser1 - File move: Files://document.txt -> Files://folder/document.txt
2016-02-24 11:25:43.9000 | INFO | dummyUser1 - File copy: Files://folder/image.jpg -> Files://image.jpg
2016-02-24 11:25:49.6668 | INFO | dummyUser1 - File delete: Files://folder/image.jpg
有关特定事件传递的事件对象参数类型的更多详细信息,请参阅 事件 部分。
自定义命令
此示例展示了一个简单的命令插件,它返回有关文件的基本信息。
有关实现此功能的完整源代码,请参阅 GetFileInfo 插件示例。
如果启用了此插件,您可以调用额外的 GetFileInfo
命令,该命令返回有关文件的某些非常基本的信息,例如大小和上次修改时间戳。此行为可以轻松地更改为返回有关文件的任何其他信息(例如,图像的 EXIF 数据或 mp3 文件的 ID3 标签)。
GetFileInfo
说明 | 返回有关文件的基本信息。 |
方法 | GET |
示例请求 | 获取位于 Files 资源类型 sub1 目录中的 foo.png 文件的基本信息。/ckfinder/connector?command=GetFileInfo&type=Files¤tFolder=/sub1/&fileName=foo.png
|
示例响应 | {
"name":"foo.png",
"createDate":"20160128084240",
"updateDate":"20160128084240",
"size":27511,
"mimeType":"image/png"
}
|
有关命令的更多详细信息,请参阅 CKFinder ASP.NET 连接器文档的 命令 部分。
将资源类型指向现有文件夹
资源类型文件夹可以使用 SetBackend 方法在 ConnectorBuilder 中定义的 SetRequestConfiguration 操作执行期间定义,也可以使用 folder
配置选项定义(请参阅 资源类型)。定义的目录相对于后端的根目录。
考虑以下文件夹结构
rootDir
└── dir1
└── dir2
└── dir3
其中 rootDir
是为名为 default
的后端定义的根目录。
只需将/
作为第二个参数传递给SetBackend方法,即可将资源类型附加到根文件夹。
配置
.AddResourceType("Files", resourceBuilder =>
resourceBuilder.SetBackend("default", "/"));
或者,通过将/
值提供给folder
配置选项
<resourceType name="Files" folder="/" backend="default" />
通过以上配置,您将在 CKFinder 中看到以下文件夹树
文件
└── dir1
└── dir2
└── dir3
您可以将资源类型指向任何子文件夹,如下所示
配置
.AddResourceType("Files", resourceBuilder =>
resourceBuilder.SetBackend("default", "/dir1"));
使用folder
选项
<resourceType name="Files" folder="/dir1" backend="default" />
或指向更深的子文件夹
配置
.AddResourceType("Files", resourceBuilder =>
resourceBuilder.SetBackend("default", "/dir1/dir2"));
<resourceType name="Files" folder="/dir1/dir2" backend="default" />
在不转换为应用程序或在 WebMatrix 中使用 Zip 包
在不转换为应用程序或在 WebMatrix 中使用.zip
包的最简单方法是将.zip
存档内容提取到一个空的站点中,并省略根ckfinder
文件夹。
接下来打开Web.config
文件并更改
<add key="ckfinderRoute" value="/connector" />
到
<add key="ckfinderRoute" value="/ckfinder/connector" />
在<appSettings />
部分中。
CKFinder 和 Classic ASP
CKFinder 3.x 不支持 Classic ASP,但是,可以通过自定义的Authenticator类和经典 ASP 应用程序中的附加 ASP 脚本,获取 CKFinder 的用户身份验证数据。
附加的 ASP 脚本应返回包含用户身份验证数据的 JSON 数据。它应该放在一个公开可见的位置。它可能看起来像这样
<%@Language=VBScript CodePage=65001%>
<% Option Explicit %>
<%
' 出于安全原因,只允许本地请求。
' 如果需要远程请求,则应使用某种密钥或证书。
If Request.ServerVariables("LOCAL_ADDR") <> Request.ServerVariables("REMOTE_ADDR") Then
Response.Status = "403 Forbidden"
Response.End
End If
Dim isAuthenticated
Dim roles
' 如果用户被允许访问 CKFinder,则将 True 分配给 isAuthenticated。
isAuthenticated = False
' 将用户角色分配给 roles 数组。
' 例如
' roles = Array("Administrator", "Manager")
Dim quotedRoles
ReDim quotedRoles(uBound(roles))
Dim role
Dim index
index = 0
For Each role In roles
quotedRoles(index) = """" & role & """"
index = index + 1
Next
Response.ContentType = "application/json"
Response.Charset = "utf-8"
Response.Write "{ ""isAuthenticated"": "
Response.Write """" & isAuthenticated & """"
Response.Write ", ""roles"": [ "
Response.Write Join(quotedRoles, ", ")
Response.Write " ] }"
%>
自定义的Authenticator类可能看起来像这样
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public class ClassicAspAuthenticator : IAuthenticator
{
private readonly string _classicAspConnectorUrl;
public ClassicAspAuthenticator(string classicAspConnectorUrl)
{
_classicAspConnectorUrl = classicAspConnectorUrl;
}
public async Task<IUser> AuthenticateAsync(ICommandRequest commandRequest, CancellationToken cancellationToken)
{
var httpClient = new HttpClient();
var response = await httpClient.GetAsync(_classicAspConnectorUrl, cancellationToken);
var json = await response.Content.ReadAsStringAsync();
return json.FromJson<User>();
}
}
最后一步是在ConnectorBuilder.SetAuthenticator方法中传递ClassicAspAuthenticator
实例
var authenticator = new ClassicAspAuthenticator("http://url/to/the/additional/classic/asp/script.asp");
var connectorBuilder = new ConnectorBuilder();
connectorBuilder.SetAuthenticator(authenticator);
混合多个 Owin 中间件
当您想将 CKFinder 中间件与其他中间件混合使用时,您可以通过路由映射来实现。
public void Configuration(IAppBuilder appBuilder)
{
var connectorBuilder = ConfigureConnector();
var connector = connectorBuilder.Build(new OwinConnectorFactory());
appBuilder.Map("/CKFinder/connector", builder => builder.UseConnector(connector));
appBuilder.Map("/anotherMiddleware", builder => builder.UseAnotherMiddleware());
}
有关 Owin 路由映射的更多信息,请参阅MSDN 上的 AppBuilder 类参考。
有关与现有应用程序集成的更多信息,请参阅在现有应用程序中集成。
添加对自定义存储的支持
可以通过实现IFileSystem接口,添加对自定义文件系统的支持。
此接口的大多数成员是不言自明的,但是有四个方法需要一些额外的说明
Task<FolderListResult> GetFolderInfosAsync(string path, CancellationToken cancellationToken);
Task<FolderListResult> GetFolderInfosAsync(IFolderListContinuation folderListContinuation, CancellationToken cancellationToken);
Task<FileListResult> GetFileInfosAsync(string path, CancellationToken cancellationToken);
Task<FileListResult> GetFileInfosAsync(IFileListContinuation fileListContinuation, CancellationToken cancellationToken);
这四个成员负责列出文件夹和文件。假设使用路径作为参数的调用始终是第一个请求,后续调用使用延续对象。这些延续对象是游标,应该由文件系统的实现内部处理。
示例适配器支持在数据库中存储。
出于本教程的目的,让我们假设文件将存储在一个数据库表中,该表由下面显示的 SQL 模式表示
SQL Server
CREATE TABLE [dbo].[DatabaseNodes](
[Id] [int] IDENTITY(1,1) NOT NULL,
[Path] [nvarchar](max) NULL,
[Type] [int] NOT NULL,
[Contents] [varbinary](max) NULL,
[Size] [int] NOT NULL,
[MimeType] [nvarchar](max) NULL,
[Timestamp] [datetime] NOT NULL,
CONSTRAINT [PK_dbo.DatabaseNodes] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
实现自定义存储
在 CKFinder 3 ASP.NET 连接器中添加自定义存储的第一步是创建IFileSystem的实现。此接口定义了与给定文件系统通信所需的所有方法——例如写入、读取或删除文件。
看看IFileSystem的自定义实现,它需要将文件保存到具有假设模式的数据库表中。DatabaseStorage
类使用EntityFramework
与数据库通信。下面介绍DatabaseStorage
类的实例化。
var databaseStorage = new DatabaseStorage("Server=myServerAddress;Database=myDataBase;User Id=myUsername;Password=myPassword;");
为<tt>web.config</tt>配置注册自定义适配器
要为静态web.config
配置注册自定义适配器,您必须定义如何创建此适配器。这在静态FileSystemFactory类中完成。对于DatabaseStorage
类,它只需要连接字符串参数,这可以通过以下示例完成
FileSystemFactory.RegisterFileSystem("local", options => new DatabaseStorage(options["connectionString"]));
请参阅DatabaseStorage 示例,了解实现此功能的完整源代码。
每个请求设置许可证详细信息
要按请求设置许可证详细信息,需要在代码中动态更改连接器配置(请参阅通过代码进行配置)。
许可证详细信息可以在传递给connectorBuilder.SetRequestConfiguration()
方法的回调中按请求更改,如以下示例所示
var connector = connectorBuilder
.LoadConfig()
.SetRequestConfiguration(
(request, config) =>
{
config.LoadConfig();
connectorBuilder.licenseProvider.SetLicense(licenseName, licenseKey);
})
.Build(connectorFactory);
在 Amazon S3 适配器中定义自定义 S3 客户端
要为 Amazon S3 适配器定义自定义 S3 客户端,请扩展默认的IFileSystem类并覆盖createClient()
工厂方法,如下所示。
public class CustomS3Storage : AmazonStorage
{
public CustomS3Storage() : base("bucket-name")
{
}
protected override AmazonS3Client createClient()
{
BasicAWSCredentials credentials = new BasicAWSCredentials("key", "secret");
AmazonS3Config config = new AmazonS3Config();
config.RegionEndpoint = RegionEndpoint.GetBySystemName("region-name");
config.SignatureVersion = "4";
return new AmazonS3Client(credentials, config);
}
}
然后您可以在连接器中注册新的存储类型
connectorBuilder
.LoadConfig()
.SetRequestConfiguration(
(request, config) =>
{
config.LoadConfig();
config.AddBackend("s3", new CustomS3Storage());
config.AddResourceType("S3 Resorce Type", resourceBuilder => {
resourceBuilder.SetBackend("s3", "");
resourceBuilder.SetLazyLoaded(true);
});
}
保护公开访问的文件夹
在集成 CKFinder 时,您通常希望让用户访问上传的文件,以便他们可以将图像或文件链接插入编辑的内容中。 这可以通过两种方式完成
- 您可以将 CKFinder 配置为使用 Proxy 命令通过连接器提供所有文件。
- 您可以使该文件夹公开访问,以便所有文件都通过 Web 服务器提供。
如果您依赖 Web 服务器来提供使用 CKFinder 上传的文件,则应采取其他步骤来确保以安全的方式提供这些文件。
假设您已将 CKFinder 配置为允许上传 .avi
文件。
即使 .avi
文件随后使用有效的 Content-Type: video/x-msvideo
标头提供,某些浏览器也可能忽略此信息并对原始文件内容执行其他检查。 如果在文件内容中检测到任何类似 HTML 的数据,浏览器可能会决定忽略有关内容类型的信息,并将提供的内容视为普通网页。 此行为称为 "内容嗅探"(也称为“媒体类型嗅探”或“MIME 嗅探”),在某些情况下,它可能导致安全问题(例如,它可能为 跨站点脚本 (XSS) 攻击 打开大门)。
为了避免内容嗅探,您应该确保您的服务器在从公开可用的文件夹提供文件时,将 X-Content-Type-Options: nosniff
标头添加到所有 HTTP 响应中。 X-Content-Type-Options
响应 HTTP 标头是服务器使用的标记,用于指示 Content-Type
标头设置的 MIME 类型不应更改,并且应遵循。 因此,浏览器不会对接收到的内容执行任何内容嗅探。
Microsoft IIS
对于 Microsoft IIS 服务器,您可以在 web.config
文件中启用 X-Content-Type-Options
标头
<system.webServer>
<httpProtocol>
<customHeaders>
<remove name="X-Content-Type-Options"/>
<add name="X-Content-Type-Options" value="nosniff"/>
</customHeaders>
</httpProtocol>
</system.webServer>
Apache
如果您使用 Apache Web 服务器,可以使用 mod_headers
添加自定义 HTTP 响应标头。 确保 mod_headers
模块已启用,并在公开可访问的文件夹的根目录中创建(或修改)以下 .htaccess
文件(例如 userfiles/.htaccess
)
Header set X-Content-Type-Options "nosniff"
Nginx
如果您使用 Nginx,可以为每个位置定义自定义 HTTP 响应标头
location /userfiles {
add_header X-Content-Type-Options nosniff;
}