在本文中,您将学习如何使用 Bluemix® Node.js 应用程序访问 Google reCAPTCHA 服务。这样做可确保应用程序的用户是人类,而不只是一个自动化系统。这个要求可以防止从依赖于这样的系统发送大量请求的许多攻击。
运行应用程序
获取代码
“ 一些攻击使用自动化系统生成大量请求。在本文中,您将学习如何使用 Google reCAPTCHA 服务确保某个人类处于保护圈内。 ”
许多攻击都让目标系统对大量请求作出响应。编写一个程序来发出这种请求非常简单且廉价。但是,在使用 reCAPTCHA(或其他任何 CAPTCHA 变体)时,请求必须包含某个问题的解决方案,对于人类,这个问题很容易解答,但计算机却很难解答这个问题。该方法将非常廉价的自动攻击转换成了需要大量枯燥的、昂贵的人类参与的攻击。
任何允许用户彼此交互的服务都可以用来发送令人厌烦的广告(也称为垃圾邮件)。大多数管理员都会通过删除违规帐户来回应对垃圾邮件的指责。从垃圾邮件发送者的角度来看,一种解决方案是创建大量的一次性帐户。
如果帐户注册需要人类的参与,那么创建这些一次性帐户就会变得更困难、更昂贵。这通常导致垃圾邮件发送者寻找更容易的受害者。
拒绝服务攻击可能是微不足道的破坏行为。当使用拒绝服务来迫使人们通过安全性较低的其他系统来完成他们的工作时,拒绝服务也有可能是一个更严重的攻击。大部分拒绝服务攻击都基于简单地发送大量请求并覆盖系统。在 Bluemix 基础架构中,不太可能出现这种情况,但大量请求可能会增加应用程序的成本。
不过,如果发出一个需要人类参与的请求,从经济角度讲,发送大量无意义的请求不再可行。
破解密码的一种方法是按照可能性顺序尝试所有可能的值(从字典单词开始,然后使用那些用零取代了 “O” 的字典单词继续尝试,以此类推)。一个常见的解决方法是在输入一定数量的错误密码后阻止进一步尝试,但这为攻击者提供了针对帐户的轻松的拒绝服务攻击。他们总是能够通过输入少量的密码来锁定帐户。
通过要求某个人立即进入保护圈,或者在一定数量的密码尝试失败后进入保护圈,让密码枚举变得太麻烦而不值得尝试。
要使用 reCAPTCHA,需要在 Google 的网站上激活它,然后修改您的程序来调用它。
在 https://www.google.com/recaptcha/admin#list 上向 Google reCAPTCHA 服务注册您的应用程序。为了避免出现问题,请注册完整的域名。例如,对于我的应用程序,我注册了 recaptcha.mybluemix.net
。
Google 提供了两个密钥:一个站点密钥用于您的 HTML 中,另一个密钥用于服务器上的应用程序,以便与 Google 进行通信。
要在一个表单中向用户显示 reCAPTCHA 提示,请按照下列步骤操作:
<script src='https://www.google.com/recaptcha/api.js'></script>
<div class="g-recaptcha" data-sitekey="--- your site key goes here –--"></div>
要查看示例,请转到 http://recaptcha.mybluemix.net/index.html 并查看源代码。
要使用 reCAPTCHA,请按照下列步骤来修改运行在服务器上的应用程序 (app.js):
// Use body-parser to receive POST form requests and JSON var bodyParser = require("body-parser"); app.use(bodyParser.urlencoded({extended:true}));
// An attempt to log on app.post("/login", function(req, res) { . . . var httpsReq = https.request( <<URL goes here >>, function(httpsRes) { . . . }); httpsReq.on("error", function(err) { res.send("Error: " + JSON.stringify(err)); }); httpsReq.end(); });
secret
和 response
,前者包含密钥,后者包含由浏览器中的 reCAPTCHA 代码返回的值。该值是在一个 POST 的主体中提供的。 var httpsReq = https.request("https://www.google.com/recaptcha/api/siteverify" + "?secret=<<your secret goes here>>" + "&response=" + req.body["g-recaptcha-response"],
var googleResponse = ""; . . . httpsRes.on("data", function (chunk) { googleResponse += chunk; });
JSON.parse()
解析它。该参数会告诉您,用户被认为是人类是否是 success
,该参数是一个布尔值。 httpsRes.on("end", function() { var human = JSON.parse(googleResponse).success; res.send(human); });
要查看 reCAPTCHA 服务是否正常工作,可执行一次提交,并查看结果是否为 true。然后,等待几秒钟并重新加载,确认该表单提交。当请求来自自动化系统时,就会出现再次使用 g-recaptcha-response
值的情况。试图攻击应用程序的程序员可以手动创建有效值一次,然后通过编程进行攻击,在每次提交时都自动发送该值。为了防止这种攻击,从 Google 到以前使用过的值的响应为 false。
Bluemix 有一个单点登录服务。我们可能想要对特定的应用程序使用该服务,但我们无法同等信任所有的身份来源。我们担心一些身份来源容易受到自动攻击,因此我们希望在使用身份来源时执行一次 reCAPTCHA 认证。但我们并不想对我们信任的身份来源执行此操作。
以下这些是配置单点登录服务的步骤:
recaptcha-sso
。 https://recaptcha-sso-aj5bgntzl9-ck11.iam.ibmcloud.com/idaas/mtfim/sps/idaas/login/facebook/callback
https://recaptcha-sso-aj5bgntzl9-ck11.iam.ibmcloud.com/idaas/mtfim/sps/idaas/login/google/callback
要使用身份认证服务,请按照下列步骤进行操作:
/sso
是一个合理选择。 https://recaptcha.mybluemix.net/sso
"dependencies": { . . . "passport": "*", "cookie-parser": "*", "express-session": "*", "passport-idaas-openidconnect": "*" }
var passport = require('passport'); var cookieParser = require('cookie-parser'); var session = require('express-session'); app.use(cookieParser()); app.use(session({resave: 'true', saveUninitialized: 'true' , secret: 'keyboard cat'})); app.use(passport.initialize()); app.use(passport.session()); passport.serializeUser(function(user, done) { done(null, user); }); passport.deserializeUser(function(obj, done) { done(null, obj); }); // VCAP_SERVICES contains all the credentials of services bound to // this application. For details of its content, please refer to // the document or sample of each service. var services = JSON.parse(process.env.VCAP_SERVICES || "{}"); var ssoConfig = services.SingleSignOn[0]; var client_id = ssoConfig.credentials.clientId; var client_secret = ssoConfig.credentials.secret; var authorization_url = ssoConfig.credentials.authorizationEndpointUrl; var token_url = ssoConfig.credentials.tokenEndpointUrl; var issuer_id = ssoConfig.credentials.issuerIdentifier; var callback_url = appEnv.url + "/sso"; var OpenIDConnectStrategy = require('passport-idaas-openidconnect').IDaaSOIDCStrategy; var Strategy = new OpenIDConnectStrategy({ authorizationURL : authorization_url, tokenURL : token_url, clientID : client_id, scope: 'openid', response_type: 'code', clientSecret : client_secret, callbackURL : callback_url, skipUserProfile: true, issuer: issuer_id}, function(iss, sub, profile, accessToken, refreshToken, params, done) { process.nextTick(function() { profile.accessToken = accessToken; profile.refreshToken = refreshToken; done(null, profile); }) }); passport.use(Strategy); app.get('/sso-login', passport.authenticate('openidconnect', {}));
ensureAuthenticated()
函数作为中间件来执行身份验证。在此应用程序中,我选择将所有需要身份验证的 URL 都放在 /auth
下。 function ensureAuthenticated(req, res, next) { if(!req.isAuthenticated()) { req.session.originalUrl = req.originalUrl; res.redirect('/sso-login'); } else { return next(); } } app.use("/auth/*", ensureAuthenticated);
/auth/test
)来进行登录。请注意,您可以登录,但会被重定向到您的应用程序上的 /sso
,它目前还没有处理程序。 Try to access an authenticated path such as /auth/test
to /sso
创建一个处理程序。 app.get('/sso',function(req,res,next) { var redirect_url = req.session.originalUrl; passport.authenticate('openidconnect', { successRedirect: redirect_url, failureRedirect: '/failure', })(req,res,next); });
app.get('/failure', function(req, res) { res.send('login failed'); });
/auth
下创建一条路径。会话信息(比如用户身份)可从 req.session 获得。 app.get('/auth/whoami', function(req, res) { res.send(JSON.stringify(req.session)); });要查看示例应用程序中的用户信息处理程序,请单击 此处 。
/auth/whoami
处理程序来避免泄露信息。我在本文的应用程序中保留它是为了演示目的。 passport.user.id
:唯一用户标识符 passport.user._json.realmName
:用户的来源 passport.user._json.displayName
:用户的名称 最后,创建一个欢迎页面。我发现最简单的方法是拥有一些页面(大部分都是静态页面),并在一个单独的 JavaScript “文件” 中提供所有特定参数。
ensureAuthenticated
处理程序,在用户是经过身份验证的用户时重定向到不同的页面,但 realmName
是 www.facebook.com(这意味着您需要使用 reCAPTCHA),而且 recaptcha
不是 true。新的代码行是第 6 到 10 行。 function ensureAuthenticated(req, res, next) { if(!req.isAuthenticated()) { req.session.originalUrl = req.originalUrl; res.redirect('/login-sso'); } else { if ((req.session.passport.user._json.realmName === "www.facebook.com") && !req.session.recaptcha) { res.session.orginalUrl = req.originalUrl; res.redirect("/get-recaptcha.html"); } else return next(); } }
data-callback
属性调用一个函数。 <form action="recaptcha-res" id="recap" role="form" method="post" class="form-inline"> <div class="g-recaptcha" data-sitekey="6LccjhcTAAAAAG-BsW-f0v9P91oWiin2sZHfxSaK" data-callback="verified" > </div> </form>调用的函数将会提交表单,将 reCAPTCHA 结果发送到服务器。
<script> var verified = function() { document.getElementById("recap").submit(); }; </script>
httpsRes.on("end", function() { var human = JSON.parse(googleResponse).success; if (human) { // Set the reCAPTCHA value to true req.session.recaptcha = true; // Redirect to where the user wanted to go res.redirect(req.session.originalUrl); } });
/auth
。 ensureAuthenticated()
的代码行的后面添加一行代码,以便为需要身份验证的静态文件提供服务。新添加的代码行是第 3 行。 // Serve files that require authentication, only if authenticated app.use("/auth/*", ensureAuthenticated); app.use("/auth", express.static(__dirname + '/auth'));
app.get("/auth/session.js", function(req, res) { res.send("// User data/n" + "var displayName = '" + req.session.passport.user._json.displayName + "';/n" + "var realmName = '" + req.session.passport.user._json.realmName + "';/n" ); });
<script src="session.js"></script> <script> var myApp = angular.module("myApp", []); var scope; myApp.controller("myCtrl", function($scope) { // Make the scope available outside the controller, which is very useful for // debugging scope = $scope; // Provide the session parameters in the scope $scope.displayName = displayName; $scope.realmName = realmName; }); </script> </head> <body> <h2>Hello {{displayName}}</h2> From {{realmName}} </body>
您现在应该可以在您的 Bluemix Node.js 应用程序中使用 reCAPTCHA,不管这些应用程序是否提供了 Bluemix 单点登录服务。请注意,在使用单点登录服务将应用程序发布到生成环境中之前,还必须执行几个步骤:
keyboard cat
更改为确实非常机密的不同字符串。 BLUEMIX SERVICES USED IN THIS TUTORIAL: