Node.js 的用户身份验证库?

发布于 2024-09-14 18:47:27 字数 1808 浏览 2 评论 0原文

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(15

一个人练习一个人 2024-09-21 18:47:27

如果您正在寻找 Connect 或 Express 的身份验证框架,Passport 值得研究:https://github.com/jaredhanson/passport

(披露:我是 Passport 的开发者)

我在研究 connect-auth 和 everyauth 后开发了 Passport。虽然它们都是很棒的模块,但它们不适合我的需求。我想要一些更轻且不引人注目的东西。

Passport 被分解为单独的模块,因此您可以选择仅使用您需要的模块(OAuth,仅在必要时使用)。 Passport 也不会在您的应用程序中挂载任何路由,使您可以灵活地决定何时何地进行身份验证,并通过挂钩来控制身份验证成功或失败时发生的情况。

例如,以下是设置基于表单(用户名和密码)身份验证的两步过程:

passport.use(new LocalStrategy(
  function(username, password, done) {
    // Find the user from your DB (MongoDB, CouchDB, other...)
    User.findOne({ username: username, password: password }, function (err, user) {
      done(err, user);
    });
  }
));

app.post('/login', 
  passport.authenticate('local', { failureRedirect: '/login' }),
  function(req, res) {
    // Authentication successful. Redirect home.
    res.redirect('/');
  });

可使用其他策略通过 Facebook、Twitter 等进行身份验证。如有必要,可以插入自定义策略。

If you are looking for an authentication framework for Connect or Express, Passport is worth investigating: https://github.com/jaredhanson/passport

(Disclosure: I'm the developer of Passport)

I developed Passport after investigating both connect-auth and everyauth. While they are both great modules, they didn't suit my needs. I wanted something that was more light-weight and unobtrusive.

Passport is broken down into separate modules, so you can choose to use only what you need (OAuth, only if necessary). Passport also does not mount any routes in your application, giving you the flexibility to decide when and where you want authentication, and hooks to control what happens when authentication succeeds or fails.

For example, here is the two-step process to setup form-based (username and password) authentication:

passport.use(new LocalStrategy(
  function(username, password, done) {
    // Find the user from your DB (MongoDB, CouchDB, other...)
    User.findOne({ username: username, password: password }, function (err, user) {
      done(err, user);
    });
  }
));

app.post('/login', 
  passport.authenticate('local', { failureRedirect: '/login' }),
  function(req, res) {
    // Authentication successful. Redirect home.
    res.redirect('/');
  });

Additional strategies are available for authentication via Facebook, Twitter, etc. Custom strategies can be plugged-in, if necessary.

歌入人心 2024-09-21 18:47:27

会话+ 如果

我猜你没有找到很多好的库的原因是使用库进行身份验证大多是过度设计的。

您正在寻找的只是一个会话绑定器:) 一个会话:

if login and user == xxx and pwd == xxx 
   then store an authenticated=true into the session 
if logout destroy session

就是这样。


我不同意你的结论,即 connect-auth 插件是正确的选择。

我也在使用 connect 但我不使用 connect-auth 有两个原因:

  1. 恕我直言,它破坏了 connect-auth连接的强大且易于阅读的洋葱圈架构。不行——我的意见:)。
    您可以在此处找到一篇关于 connect 工作原理和洋葱圈想法的非常好的简短文章。

  2. 如果您 - 正如所写的 - 只是想使用数据库或文件的基本或 http 登录。 Connect-auth 太大了。它更适合 OAuth 1.0、OAuth 2.0 和 OAuth 2.0 等内容。 Co


一个非常简单的连接身份验证

(它已经完成。只需执行它进行测试,但如果您想在生产中使用它,请确保使用 https)
(为了符合 REST 原则,您应该使用 POST 请求而不是 GET 请求,因为您更改了状态:)

var connect = require('connect');
var urlparser = require('url');

var authCheck = function (req, res, next) {
    url = req.urlp = urlparser.parse(req.url, true);

    // ####
    // Logout
    if ( url.pathname == "/logout" ) {
      req.session.destroy();
    }

    // ####
    // Is User already validated?
    if (req.session && req.session.auth == true) {
      next(); // stop here and pass to the next onion ring of connect
      return;
    }

    // ########
    // Auth - Replace this example with your Database, Auth-File or other things
    // If Database, you need a Async callback...
    if ( url.pathname == "/login" && 
         url.query.name == "max" && 
         url.query.pwd == "herewego"  ) {
      req.session.auth = true;
      next();
      return;
    }

    // ####
    // This user is not authorized. Stop talking to him.
    res.writeHead(403);
    res.end('Sorry you are not authorized.\n\nFor a login use: /login?name=max&pwd=herewego');
    return;
}

var helloWorldContent = function (req, res, next) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('authorized. Walk around :) or use /logout to leave\n\nYou are currently at '+req.urlp.pathname);
}

var server = connect.createServer(
      connect.logger({ format: ':method :url' }),
      connect.cookieParser(),
      connect.session({ secret: 'foobar' }),
      connect.bodyParser(),
      authCheck,
      helloWorldContent
);

server.listen(3000);

注意,

我一年多前写了此声明,目前没有活动的节点项目。因此 Express 中可能会有 API 更改。如果我需要更改任何内容,请添加评论。

Session + If

I guess the reason that you haven't found many good libraries is that using a library for authentication is mostly over engineered.

What you are looking for is just a session-binder :) A session with:

if login and user == xxx and pwd == xxx 
   then store an authenticated=true into the session 
if logout destroy session

thats it.


I disagree with your conclusion that the connect-auth plugin is the way to go.

I'm using also connect but I do not use connect-auth for two reasons:

  1. IMHO breaks connect-auth the very powerful and easy to read onion-ring architecture of connect. A no-go - my opinion :).
    You can find a very good and short article about how connect works and the onion ring idea here.

  2. If you - as written - just want to use a basic or http login with database or file. Connect-auth is way too big. It's more for stuff like OAuth 1.0, OAuth 2.0 & Co


A very simple authentication with connect

(It's complete. Just execute it for testing but if you want to use it in production, make sure to use https)
(And to be REST-Principle-Compliant you should use a POST-Request instead of a GET-Request b/c you change a state :)

var connect = require('connect');
var urlparser = require('url');

var authCheck = function (req, res, next) {
    url = req.urlp = urlparser.parse(req.url, true);

    // ####
    // Logout
    if ( url.pathname == "/logout" ) {
      req.session.destroy();
    }

    // ####
    // Is User already validated?
    if (req.session && req.session.auth == true) {
      next(); // stop here and pass to the next onion ring of connect
      return;
    }

    // ########
    // Auth - Replace this example with your Database, Auth-File or other things
    // If Database, you need a Async callback...
    if ( url.pathname == "/login" && 
         url.query.name == "max" && 
         url.query.pwd == "herewego"  ) {
      req.session.auth = true;
      next();
      return;
    }

    // ####
    // This user is not authorized. Stop talking to him.
    res.writeHead(403);
    res.end('Sorry you are not authorized.\n\nFor a login use: /login?name=max&pwd=herewego');
    return;
}

var helloWorldContent = function (req, res, next) {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('authorized. Walk around :) or use /logout to leave\n\nYou are currently at '+req.urlp.pathname);
}

var server = connect.createServer(
      connect.logger({ format: ':method :url' }),
      connect.cookieParser(),
      connect.session({ secret: 'foobar' }),
      connect.bodyParser(),
      authCheck,
      helloWorldContent
);

server.listen(3000);

NOTE

I wrote this statement over a year ago and have currently no active node projects. So there are may be API-Changes in Express. Please add a comment if I should change anything.

度的依靠╰つ 2024-09-21 18:47:27

看起来连接中间件的 connect-auth 插件正是我所需要的

”我使用express [http://expressjs.com],所以connect插件非常适合,因为express是connect的子类(好的 - 原型)

Looks like the connect-auth plugin to the connect middleware is exactly what I need

I'm using express [ http://expressjs.com ] so the connect plugin fits in very nicely since express is subclassed (ok - prototyped) from connect

滥情空心 2024-09-21 18:47:27

我基本上在寻找同样的东西。具体来说,我想要以下内容:

  1. 使用express.js,它包装了 Connect 的中间件功能
  2. “基于表单”的身份验证
  3. 对身份验证的路由进行精细控制
  4. 用户/密码的数据库后端
  5. 使用会话

我最终做的是创建自己的我将其作为参数传递给我想要进行身份验证的每个路由的中间件函数 check_authcheck_auth 仅检查会话,如果用户未登录,则将其重定向到登录页面,如下所示:

function check_auth(req, res, next) {

  //  if the user isn't logged in, redirect them to a login page
  if(!req.session.login) {
    res.redirect("/login");
    return; // the buck stops here... we do not call next(), because
            // we don't want to proceed; instead we want to show a login page
  }

  //  the user is logged in, so call next()
  next();
}

然后,对于每个路由,我确保此函数作为中间件传递。例如:

app.get('/tasks', check_auth, function(req, res) {
    // snip
});

最后,我们需要实际处理登录过程。这很简单:

app.get('/login', function(req, res) {
  res.render("login", {layout:false});
});

app.post('/login', function(req, res) {

  // here, I'm using mongoose.js to search for the user in mongodb
  var user_query = UserModel.findOne({email:req.body.email}, function(err, user){
    if(err) {
      res.render("login", {layout:false, locals:{ error:err } });
      return;
    }

    if(!user || user.password != req.body.password) {
      res.render("login",
        {layout:false,
          locals:{ error:"Invalid login!", email:req.body.email }
        }
      );
    } else {
      // successful login; store the session info
      req.session.login = req.body.email;
      res.redirect("/");
    }
  });
});

无论如何,这种方法的设计主要是为了灵活和简单。我确信有很多方法可以改进它。如果您有任何反馈,我非常希望得到您的反馈。

编辑:这是一个简化的示例。在生产系统中,您永远不想存储和存储数据。比较纯文本形式的密码。正如评论者指出的那样,有一些库可以帮助管理密码安全。

I was basically looking for the same thing. Specifically, I wanted the following:

  1. To use express.js, which wraps Connect's middleware capability
  2. "Form based" authentication
  3. Granular control over which routes are authenticated
  4. A database back-end for users/passwords
  5. Use sessions

What I ended up doing was creating my own middleware function check_auth that I pass as an argument to each route I want authenticated. check_auth merely checks the session and if the user is not logged in, then redirects them to the login page, like so:

function check_auth(req, res, next) {

  //  if the user isn't logged in, redirect them to a login page
  if(!req.session.login) {
    res.redirect("/login");
    return; // the buck stops here... we do not call next(), because
            // we don't want to proceed; instead we want to show a login page
  }

  //  the user is logged in, so call next()
  next();
}

Then for each route, I ensure this function is passed as middleware. For example:

app.get('/tasks', check_auth, function(req, res) {
    // snip
});

Finally, we need to actually handle the login process. This is straightforward:

app.get('/login', function(req, res) {
  res.render("login", {layout:false});
});

app.post('/login', function(req, res) {

  // here, I'm using mongoose.js to search for the user in mongodb
  var user_query = UserModel.findOne({email:req.body.email}, function(err, user){
    if(err) {
      res.render("login", {layout:false, locals:{ error:err } });
      return;
    }

    if(!user || user.password != req.body.password) {
      res.render("login",
        {layout:false,
          locals:{ error:"Invalid login!", email:req.body.email }
        }
      );
    } else {
      // successful login; store the session info
      req.session.login = req.body.email;
      res.redirect("/");
    }
  });
});

At any rate, this approach was mostly designed to be flexible and simple. I'm sure there are numerous ways to improve it. If you have any, I'd very much like your feedback.

EDIT: This is a simplified example. In a production system, you'd never want to store & compare passwords in plain text. As a commenter points out, there are libs that can help manage password security.

从此见与不见 2024-09-21 18:47:27

如果您想要第三方/社交网络登录集成,还可以查看 everyauth

Also have a look at everyauth if you want third party/social network login integration.

桃气十足 2024-09-21 18:47:27

这是我的一个项目中用于基本身份验证的一些代码。我将它用于带有附加身份验证数据缓存的 CouchDB,但我删除了该代码。

围绕请求处理封装身份验证方法,并为不成功的身份验证提供第二个回调。成功回调将获取用户名作为附加参数。不要忘记在失败回调中正确处理凭证错误或缺失的请求:

/**
 * Authenticate a request against this authentication instance.
 * 
 * @param request
 * @param failureCallback
 * @param successCallback
 * @return
 */
Auth.prototype.authenticate = function(request, failureCallback, successCallback)
{
    var requestUsername = "";
    var requestPassword = "";
    if (!request.headers['authorization'])
    {
        failureCallback();
    }
    else
    {
        var auth = this._decodeBase64(request.headers['authorization']);
        if (auth)
        {
            requestUsername = auth.username;
            requestPassword = auth.password;
        }
        else
        {
            failureCallback();
        }
    }


    //TODO: Query your database (don't forget to do so async)


    db.query( function(result)
    {
        if (result.username == requestUsername && result.password == requestPassword)
        {
            successCallback(requestUsername);
        }
        else
        {
            failureCallback();
        }
    });

};


/**
 * Internal method for extracting username and password out of a Basic
 * Authentication header field.
 * 
 * @param headerValue
 * @return
 */
Auth.prototype._decodeBase64 = function(headerValue)
{
    var value;
    if (value = headerValue.match("^Basic\\s([A-Za-z0-9+/=]+)$"))
    {
        var auth = (new Buffer(value[1] || "", "base64")).toString("ascii");
        return {
            username : auth.slice(0, auth.indexOf(':')),
            password : auth.slice(auth.indexOf(':') + 1, auth.length)
        };
    }
    else
    {
        return null;
    }

};

Here is some code for basic authentication from one of my projects. I use it against CouchDB with and additional auth data cache, but I stripped that code.

Wrap an authentication method around you request handling, and provide a second callback for unsuccessfull authentication. The success callback will get the username as an additional parameter. Don't forget to correctly handle requests with wrong or missing credentials in the failure callback:

/**
 * Authenticate a request against this authentication instance.
 * 
 * @param request
 * @param failureCallback
 * @param successCallback
 * @return
 */
Auth.prototype.authenticate = function(request, failureCallback, successCallback)
{
    var requestUsername = "";
    var requestPassword = "";
    if (!request.headers['authorization'])
    {
        failureCallback();
    }
    else
    {
        var auth = this._decodeBase64(request.headers['authorization']);
        if (auth)
        {
            requestUsername = auth.username;
            requestPassword = auth.password;
        }
        else
        {
            failureCallback();
        }
    }


    //TODO: Query your database (don't forget to do so async)


    db.query( function(result)
    {
        if (result.username == requestUsername && result.password == requestPassword)
        {
            successCallback(requestUsername);
        }
        else
        {
            failureCallback();
        }
    });

};


/**
 * Internal method for extracting username and password out of a Basic
 * Authentication header field.
 * 
 * @param headerValue
 * @return
 */
Auth.prototype._decodeBase64 = function(headerValue)
{
    var value;
    if (value = headerValue.match("^Basic\\s([A-Za-z0-9+/=]+)$"))
    {
        var auth = (new Buffer(value[1] || "", "base64")).toString("ascii");
        return {
            username : auth.slice(0, auth.indexOf(':')),
            password : auth.slice(auth.indexOf(':') + 1, auth.length)
        };
    }
    else
    {
        return null;
    }

};
谁的年少不轻狂 2024-09-21 18:47:27

几年过去了,我想介绍一下我的 Express 身份验证解决方案。它被称为Lockit。您可以在 GitHub 上找到该项目,并在 我的博客

那么与现有解决方案有什么区别?

  • 易于使用:设置数据库、npm install、require('lockit')lockit(app)
  • 已内置完成的路由(/signup、/login、/forgot-password 等)
  • 已内置视图(基于 Bootstrap,但您可以轻松使用自己的视图)
  • 它支持 JSON 通信AngularJS / Ember.js 单页应用程序
  • 不支持 OAuth 和 OpenID。只有用户名密码
  • 它可以与多个数据库(CouchDB、MongoDB、SQL)一起使用,开箱即用,
  • 它有测试(我找不到任何针对 Drywall 的测试),
  • 它是积极维护的(与 everyauth 相比),
  • 电子邮件验证和忘记密码过程(使用令牌发送电子邮件) ,不受 Passport 支持)
  • 模块化:仅使用您需要的
  • 灵活性:自定义所有内容

请查看 示例

A few years have passed and I'd like to introduce my authentication solution for Express. It's called Lockit. You can find the project on GitHub and a short intro at my blog.

So what are the differences to the existing solutions?

  • easy to use: set up your DB, npm install, require('lockit'), lockit(app), done
  • routes already built-in (/signup, /login, /forgot-password, etc.)
  • views already built-in (based on Bootstrap but you can easily use your own views)
  • it supports JSON communication for your AngularJS / Ember.js single page apps
  • it does NOT support OAuth and OpenID. Only username and password.
  • it works with several databases (CouchDB, MongoDB, SQL) out of the box
  • it has tests (I couldn't find any tests for Drywall)
  • it is actively maintained (compared to everyauth)
  • email verification and forgot password process (send email with token, not supported by Passport)
  • modularity: use only what you need
  • flexibility: customize all the things

Take a look at the examples.

和影子一齐双人舞 2024-09-21 18:47:27

身份验证的另一种方式是无密码,这是一种基于令牌的身份验证模块,用于规避密码的固有问题[1]。它的实施速度很快,不需要太多的表单,并且为普通用户提供了更好的安全性(完全公开:我是作者)。

[1]:密码已过时

A different take on authentication is Passwordless, a token-based authentication module for express that circumvents the inherent problem of passwords [1]. It's fast to implement, doesn't require too many forms, and offers better security for the average user (full disclosure: I'm the author).

[1]: Passwords are Obsolete

时光匆匆的小流年 2024-09-21 18:47:27

关于手卷方法的警告:

我很失望地看到本文中建议的一些代码示例无法防范此类基本身份验证漏洞,例如会话固定或定时攻击。

与此处的几个建议相反,身份验证并不简单,并且手动解决方案并不总是微不足道的。我会推荐 passportjsbcrypt

但是,如果您确实决定手动解决方案,请查看提供的 express js示例以获取灵感。

祝你好运。

A word of caution regarding handrolled approaches:

I'm disappointed to see that some of the suggested code examples in this post do not protect against such fundamental authentication vulnerabilities such as session fixation or timing attacks.

Contrary to several suggestions here, authentication is not simple and handrolling a solution is not always trivial. I would recommend passportjs and bcrypt.

If you do decide to handroll a solution however, have a look at the express js provided example for inspiration.

Good luck.

在风中等你 2024-09-21 18:47:27

有一个名为 Drywall 的项目,它使用 Passport 并且还有一个用户管理管理面板。如果您正在寻找一个功能齐全的用户身份验证和管理系统,类似于 Django 所具有的但适用于 Node.js 的系统,那么这就是它。我发现它是构建需要用户身份验证和管理系统的节点应用程序的一个非常好的起点。有关 Passport 工作原理的信息,请参阅 Jared Hanson 的回答

There is a project called Drywall that implements a user login system with Passport and also has a user management admin panel. If you're looking for a fully-featured user authentication and management system similar to something like what Django has but for Node.js, this is it. I found it to be a really good starting point for building a node app that required a user authentication and management system. See Jared Hanson's answer for information on how Passport works.

剩余の解释 2024-09-21 18:47:27

以下是用于 Node js 身份验证的两个流行的 Github 库:

https://github.com/jaredhanson/passport (建议)

https://nodejsmodules.org/pkg/everyauth

Here are two popular Github libraries for node js authentication:

https://github.com/jaredhanson/passport ( suggestible )

https://nodejsmodules.org/pkg/everyauth

凉风有信 2024-09-21 18:47:27

Angular 客户端提供用户身份验证的 API,

使用 mongo 的快速简单示例,对于为app.js 中的

var express = require('express');
var MongoStore = require('connect-mongo')(express);

// ...

app.use(express.cookieParser());
// obviously change db settings to suit
app.use(express.session({
    secret: 'blah1234',
    store: new MongoStore({
        db: 'dbname',
        host: 'localhost',
        port: 27017
    })
}));

app.use(app.router);

您的路线如下所示:

// (mongo connection stuff)

exports.login = function(req, res) {

    var email = req.body.email;
    // use bcrypt in production for password hashing
    var password = req.body.password;

    db.collection('users', function(err, collection) {
        collection.findOne({'email': email, 'password': password}, function(err, user) {
            if (err) {
                res.send(500);
            } else {
                if(user !== null) {
                    req.session.user = user;
                    res.send(200);
                } else {
                    res.send(401);
                }
            }
        });
    });
};

然后在需要身份验证的路线中,您只需检查用户会话即可:

if (!req.session.user) {
    res.send(403);
}

Quick simple example using mongo, for an API that provides user auth for ie Angular client

in app.js

var express = require('express');
var MongoStore = require('connect-mongo')(express);

// ...

app.use(express.cookieParser());
// obviously change db settings to suit
app.use(express.session({
    secret: 'blah1234',
    store: new MongoStore({
        db: 'dbname',
        host: 'localhost',
        port: 27017
    })
}));

app.use(app.router);

for your route something like this:

// (mongo connection stuff)

exports.login = function(req, res) {

    var email = req.body.email;
    // use bcrypt in production for password hashing
    var password = req.body.password;

    db.collection('users', function(err, collection) {
        collection.findOne({'email': email, 'password': password}, function(err, user) {
            if (err) {
                res.send(500);
            } else {
                if(user !== null) {
                    req.session.user = user;
                    res.send(200);
                } else {
                    res.send(401);
                }
            }
        });
    });
};

Then in your routes that require auth you can just check for the user session:

if (!req.session.user) {
    res.send(403);
}
夜夜流光相皎洁 2024-09-21 18:47:27

这是一个使用时间戳令牌的新身份验证库。令牌可以通过电子邮件或短信发送给用户,无需将其存储在数据库中。它可用于无密码身份验证或双因素身份验证。

https://github.com/vote539/easy-no-password

披露:我是该库的开发者。

Here is a new authentication library that uses timestamped tokens. The tokens can be emailed or texted to users without the need to store them in a database. It can be used for passwordless authentication or for two-factor authentication.

https://github.com/vote539/easy-no-password

Disclosure: I am the developer of this library.

最好是你 2024-09-21 18:47:27

如果您需要使用 Microsoft Windows 用户帐户进行 SSO(单点登录)身份验证。您可以尝试 https://github.com/jlguenego/node-expose-sspi< /a>.

它将为您提供一个 req.sso 对象,其中包含所有客户端用户信息(登录名、显示名称、sid、组)。

const express = require("express");
const { sso, sspi } = require("node-expose-sspi");

sso.config.debug = false;

const app = express();

app.use(sso.auth());

app.use((req, res, next) => {
  res.json({
    sso: req.sso
  });
});

app.listen(3000, () => console.log("Server started on port 3000"));

免责声明:我是 node-expose-sspi 的作者。

If you need authentication with SSO (Single Sign On) with Microsoft Windows user account. You may give a try to https://github.com/jlguenego/node-expose-sspi.

It will give you a req.sso object which contains all client user information (login, display name, sid, groups).

const express = require("express");
const { sso, sspi } = require("node-expose-sspi");

sso.config.debug = false;

const app = express();

app.use(sso.auth());

app.use((req, res, next) => {
  res.json({
    sso: req.sso
  });
});

app.listen(3000, () => console.log("Server started on port 3000"));

Disclaimer: I am the author of node-expose-sspi.

人疚 2024-09-21 18:47:27

slim-auth

一个轻量级、零配置的用户身份验证模块。它不需要单独的数据库。

https://www.npmjs.com/package/slimauth

很简单:

app.get('/private-page', (req, res) => {

    if (req.user.isAuthorized) {
        // user is logged in! send the requested page
        // you can access req.user.email
    }
    else {
        // user not logged in. redirect to login page
    }
})

slim-auth

A lightweight, zero-configuration user authentication module. It doesn't need a sperate database.

https://www.npmjs.com/package/slimauth

It's simple as:

app.get('/private-page', (req, res) => {

    if (req.user.isAuthorized) {
        // user is logged in! send the requested page
        // you can access req.user.email
    }
    else {
        // user not logged in. redirect to login page
    }
})
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文