Meteor中服务器端路由的authentication

什么是最好的方式(最安全和最简单)来validation服务器端路由的用户?

软件/版本

我正在使用最新的铁路路由器1. *和meteor1. *开始,我只是使用帐户密码。

参考代码

我有一个简单的服务器端路由,呈现一个pdf到屏幕上:

两者/ routes.js

Router.route('/pdf-server', function() { var filePath = process.env.PWD + "/server/.files/users/test.pdf"; console.log(filePath); var fs = Npm.require('fs'); var data = fs.readFileSync(filePath); this.response.write(data); this.response.end(); }, {where: 'server'}); 

作为一个例子,我想做一些接近这个SO回答build议的东西 :

在服务器上:

 var Secrets = new Meteor.Collection("secrets"); Meteor.methods({ getSecretKey: function () { if (!this.userId) // check if the user has privileges throw Meteor.Error(403); return Secrets.insert({_id: Random.id(), user: this.userId}); }, }); 

然后在客户端代码中:

 testController.events({ 'click button[name=get-pdf]': function () { Meteor.call("getSecretKey", function (error, response) { if (error) throw error; if (response) Router.go('/pdf-server'); }); } }); 

但即使我不知道如何得到这个方法的工作,我仍然是脆弱的用户只需要像“/ pdf-server”的URL,除非路由本身以某种方式检查了秘密收集权?

在路线,我可以得到请求,并以某种方式获得头信息?

 Router.route('/pdf-server', function() { var req = this.request; var res = this.response; }, {where: 'server'}); 

并从客户端传递一个令牌通过HTTP头,然后在路由检查令牌是否从收集好?

除了使用url标记作为其他答案,你也可以使用cookies:

添加一些包,允许您设置cookie并读取它们的服务器端:

 meteor add mrt:cookies thepumpinglemma:cookies 

那么你可以有一些东西,将cookies与你的login状态同步

客户端

 Tracker.autorun(function() { //Update the cookie whenever they log in or out Cookie.set("meteor_user_id", Meteor.userId()); Cookie.set("meteor_token", localStorage.getItem("Meteor.loginToken")); }); 

服务器端

在服务器端,你只需要检查这个cookie是有效的(与铁路由器)

 Router.route('/somepath/:fileid', function() { //Check the values in the cookies var cookies = new Cookies( this.request ), userId = cookies.get("meteor_user_id") || "", token = cookies.get("meteor_token") || ""; //Check a valid user with this token exists var user = Meteor.users.findOne({ _id: userId, 'services.resume.loginTokens.hashedToken' : Accounts._hashLoginToken(token) }); //If they're not logged in tell them if(!user) return this.response.end("Not allowed"); //Theyre logged in! this.response.end("You're logged in!"); }, {where:'server'}); 

由于服务器端路由作为简单的REST端点,因此他们无法访问用户authentication数据(例如,他们不能调用Meteor.user() )。 所以你需要devise一个替代authenticationscheme。 完成这个最直接的方法是在这里和这里讨论的某种forms的密钥交换。

示例实现:

服务器/ app.js

 // whenever the user logs in, update her apiKey Accounts.onLogin(function(info) { // generate a new apiKey var apiKey = Random.id(); // add the apiKey to the user's document Meteor.users.update(info.user._id, {$set: {apiKey: apiKey}}); }); // auto-publish the current user's apiKey Meteor.publish(null, function() { return Meteor.users.find(this.userId, {fields: {apiKey: 1}}); }); 

LIB / routes.js

 // example route using the apiKey Router.route('/secret/:apiKey', {name: 'secret', where: 'server'}) .get(function() { // fetch the user with this key // note you may want to add an index on apiKey so this is fast var user = Meteor.users.findOne({apiKey: this.params.apiKey}); if (user) { // we have authenticated the user - do something useful here this.response.statusCode = 200; return this.response.end('ok'); } else { // the key is invalid or not provided so return an error this.response.statusCode = 403; return this.response.end('not allowed'); } }); 

客户机/ app.html

 <template name="myTemplate"> {{#with currentUser}} <a href="{{pathFor route='secret'}}">secret</a> {{/with}} </template> 

笔记

  • 只能通过HTTPS访问/secret

  • 尽pipe用户请求/secret很可能是当前连接的,但不能保证她是。 用户可能已经login,复制了她的密钥,closures了选项卡,稍后再发起请求。

  • 这是用户authentication的简单方法。 如果服务器路由显示高价值数据(SSN,信用卡等),我将探索更复杂的机制(参见上面的链接)。

  • 有关从服务器发送静态内容的更多详细信息,请参阅此问题 。

我想我在IronRouter.route()中有一个安全而简单的解决scheme。 该请求必须使用标题中的有效用户标识和身份validation令牌进行。 我从Router.route()中调用这个函数,然后让我访问this.user,或者如果authentication失败,则返回一个401响应:

 // Verify the request is being made by an actively logged in user // @context: IronRouter.Router.route() authenticate = -> // Get the auth info from header userId = this.request.headers['x-user-id'] loginToken = this.request.headers['x-auth-token'] // Get the user from the database if userId and loginToken user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': loginToken} // Return an error if the login token does not match any belonging to the user if not user respond.call this, {success: false, message: "You must be logged in to do this."}, 401 // Attach the user to the context so they can be accessed at this.user within route this.user = user // Respond to an HTTP request // @context: IronRouter.Router.route() respond = (body, statusCode=200, headers) -> this.response.statusCode statusCode this.response.setHeader 'Content-Type', 'text/json' this.response.writeHead statusCode, headers this.response.write JSON.stringify(body) this.response.end() 

从客户那里得到这样的东西:

 Meteor.startup -> HTTP.get "http://yoursite.com/pdf-server", headers: 'X-Auth-Token': Accounts._storedLoginToken() 'X-User-Id': Meteor.userId() (error, result) -> // This callback triggered once http response received console.log result 

这段代码深受RestStop和RestStop2的启发。 它是在Meteor 0.9.0+(构build在Iron Router之上)编写REST API的meteor包的一部分。 你可以在这里查看完整的源代码:

https://github.com/krose72205/meteor-restivus

我真的相信使用HTTP头是解决这个问题的最好方法,因为它们很简单,不需要搞乱cookies或开发新的authenticationscheme。

我喜欢@ kahmali的答案,所以我写了它与WebApp和一个简单的XMLHttpRequest。 这已经在Meteor 1.6上testing过了。

客户

 import { Meteor } from 'meteor/meteor'; import { Accounts } from 'meteor/accounts-base'; // Skipping ahead to the upload logic const xhr = new XMLHttpRequest(); const form = new FormData(); // Add files files.forEach((file) => { form.append(file.name, // So BusBoy sees as file instead of field, use Blob new Blob([file.data], { type: 'text/plain' })); // w/e your mime type is }); // XHR progress, load, error, and readystatechange event listeners here // Open Connection xhr.open('POST', '/path/to/upload', true); // Meteor authentication details (must happen *after* xhr.open) xhr.setRequestHeader('X-Auth-Token', Accounts._storedLoginToken()); xhr.setRequestHeader('X-User-Id', Meteor.userId()); // Send xhr.send(form); 

服务器

 import { Meteor } from 'meteor/meteor'; import { WebApp } from 'meteor/webapp'; import { Roles } from 'meteor/alanning:roles'; // optional const BusBoy = require('connect-busboy'); const crypto = require('crypto'); // built-in Node library WebApp.connectHandlers .use(BusBoy()) .use('/path/to/upload', (req, res) => { const user = req.headers['x-user-id']; // We have to get a base64 digest of the sha256 hashed login token // I'm not sure when Meteor changed to hashed tokens, but this is // one of the major differences from @kahmali's answer const hash = crypto.createHash('sha256'); hash.update(req.headers['x-auth-token']); // Authentication (is user logged-in) if (!Meteor.users.findOne({ _id: user, 'services.resume.loginTokens.hashedToken': hash.digest('base64'), })) { // User not logged in; 401 Unauthorized res.writeHead(401); res.end(); return; } // Authorization if (!Roles.userIsInRole(user, 'whatever')) { // User is not authorized; 403 Forbidden res.writeHead(403); res.end(); return; } if (req.busboy) { // Handle file upload res.writeHead(201); // eventually res.end(); } else { // Something went wrong res.writeHead(500); // server error res.end(); } }); 

我希望这可以帮助别人!

由于Meteor不使用会话cookie,因此客户端在向服务器路由发送HTTP请求时必须明确包含某种用户标识。

最简单的方法是在URL的查询string中传递userId。 很明显,你还需要添加一个安全令牌来certificate用户确实是谁的主张。 获取这个令牌可以通过Meteor方法来完成。

meteor本身并不提供这样的机制,所以你需要一些自定义的实现。 我写了一个名为mhagmajer:server-route的meteor包mhagmajer:server-route ,经过了彻底的testing。 你可以在这里了解更多: https : //blog.hagmajer.com/server-side-routing-with-authentication-in-meteor-6625ed832a94