在 CKFinder v3 中将网站成员验证为用户

Authenticating Website Members as Users in CKFinder v3

在开始这个问题之前,我应该指出我对 ASP.NET 和 C# 的了解几乎为零。

我正在尝试将 ASP.NET 版本的 CKFinder v3 集成到以不同语言构建的站点中,目前一切顺利;我已经按照我的意愿进行了所有设置,并且当我授予对 CKF 的无限制访问权限时它正在工作,但我现在停留在试图通过仅验证我网站的某些成员来使用它来限制对它的访问。 CKFinder 出现在我网站上的所有页面只能由那些特定的成员访问,但我需要额外的安全级别,例如,如果有人找出我的 "ckfinder.html" 文件的直接路径。

在 CKFinder 的 ASP 版本中,我只是在检查我的成员权限的函数中添加了这一行,其中 isEditor 是一个布尔值,其值是根据我的信息为每个成员分配的数据库:

session("accessckf")=isEditor

然后在 CKFinder 的 "config.asp" 文件中编辑 CheckAuthentication() 函数以读取:

function CheckAuthentication()
    CheckAuthentication=session("accessckf")
end function

通读 this "Howto", authentication seems to be much more complex in v3 but, after and ,我创建了这个 C# 文件,它位于我的 CKF 目录中:

<%@page codepage="65001" debug="true" language="c#" lcid="6153"%>
<%@import namespace="CKSource.CKFinder.Connector.Core"%>
<%@import namespace="CKSource.CKFinder.Connector.Core.Authentication"%>
<%@import namespace="CKSource.CKFinder.Connector.Core.Builders"%>
<%@import namespace="CKSource.CKFinder.Connector.Host.Owin"%>
<%@import namespace="Owin"%>
<%@import namespace="System.Data.Odbc"%>
<%@import namespace="System.Threading"%>
<%@import namespace="System.Threading.Tasks"%>
<script runat="server">
    public void Configuration(IAppBuilder appBuilder){
        var connectorBuilder=ConfigureConnector();
        var connector=connectorBuilder.Build(new OwinConnectorFactory());
        appBuilder.Map("/path/to/connector",builder=>builder.UseConnector(connector));
    }
    public ConnectorBuilder ConfigureConnector(){
        var connectorBuilder=new ConnectorBuilder();
        connectorBuilder.SetAuthenticator(new MyAuthenticator());
        return connectorBuilder;
    }
    public class MyAuthenticator:IAuthenticator{
        public Task<IUser> AuthenticateAsync(ICommandRequest commandRequest,CancellationToken cancellationToken){
            var domain=HttpContext.Current.Request.Url.Host;
            var cookie=HttpContext.Current.Request.Cookies[urlDomain];
            var password="";
            var username="";
            var user=new User(false,null);
            if (cookie!=null){
                if (cookie["username"]!=null)
                    username=cookie["username"];
                if (cookie["password"]!=null)
                    password=cookie["password"];
                if(username!=""&&password!=""){
                    var connection=new OdbcConnection("database=[database];driver=MySQL;pwd=[pwd];server=[server];uid=[uid];");
                    connection.Open();
                    OdbcDataReader records=new OdbcCommand("SELECT ISEDITOR FROM MEMBERS WHERE USERNAME='"+username+"' AND PASSWORD='"+password+"'",connection).ExecuteReader();
                    if(records.HasRows){
                        records.Read();
                        bool isEditor=records.GetString(0)=="1";
                        var roles="member";
                        if(isEditor)
                            roles="editor,member";
                            user=new User(isEditor,roles.Split(','));
                        }
                        records.Close();
                        connection.Close();
                }
            }
            return Task.FromResult((IUser)user);
        }
    }
</script>

加载该页面不会产生任何错误(这并不一定意味着它正在工作,因为出于某种原因,尝试从 public class 中向屏幕写入任何内容都不起作用)所以现在我在以某种方式检查该文件以进行身份​​验证的阶段。

最初,我曾尝试通过 XMLHttp 从我检查站点成员权限的函数中加载它,但是,正如我所怀疑和 Lesmian 所证实的那样,那是行不通的。经过更多的尝试和错误后,我添加了代码来检查网站成员对 C# 文件的权限,这导致我现在的位置:卡住了!

我需要在 CKFinder 中编辑什么才能让它使用此自定义文件来检查用户是否已通过身份验证?

您是否使用 ConnectorBuilder 设置了自定义身份验证提供程序?

public ConnectorBuilder ConfigureConnector()
{
   var connectorBuilder = new ConnectorBuilder();
   connectorBuilder.SetAuthenticator(new MyAuthenticator());

   return connectorBuilder;
}

您可以在此处找到完整示例:http://docs.cksource.com/ckfinder3-net/configuration_by_code.html

更新

此外,您应该在 Startup class 中注册 ConnectorBuilder 以将其添加到请求管道:

public void Configuration(IAppBuilder appBuilder)
{
   var connectorBuilder = ConfigureConnector();
   var connector = connectorBuilder.Build(new OwinConnectorFactory());
   appBuilder.Map("/CKFinder/connector", builder => builder.UseConnector(connector));
}

所有这些都来自我之前提供的文档link。

首先,您需要在 ASP 的会话和 CKFinder 的 .Net 验证器之间建立一个连接器。下面是一个将 ASP 会话内容序列化为 JSON 的示例。

connector.asp 放入可公开访问的位置。 http://myaspwebsite.com/connector.asp 例如。

connector.asp

<%@Language=VBScript CodePage=65001%>
<% Option Explicit %>
<!--#include file="JSON.asp"-->
<%
' obtain JSON.asp from https://github.com/tugrul/aspjson/archive/master.zip

' just for testing, must be removed in the production environment
Session("isEditor") = True
Session("isMember") = True

' only local requests allowed
' instead of local and remote ip comparison, a secret key can be used
If Request.ServerVariables("LOCAL_ADDR") <> Request.ServerVariables("REMOTE_ADDR") Then
    Response.Status = "403 Forbidden"
    Response.End
End If

Response.ContentType = "application/json"
Response.Charset = "utf-8"

Dim JSONObject, Key
Set JSONObject = jsObject()

For Each Key In Session.Contents
    If Not IsObject(Session.Contents(Key)) Then 'skip the objects cannot be serialized
        JSONObject(Key) = Session.Contents(Key)
    End If
Next

JSONObject.Flush
%>

CKFinder 3.3.0 带有默认连接器,可以在 /ckfinder/bin/CKSource.CKFinder.Connector.WebApp.dll 中找到,删除它

检查以下程序并记住用您自己的值替换 builder.Map("/connector", SetupConnector);new Uri("http://myaspwebsite.com/connector.asp");

它只是通过 connector.asp 检查 ASP 会话变量 isEditorisMember 来验证用户,最后声明角色 editormember 或 none.

我假设您已经在 web.config.

中配置了角色 editormember

然后将Shaggy.cs放入/ckfinder/App_Code。如果不存在,则创建 App_Code 目录。此文件夹中的 .Net 文件将即时编译。

有关详细信息,请查看 Shared Code Folders in ASP.NET Web Projects

Shaggy.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Owin.Security;
using Microsoft.Owin.Security.Cookies;
using Newtonsoft.Json.Linq;
using Owin;

[assembly: Microsoft.Owin.OwinStartup(typeof(CKSource.CKFinder.Connector.Shaggy.Startup))]
namespace CKSource.CKFinder.Connector.Shaggy
{
    using FileSystem.Local;
    using FileSystem.Dropbox;
    using Core;
    using Core.Authentication;
    using Config;
    using Core.Builders;
    using Core.Logs;
    using Host.Owin;
    using Logs.NLog;
    using KeyValue.EntityFramework;

    public class Startup
    {
        public void Configuration(IAppBuilder builder)
        {
            LoggerManager.LoggerAdapterFactory = new NLogLoggerAdapterFactory();

            RegisterFileSystems();

            builder.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = "ApplicationCookie",
                AuthenticationMode = AuthenticationMode.Active
            });

            //replace connector path with yours
            builder.Map("/connector", SetupConnector);
        }

        private static void RegisterFileSystems()
        {
            FileSystemFactory.RegisterFileSystem<LocalStorage>();
            FileSystemFactory.RegisterFileSystem<DropboxStorage>();
        }

        private static void SetupConnector(IAppBuilder builder)
        {
            var keyValueStoreProvider = new EntityFrameworkKeyValueStoreProvider("CacheConnectionString");
            var authenticator = new ShaggysAuthenticator();

            var connectorFactory = new OwinConnectorFactory();
            var connectorBuilder = new ConnectorBuilder();
            var connector = connectorBuilder
                .LoadConfig()
                .SetAuthenticator(authenticator)
                .SetRequestConfiguration(
                    (request, config) =>
                    {
                        config.LoadConfig();
                        config.SetKeyValueStoreProvider(keyValueStoreProvider);
                    })
                .Build(connectorFactory);

            builder.UseConnector(connector);
        }
    }

    public class ShaggysAuthenticator : IAuthenticator
    {
        // this method makes an http request on the background to gather ASP's all session contents and returns a JSON object
        // if the request contains ASP's session cookie(s)
        private static JObject GetAspSessionState(ICommandRequest requestContext)
        {
            // building Cookie header with ASP's session cookies
            var aspSessionCookies = string.Join(";",
                requestContext.Cookies.Where(cookie => cookie.Key.StartsWith("ASPSESSIONID"))
                    .Select(cookie => string.Join("=", cookie.Key, cookie.Value)));

            if (aspSessionCookies.Length == 0)
            {
                // logs can be found in /ckfinder/App_Data/logs
                LoggerManager.GetLoggerForCurrentClass().Info("No ASP session cookie found");
                // don't make an extra request to the connector.asp, there's no session initiated
                return new JObject();
            }

            //replace this URL with your connector.asp's
            var publicAspSessionConnectorUrl = new Uri("http://myaspwebsite.com/connector.asp");
            var localSafeAspSessionConnectorUrl = new UriBuilder(publicAspSessionConnectorUrl) { Host = requestContext.LocalIpAddress };

            using (var wCli = new WebClient())
                try
                {
                    wCli.Headers.Add(HttpRequestHeader.Cookie, aspSessionCookies);
                    wCli.Headers.Add(HttpRequestHeader.Host, publicAspSessionConnectorUrl.Host);
                    return JObject.Parse(wCli.DownloadString(localSafeAspSessionConnectorUrl.Uri));
                }
                catch (Exception ex) // returning an empty JObject object in any fault
                {
                    // logs can be found in /ckfinder/App_Data/logs
                    LoggerManager.GetLoggerForCurrentClass().Error(ex);
                    return new JObject();
                }
        }

        public Task<IUser> AuthenticateAsync(ICommandRequest commandRequest, CancellationToken cancellationToken)
        {
            var aspSessionState = GetAspSessionState(commandRequest);

            var roles = new List<string>();
            var isEditor = aspSessionState.GetNullSafeValue("isEditor", false);
            var isMember = aspSessionState.GetNullSafeValue("isMember", false);

            if (isEditor) roles.Add("editor");
            if (isMember) roles.Add("member");

            var isAuthenticated = isEditor || isMember;
            var user = new User(isAuthenticated, roles);
            return Task.FromResult((IUser)user);
        }
    }

    public static class JObjectExtensions
    {
        // an extension method to help case insensitive lookups with a default value to get avoid NullReferenceException
        public static T GetNullSafeValue<T>(this JObject jobj, string key, T defaultValue = default(T))
        {
            dynamic val = jobj.GetValue(key, StringComparison.OrdinalIgnoreCase);
            if (val == null) return defaultValue;
            return (T)val;
        }
    }
}

现在您应该有一个工作的 CKFinder 连接器。如果需要,请更改方法 AuthenticateAsync 中的逻辑,看看 CKFinder 如何处理您的经典 ASP 会员管理。