Add the SecureSSLRedirector plugin (#306)

This commit is contained in:
An Tao 2019-11-30 08:35:40 +08:00 committed by GitHub
parent 2784a91dcb
commit 5c1c73d9b6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 194 additions and 13 deletions

View File

@ -4,12 +4,6 @@ project(drogon CXX)
add_library(${PROJECT_NAME} STATIC)
#if(MSVC)
# target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX)
#else()
# target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wextra -pedantic)
#endif()
set(DROGON_MAJOR_VERSION 1)
set(DROGON_MINOR_VERSION 0)
set(DROGON_PATCH_VERSION 0)
@ -143,6 +137,7 @@ set(DROGON_SOURCES
lib/src/MultiPart.cc
lib/src/NotFound.cc
lib/src/PluginsManager.cc
lib/src/SecureSSLRedirector.cc
lib/src/SessionManager.cc
lib/src/SharedLibManager.cc
lib/src/StaticFileRouter.cc
@ -281,7 +276,8 @@ endif()
target_sources(${PROJECT_NAME} PRIVATE ${DROGON_SOURCES})
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD ${DROGON_CXX_STANDARD})
set_target_properties(${PROJECT_NAME}
PROPERTIES CXX_STANDARD ${DROGON_CXX_STANDARD})
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_STANDARD_REQUIRED ON)
set_target_properties(${PROJECT_NAME} PROPERTIES CXX_EXTENSIONS OFF)
set_target_properties(${PROJECT_NAME} PROPERTIES EXPORT_NAME Drogon)
@ -414,7 +410,8 @@ set(DROGON_UTIL_HEADERS
install(FILES ${DROGON_UTIL_HEADERS}
DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/utils)
set(DROGON_PLUGIN_HEADERS lib/inc/drogon/plugins/Plugin.h)
set(DROGON_PLUGIN_HEADERS lib/inc/drogon/plugins/Plugin.h
lib/inc/drogon/plugins/SecureSSLRedirector.h)
install(FILES ${DROGON_PLUGIN_HEADERS}
DESTINATION ${INSTALL_INCLUDE_DIR}/drogon/plugins)

View File

@ -184,13 +184,14 @@
//plugins: Define all plugins running in the application
"plugins": [{
//name: The class name of the plugin
//"name": "TestPlugin",
//"name": "drogon::plugin::SecureSSLRedirector",
//dependencies: Plugins that the plugin depends on. It can be commented out
"dependencies": [],
//config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
//It can be commented out
"config": {
"heartbeat_interval": 2
"ssl_redirect_exempt": [".*\\.jpg"],
"secure_ssl_host": "localhost:8849"
}
}],
//custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.

View File

@ -184,13 +184,14 @@
//plugins: Define all plugins running in the application
"plugins": [{
//name: The class name of the plugin
//"name": "TestPlugin",
//"name": "drogon::plugin::SecureSSLRedirector",
//dependencies: Plugins that the plugin depends on. It can be commented out
"dependencies": [],
//config: The configuration of the plugin. This json object is the parameter to initialize the plugin.
//It can be commented out
"config": {
"heartbeat_interval": 2
"ssl_redirect_exempt": [".*\\.jpg"],
"secure_ssl_host": "localhost:8849"
}
}],
//custom_config: custom configuration for users. This object can be get by the app().getCustomConfig() method.

View File

@ -359,6 +359,8 @@ class HttpRequest
return toRequest(std::forward<T>(obj));
}
virtual bool isOnSecureConnection() const noexcept = 0;
virtual ~HttpRequest()
{
}

View File

@ -27,6 +27,7 @@
#include <drogon/utils/Utilities.h>
#include <drogon/MultiPart.h>
#include <drogon/plugins/Plugin.h>
#include <drogon/plugins/SecureSSLRedirector.h>
#include <drogon/Cookie.h>
#include <drogon/Session.h>
#include <drogon/IOThreadStorage.h>

View File

@ -22,6 +22,8 @@ namespace drogon
{
class HttpResponse;
using HttpResponsePtr = std::shared_ptr<HttpResponse>;
class HttpRequest;
using HttpRequestPtr = std::shared_ptr<HttpRequest>;
using AdviceCallback = std::function<void(const HttpResponsePtr &)>;
using AdviceChainCallback = std::function<void()>;
using FilterCallback = std::function<void(const HttpResponsePtr &)>;

View File

@ -0,0 +1,67 @@
/**
*
* drogon_plugin_SecureSSLRedirector.h
*
*/
#pragma once
#include <drogon/drogon_callbacks.h>
#include <drogon/plugins/Plugin.h>
#include <regex>
namespace drogon
{
namespace plugin
{
/**
* @brief This plugin is used to redirect all non-HTTPS requests to HTTPS
* (except for those URLs matching a regular expression listed in
* the 'ssl_redirect_exempt' list).
*
* The json configuration is as follows:
*
* @code
{
"name": "drogon::plugin::SecureSSLRedirector",
"dependencies": [],
"config": {
"ssl_redirect_exempt": ["^/.*\\.jpg", ...],
"secure_ssl_host": "localhost:8849"
}
}
@endcode
*
* ssl_redirect_exempt: a regular expression (for matching the path of a
* request) list for URLs that don't have to be redirected.
* secure_ssl_host: If this string is not empty, all SSL redirects
* will be directed to this host rather than the originally-requested host.
*
* Enable the plugin by adding the configuration to the list of plugins in the
* configuration file.
*
*/
class SecureSSLRedirector : public drogon::Plugin<SecureSSLRedirector>
{
public:
SecureSSLRedirector()
{
}
/// This method must be called by drogon to initialize and start the plugin.
/// It must be implemented by the user.
virtual void initAndStart(const Json::Value &config) override;
/// This method must be called by drogon to shutdown the plugin.
/// It must be implemented by the user.
virtual void shutdown() override;
private:
HttpResponsePtr redirectingAdvice(const HttpRequestPtr &) const;
HttpResponsePtr redirectToSSL(const HttpRequestPtr &) const;
std::regex exemptPegex_;
bool regexFlag_{false};
std::string secureHost_;
};
} // namespace plugin
} // namespace drogon

View File

@ -85,6 +85,10 @@ class HttpRequestImpl : public HttpRequest
}
bool setMethod(const char *start, const char *end);
void setSecure(bool secure)
{
isOnSecureConnection_ = secure;
}
virtual void setMethod(const HttpMethod method) override
{
@ -404,6 +408,11 @@ class HttpRequestImpl : public HttpRequest
{
return keepAlive_;
}
virtual bool isOnSecureConnection() const noexcept override
{
return isOnSecureConnection_;
}
~HttpRequestImpl();
protected:
@ -450,6 +459,7 @@ class HttpRequestImpl : public HttpRequest
std::unique_ptr<CacheFile> cacheFilePtr_;
std::string expect_;
bool keepAlive_{true};
bool isOnSecureConnection_{false};
protected:
std::string content_;

View File

@ -203,6 +203,8 @@ void HttpServer::onMessage(const TcpConnectionPtr &conn, MsgBuffer *buf)
requestParser->requestImpl()->setLocalAddr(conn->localAddr());
requestParser->requestImpl()->setCreationDate(
trantor::Date::date());
requestParser->requestImpl()->setSecure(
conn->isSSLConnection());
if (requestParser->firstReq() &&
isWebSocket(requestParser->requestImpl()))
{

View File

@ -0,0 +1,98 @@
/**
*
* drogon_plugin_SecureSSLRedirector.cc
*
*/
#include <drogon/drogon.h>
#include <drogon/plugins/SecureSSLRedirector.h>
#include <string>
using namespace drogon;
using namespace drogon::plugin;
void SecureSSLRedirector::initAndStart(const Json::Value &config)
{
if (config.isMember("ssl_redirect_exempt") &&
config["ssl_redirect_exempt"].isArray())
{
std::string regexString;
for (auto &exempt : config["ssl_redirect_exempt"])
{
assert(exempt.isString());
regexString.append("(").append(exempt.asString()).append(")|");
}
if (!regexString.empty())
{
regexString.resize(regexString.length() - 1);
exemptPegex_ = std::regex(regexString);
regexFlag_ = true;
}
}
secureHost_ = config.get("secure_ssl_host", "").asString();
app().registerSyncAdvice([this](const HttpRequestPtr &req) {
return this->redirectingAdvice(req);
});
}
void SecureSSLRedirector::shutdown()
{
/// Shutdown the plugin
}
HttpResponsePtr SecureSSLRedirector::redirectingAdvice(
const HttpRequestPtr &req) const
{
if (req->isOnSecureConnection())
{
return HttpResponsePtr{};
}
else if (regexFlag_)
{
std::smatch regexResult;
if (std::regex_match(req->path(), regexResult, exemptPegex_))
{
return HttpResponsePtr{};
}
else
{
return redirectToSSL(req);
}
}
else
{
return redirectToSSL(req);
}
}
HttpResponsePtr SecureSSLRedirector::redirectToSSL(
const HttpRequestPtr &req) const
{
if (!secureHost_.empty())
{
static std::string urlPrefix{"https://" + secureHost_};
std::string query{urlPrefix + req->path()};
if (!req->query().empty())
{
query += "?" + req->query();
}
return HttpResponse::newRedirectionResponse(query);
}
else
{
const auto &host = req->getHeader("host");
if (!host.empty())
{
std::string query{"https://" + host};
query += req->path();
if (!req->query().empty())
{
query += "?" + req->query();
}
return HttpResponse::newRedirectionResponse(query);
}
else
{
return HttpResponse::newNotFoundResponse();
}
}
}

@ -1 +1 @@
Subproject commit 374acaa8aa39aa7a1a2c2d92dbb2c0d940e0f33b
Subproject commit f36e2e46a7bfa404e730ec36cf223c75f6fbe34a