转载

将 Google reCAPTCHA 添加到您的 Bluemix Node.js 应用程序

在本文中,您将学习如何使用 Bluemix® Node.js 应用程序访问 Google reCAPTCHA 服务。这样做可确保应用程序的用户是人类,而不只是一个自动化系统。这个要求可以防止从依赖于这样的系统发送大量请求的许多攻击。

构建您的应用程序需要做的准备工作

  • 一个Bluemix 帐户。
  • 一个 Facebook 帐户。
  • 一个 Google 帐户。
  • 了解 HTML 和 JavaScript。
  • 了解 MEAN 应用程序堆栈(至少了解 Node.js 和 Express)。(如果您不熟悉 MEAN,可以从 " 使用 Bluemix 和 MEAN 堆栈构建自动发表的 Facebook 应用程序 " 开始了解它,这是 IBM developerWorks® 上的一篇由三部分组成的文章。)
  • 一个开发环境,可用来将 Node.js 应用程序上传到 Bluemix,比如 Eclipse 。

运行应用程序

获取代码

“ 一些攻击使用自动化系统生成大量请求。在本文中,您将学习如何使用 Google reCAPTCHA 服务确保某个人类处于保护圈内。 ”

为何使用 reCAPTCHA?

许多攻击都让目标系统对大量请求作出响应。编写一个程序来发出这种请求非常简单且廉价。但是,在使用 reCAPTCHA(或其他任何 CAPTCHA 变体)时,请求必须包含某个问题的解决方案,对于人类,这个问题很容易解答,但计算机却很难解答这个问题。该方法将非常廉价的自动攻击转换成了需要大量枯燥的、昂贵的人类参与的攻击。

垃圾邮件

任何允许用户彼此交互的服务都可以用来发送令人厌烦的广告(也称为垃圾邮件)。大多数管理员都会通过删除违规帐户来回应对垃圾邮件的指责。从垃圾邮件发送者的角度来看,一种解决方案是创建大量的一次性帐户。

如果帐户注册需要人类的参与,那么创建这些一次性帐户就会变得更困难、更昂贵。这通常导致垃圾邮件发送者寻找更容易的受害者。

拒绝服务攻击

拒绝服务攻击可能是微不足道的破坏行为。当使用拒绝服务来迫使人们通过安全性较低的其他系统来完成他们的工作时,拒绝服务也有可能是一个更严重的攻击。大部分拒绝服务攻击都基于简单地发送大量请求并覆盖系统。在 Bluemix 基础架构中,不太可能出现这种情况,但大量请求可能会增加应用程序的成本。

不过,如果发出一个需要人类参与的请求,从经济角度讲,发送大量无意义的请求不再可行。

密码枚举

破解密码的一种方法是按照可能性顺序尝试所有可能的值(从字典单词开始,然后使用那些用零取代了 “O” 的字典单词继续尝试,以此类推)。一个常见的解决方法是在输入一定数量的错误密码后阻止进一步尝试,但这为攻击者提供了针对帐户的轻松的拒绝服务攻击。他们总是能够通过输入少量的密码来锁定帐户。

通过要求某个人立即进入保护圈,或者在一定数量的密码尝试失败后进入保护圈,让密码枚举变得太麻烦而不值得尝试。

如何使用 reCAPTCHA

要使用 reCAPTCHA,需要在 Google 的网站上激活它,然后修改您的程序来调用它。

Google 端的配置

在 https://www.google.com/recaptcha/admin#list 上向 Google reCAPTCHA 服务注册您的应用程序。为了避免出现问题,请注册完整的域名。例如,对于我的应用程序,我注册了 recaptcha.mybluemix.net 。

Google 提供了两个密钥:一个站点密钥用于您的 HTML 中,另一个密钥用于服务器上的应用程序,以便与 Google 进行通信。

客户端的配置

要在一个表单中向用户显示 reCAPTCHA 提示,请按照下列步骤操作:

  1. 在 HTML 的 HEAD 标记中的某个地方,调用 Google reCAPTCHA 脚本。
    <script src='https://www.google.com/recaptcha/api.js'></script>
  2. 确定要保护的表单,该表单应该仅由人类提交。通过使用从 Google 获得的站点密钥,在该表单底部的下方添加定义。
<div class="g-recaptcha" data-sitekey="--- your site key goes here –--"></div>

要查看示例,请转到 http://recaptcha.mybluemix.net/index.html 并查看源代码。

服务器端的配置

要使用 reCAPTCHA,请按照下列步骤来修改运行在服务器上的应用程序 (app.js):

  1. 将主体解析器 (body-parser) 和 https 包添加到 package.json 作为依赖关系。首先,您需要读取 POST 表单的结果,其次,要向 Google 的服务器发送一个请求来解释该结果。
  2. 添加以下代码行来应用主体解析器包:
    // Use body-parser to receive POST form requests and JSON var bodyParser = require("body-parser"); app.use(bodyParser.urlencoded({extended:true}));
  3. 添加将一个 HTTPS 请求发送到 Google 的代码,如文章 " 使用 IBM DevOps Track & Plan 管理安全警报 " 中所述。
    1. 创建一个变量来使用 HTTPS 包。
    2. 在表单的处理程序中向 Google 的服务器发出一个请求。
      // 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();   });
  4. 在发送请求时,向 URL 添加两个查询参数: 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"],
  5. 响应可能位于多个数据块中,所以您需要将它们收集在一起,直到完成响应。
    var googleResponse = "";  .  .  .     httpsRes.on("data", function (chunk) {    googleResponse += chunk;     });
  6. 响应是一个 JSON 对象。您可以使用 JSON.parse() 解析它。该参数会告诉您,用户被认为是人类是否是 success ,该参数是一个布尔值。
    httpsRes.on("end", function() {     var human = JSON.parse(googleResponse).success;     res.send(human);    });

要查看 reCAPTCHA 服务是否正常工作,可执行一次提交,并查看结果是否为 true。然后,等待几秒钟并重新加载,确认该表单提交。当请求来自自动化系统时,就会出现再次使用 g-recaptcha-response 值的情况。试图攻击应用程序的程序员可以手动创建有效值一次,然后通过编程进行攻击,在每次提交时都自动发送该值。为了防止这种攻击,从 Google 到以前使用过的值的响应为 false。

结合使用 reCAPTCHA 与 Bluemix 单点登录服务

Bluemix 有一个单点登录服务。我们可能想要对特定的应用程序使用该服务,但我们无法同等信任所有的身份来源。我们担心一些身份来源容易受到自动攻击,因此我们希望在使用身份来源时执行一次 reCAPTCHA 认证。但我们并不想对我们信任的身份来源执行此操作。

配置单点登录服务

以下这些是配置单点登录服务的步骤:

  1. 在 Bluemix 仪表板中,单击 USE SERVICES OR APIS 。
  2. 选择 Security > Single Sign On 。
  3. 单击 CREATE 。
  4. 指定一个名称。对于我的应用程序,我选择了 recaptcha-sso 。
  5. 如果获得一个错误,请忽略它。回到仪表板并单击该服务。
  6. 单击 SETUP 。
  7. 您会看到一个身份来源列表。对于本文之目的,我们配置了两种身份来源:Facebook 和 Google+。

Facebook

  1. 单击 Facebook 磁贴。
  2. 复制 OAUTH 重定向 URI。该 URI 类似于:
    https://recaptcha-sso-aj5bgntzl9-ck11.iam.ibmcloud.com/idaas/mtfim/sps/idaas/login/facebook/callback
  3. 登录到 Facebook。
  4. 转到 Facebook 开发者页面 并单击 My Apps > Add a New App 。
  5. 单击 Website 。
  6. 为应用程序命名并单击 Create New Facebook App ID 。
  7. 选择一个目录并单击 Create App ID 。
  8. 单击 Skip Quickstart 。
  9. 单击 Settings 。
  10. 单击 Add Platform 。
  11. 单击 Website 。
  12. 键入您的应用程序的 URL。 将 Google reCAPTCHA 添加到您的 Bluemix Node.js 应用程序

    将 Google reCAPTCHA 添加到您的 Bluemix Node.js 应用程序

  13. 单击 Advanced 选项卡。
  14. 在 Valid OAuth redirect URIs 字段中粘贴 OAUTH 重定向 URI。
  15. 单击 Save Changes 。
  16. 单击 Basic 选项卡。
  17. 将 App ID 和 App Secret 从 Facebook 复制到 Bluemix。您需要单击 Show 来查看应用程序密钥。
  18. 单击 Save 。
  19. 单击 Verify ,然后 单击此处 。
  20. 在 Facebook 中,单击 Okay 。
  21. 当您看到一条提示登录成功的消息后,返回到 Bluemix 并单击 Done 。

Google +

  1. 单击 Google + 磁贴。
  2. 复制 OAUTH 重定向 URI。该 URI 类似于:
    https://recaptcha-sso-aj5bgntzl9-ck11.iam.ibmcloud.com/idaas/mtfim/sps/idaas/login/google/callback
  3. 单击 Click here 。您被重定向到 Google Developers Console 。
  4. 在 Google Developers Console 中,单击 Enable and manage APIs 。 将 Google reCAPTCHA 添加到您的 Bluemix Node.js 应用程序

    将 Google reCAPTCHA 添加到您的 Bluemix Node.js 应用程序

  5. 单击 Google+ API 。
  6. 单击 Enable API 。
  7. 单击 Go to Credentials 。
  8. 选择从一个 Web 服务器 调用 API。
  9. 选择 User data 并单击 What credentials do I need? 。
  10. 键入您获得的经过认证的重定向 URI,然后单击 Create client ID 。
  11. 为您的应用程序键入一个名称并单击 Continue 。
  12. 下载 client_id.json。复制客户端 ID 和客户端密钥,然后将它们复制到 Bluemix 中。
  13. 单击 Save 。

在应用程序中使用身份认证服务

要使用身份认证服务,请按照下列步骤进行操作:

  1. 返回到您的应用程序。
  2. 单击 BIND A SERVICE OR API 磁贴。
  3. 选择单点登录服务并单击 ADD 。
  4. 单击 Restage 。
  5. 再次打开该服务,并单击新的 INTEGRATE 选项卡。
  6. 键入一个回调 URL。让应用程序的 URL 后跟 /sso 是一个合理选择。
    https://recaptcha.mybluemix.net/sso
  7. 单击 Save ,然后单击 OK 。
  8. 在 package.json 中,添加以下依赖关系:
    "dependencies": {     .     .     .     "passport": "*",     "cookie-parser": "*",     "express-session": "*",      "passport-idaas-openidconnect": "*"  }
  9. 将这些代码行添加到 app.js,添加到定义应用程序和 appEnv 变量的代码的后面:
    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', {}));
  10. 要添加该代码,需要使用 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);
  11. 运行该应用程序。
  12. 尝试访问某个经过身份验证的路径(比如 /auth/test )来进行登录。请注意,您可以登录,但会被重定向到您的应用程序上的 /sso ,它目前还没有处理程序。 Try to access an authenticated path such as /auth/test to

读取身份属性

  1. 为 /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);         });
  2. 创建一个针对无法登录情况的处理程序。
    app.get('/failure', function(req, res) {              res.send('login failed'); });
  3. 在显示用户信息的 /auth 下创建一条路径。会话信息(比如用户身份)可从 req.session 获得。
    app.get('/auth/whoami', function(req, res) {  res.send(JSON.stringify(req.session)); });
    要查看示例应用程序中的用户信息处理程序,请单击 此处 。
    备注: 在生产应用程序中,会删除 /auth/whoami 处理程序来避免泄露信息。我在本文的应用程序中保留它是为了演示目的。
  4. 使用 “JSON 到 HTML” 的转换(比如 http://json.bloople.net/ )来解释结果。以下这些是最重要的字段:
    • passport.user.id :唯一用户标识符
    • passport.user._json.realmName :用户的来源
    • passport.user._json.displayName :用户的名称

仅在需要时发送 reCAPTCHA

最后,创建一个欢迎页面。我发现最简单的方法是拥有一些页面(大部分都是静态页面),并在一个单独的 JavaScript “文件” 中提供所有特定参数。

  1. 修改 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();  } }
  2. 创建一个只是要求 reCAPTCHA 的 get-recaptcha.html 文件。在 reCAPTCHA div 中,在 Google 确定用户是一个人类时,使用 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>
  3. 创建一个针对 reCAPTCHA 结果的处理程序。该处理程序非常相似于前面显示的处理程序,只是它重定向到了原始 URL,并设置了一个会话变量,以便在执行会话期间不再需要该 reCAPTCHA。
    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);      }           });

为应用程序的身份验证部分提供服务

  1. 在应用程序中,为需要身份验证的静态文件创建一个新目录,例如 /auth 。
  2. 在调用 ensureAuthenticated() 的代码行的后面添加一行代码,以便为需要身份验证的静态文件提供服务。新添加的代码行是第 3 行。
    // Serve files that require authentication, only if authenticated app.use("/auth/*", ensureAuthenticated); app.use("/auth", express.static(__dirname + '/auth'));
  3. 为返回可能是在浏览器中有用的会话数据的 /auth/session.js 创建一个处理程序。
    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"  ); });
  4. 根据需要在您的 HTML 文件中使用脚本。 例如:
    <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 单点登录服务。请注意,在使用单点登录服务将应用程序发布到生成环境中之前,还必须执行几个步骤:

  1. 将 keyboard cat 更改为确实非常机密的不同字符串。
  2. 需要使用 HTTPS,如 使用 Bluemix 和 MEAN 堆栈构建自助发表 Facebook 信息的应用程序,第 1 部分 " 的第 5 步中所述。
  3. 将会话信息存储到比内存更好的地方,防止您有多个使用它们的实例,并避免内存泄漏。

BLUEMIX SERVICES USED IN THIS TUTORIAL:

  • SDK for Node.js 运行时 可帮助您轻松开发、部署和扩展服务器端 JavaScript 应用程序。
  • Single Sign On 服务 可以帮助您保护为云开发的 Web 和移动应用程序,轻松地构建和改进应用程序,以便提供基于策略的用户访问安全性。
原文  http://www.ibm.com/developerworks/cn/security/se-add-google-recaptcha-bluemix-node.js-application/index.html?ca=drs-
正文到此结束
Loading...