【问题标题】:How to include access-token in the HTTP header when requesting a new page from browser从浏览器请求新页面时如何在 HTTP 标头中包含访问令牌
【发布时间】:2017-09-13 04:09:18
【问题描述】:

其他人 (here) 提出了类似的问题,但没有得到正确答案。由于这对我来说是基本且重要的(也许对其他人来说也是如此),我想在这里问。我在服务器端使用 Node.js+Express+EJS。我努力通过在服务器上使用 jsonwebtoken 和在 Web 浏览器上使用 jQuery 的 ajax-jsonp 来使令牌认证成功。现在,在授予令牌并将其存储在浏览器端的 sessionStorage 中之后,我可以使用请求标头中包含的令牌发起另一个 ajax 请求,以获取用户的个人资料并将其显示在“当前”页面的某处。但我想要的是显示一个新网页来显示用户的个人资料,而不是在“当前”页面(网站的主/索引页面)中显示它。问题是:

  1. 如何发起这样的HTTP GET请求,包括HTTP头中的token;并将响应显示为新网页?
  2. Node.js 如何处理这个问题?如果我使用 res.render 那么在哪里放置 js 逻辑来验证令牌并访问数据库并生成页面内容?

或者,我们应该说令牌机制更适合API认证而不是普通网页认证(网络浏览器提供有限的API)?

如果我们想使用令牌机制作为一般身份验证,我认为这个问题的答案很重要,因为在网站场景中,内容主要组织为服务器上的网页,而客户端的 API 由浏览器。

纯粹猜测,可能有另一种方法,即 ajax 成功回调从当前页面创建一个新页面,并使用来自服务器的响应,但我也不知道如何实现这一点。

通过调用波纹管代码成功返回customer_profile.ejs中的HTML内容,但客户端ajax(显然)拒绝了它。

exports.customer_profile = function (req, res) {
  var token = req.headers.token;
  var public_key = fs.readFileSync(path.resolve() + '/cert/public_key.pem');
  var decoded = jwt.verify(token, public_key);
  var sql = 'SELECT * FROM  customer WHERE username = "' + decoded.sub + '"';
  util.conn.query(sql, function (err, rows) {
    if (!err) {
      for (var i = 0; i < rows.length; i++) {
        res.render('customer_profile', {customer_profile: rows[i]});
        break;
      }
    }
  });
};

【问题讨论】:

标签: node.js authentication web browser token


【解决方案1】:

我也在努力寻找解决方案。请注意,我将 Firebase 用于某些功能,但我会尽我所能记录逻辑。

到目前为止,我能够弄清楚的是:

  1. 将自定义标头附加到 HTTP 请求客户端
// landing.js - main page script snippet
function loadPage(path) {
    // Get current user's ID Token
    firebase.auth().currentUser.getIdToken()
    .then(token => {
        // Make a fetch request to 'path'
        return fetch(`${window.location.origin}/${document.documentElement.lang}/${path}`, {
            method: 'GET',
            headers: {'X-Firebase-ID-Token': token} // Adds unverified token to a custom header
        });
    })
    .then(response => { 
        // As noted below, this part I haven't solved yet. 
        // TODO: Open response as new webpage instead of displaying as data in existing one
        return response.text();
    })
    .then(text => {
        console.log(text);
    })
    .catch(error => {
        console.log(error);
    });
}
  1. 通过在服务器端检索相应的标头值来根据您的逻辑验证令牌
// app.js - main Express application server-side file
// First of all, I set up middleware on my application (and all other setup). 
// getLocale - language negotiation. 
// getContext - auth token verification if it is available and appends it to Request object for convenience

app.use('/:lang([a-z]{2})?', middleware.getLocale, middleware.getContext, routes);

// Receives all requests on optional 2 character route, runs middleware then passes to router "routes"
// middleware/index.js - list of all custom middleware functions (only getContext shown for clarity)
getContext: function(req, res, next) {
        const idToken = req.header('X-Firebase-ID-Token'); // Retrieves token from header
        if(!idToken) {
            return next(); // Passes to next middleware if no token, terminates further execution
        }
        admin.auth().verifyIdToken(idToken, true) // If token provided, verify authenticity (Firebase is kind enough to do it for you)
        .then(token => {
            req.decoded_token = token; // Append token to Request object for convenience in further middleware
            return next(); // Pass on further
        })
        .catch(error => {
            console.log('Request not authorized', 401, error)
            return next(); // Log error to server console, pass to next middleware (not interested in failing the request here as app can still work without token)
        });
    }
  1. 渲染并发回数据
// routes/index.js - main router for my application mounted on top of /:lang([a-z]{2})? - therefore routes are now relative to it
// here is the logic for displaying or not displaying the page to the user

router.get('/console', middleware.getTranslation('console'), (req, res) => {
    if(req.decoded_token) { // if token was verified successfully and is appended to req
        res.render('console', responseObject); // render the console.ejs with responseObject as the data source (assume for now that it contains desired DB data)
    } else {
        res.status(401).send('Not authorized'); // else send 401 to user
    }
});

如您所见,我能够通过使用自定义中间件将代码模块化并使其简洁明了。它现在是一个有效的 API,使用身份验证和受限访问从服务器返回数据

我还没有解决的问题:

如上所述,该解决方案使用 fetch API,请求的结果是来自服务器 (html) 的数据,而不是新页面(即跟随锚链接时)。这意味着现在使用此代码的唯一方法是使用 DOM 操作并将响应设置为页面的 innerHTML。 MDN 建议您可以设置“位置”标头,该标头将在浏览器中显示一个新 URL(您希望指示的那个)。这意味着您实际上实现了您和我都想要的东西,但是如果您知道我的意思,我仍然无法理解如何以与浏览器相同的方式显示它,当您点击链接时。

无论如何,请让我知道您对此的看法以及您是否能够从我尚未解决的部分解决它

【讨论】:

  • 我刚刚想到的另一种方法是将元素包装在
    标记中并将其解析为普通表单 POST 请求。在这种情况下,您仍然不向标头添加数据,但您至少可以在正文中传递它,这已经比查询参数更好,但似乎仍然是一种变通方法而不是合法的解决方案。来自您的服务器的响应是所需的新加载网页。好的妥协?
猜你喜欢
  • 1970-01-01
  • 2021-08-13
  • 1970-01-01
  • 1970-01-01
  • 2013-10-27
  • 2012-11-05
  • 2015-07-20
  • 2016-11-07
  • 1970-01-01
相关资源
最近更新 更多