web api json_如何使用JSON Web令牌和Passport实现API身份验证

web api json

介绍 (Introduction)

Many web applications and APIs use a form of authentication to protect resources and restrict their access only to verified users. This guide will walk you through how to implement authentication for an API using Json Web Tokens (JWTs) and Passport, an authentication middleware for Node.

许多Web应用程序和API使用一种身份验证形式来保护资源并仅对经过验证的用户限制其访问。 本指南将引导您逐步了解如何使用Json Web令牌(JWT)和Passport ( Node的身份验证中间件)对API进行身份验证。

了解JSON Web令牌 (Understanding JSON Web Tokens)

JSON Web Token (JWT) is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object.

JSON Web令牌(JWT)是一个开放标准 ,它定义了一种紧凑且自包含的方式,用于在各方之间安全地将信息作为JSON对象传输。

JWTs are secure because they are digitally signed and if the information contained within is tampered in any way, it renders that token invalid. We’ll look at how this is made possible later on. J

JWT是安全的,因为它们是经过数字签名的,并且如果其中包含的信息受到任何篡改,它将使该令牌无效。 稍后我们将探讨如何实现这一点。 Ĵ

A JWT is composed of three separate definitions: header, payload, and signature.

JWT由三个独立的定义组成:标头,有效负载和签名。

Header : the header defines the type of algorithm used to verify the token and the type of token:

标头 :标头定义了用于验证令牌的算法类型和令牌类型:

{
        "type" : "JWT",
        "alg" : "HS256"
      }

*Payload *: contains the claims. Claims are information about the user together with other additional metadata, such as the following:

* 有效载荷* :包含索赔 。 声明是有关用户的信息以及其他其他元数据,例如:

{
        id : 1
        name : 'devgson'
        iat : 1421211952
      }

Signature : The signature encodes the information in the header and payload in base64 format together with a secret key. All this information is then signed by the algorithm specified in the header. In our example, we’re using HMACSHA256. The signature verifies that the message being sent wasn’t tampered along the way.

签名 :签名使用base64格式将标头和有效载荷中的信息与密钥一起编码。 然后,所有这些信息都由标题中指定的算法签名。 在我们的示例中,我们使用HMACSHA256 。 签名可以验证发送的消息在途中没有被篡改。

HMACSHA256(
        base64UrlEncode(header) + "." +
        base64UrlEncode(payload),
        secret
      )

Note: JWT’s should not be used to transfer or store secure information, because anyone that manages to intercept the token can decode the header and payload within. All the signature does is verify that the token hasn’t been tampered in any way. It doesn’t stop the token from being tampered with.

注意 :不应使用JWT传输或存储安全信息,因为设法拦截令牌的任何人都可以解码其中的标头和有效负载。 签名所做的全部工作就是验证令牌是否未被篡改。 它不会阻止令牌被篡改。

设置护照 (Setting up Passport)

Passport is an authentication middleware used to authenticate requests. It allows developers to use different strategies for authenticating users, such as using a local database or connecting to social networks through APIs. In this guide, we’ll be using the local (email / password) strategy.

Passport是用于对请求进行身份验证的身份验证中间件。 它允许开发人员使用不同的策略来验证用户身份,例如使用本地数据库或通过API连接到社交网络。 在本指南中,我们将使用本地(电子邮件/密码)策略。

Lets create a folder structure for the files we’ll be using :

让我们为将要使用的文件创建一个文件夹结构:

-model
    ---model.js
    -routes
    ---routes.js
    ---secure-routes.js
    -auth
    ---auth.js
    -app.js
    -package.json

Install the necessary packages with:

使用以下方法安装必要的软件包:

  • npm install --save bcrypt body-parser express jsonwebtoken mongoose passport passport-local passport-jwt

    npm install-保存bcrypt主体解析器express jsonwebtoken猫鼬护照护照本地护照jwt

We’ll need bcrypt for hashing user passwords, jsonwebtoken for signing tokens, passport-local for implementing local strategy, and passport-jwt for getting and verifying JWTs.

我们将需要使用bcrypt来哈希用户密码,使用jsonwebtoken来对令牌进行签名,需要passport-local来实现本地策略,以及需要使用passport-jwt来获取和验证JWT。

Here’s how our application is going to work :

这是我们的应用程序的工作方式:

  • The user signs up and then logs in, after the user logs in, a JSON web token would be given to the user.

    用户注册然后登录,在用户登录后,将向用户提供一个JSON Web令牌。
  • The user is expected to store this token locally.

    用户应在本地存储此令牌。
  • This token is to be sent by the user when trying to access certain secure routes, once the token has been verified, the user is then allowed to access the route.

    当尝试访问某些安全路由时,该令牌将由用户发送,一旦令牌经过验证,便允许用户访问该路由。

设置数据库 (Setting Up the Database)

First of all, we’ll create the user schema. A user should only provide email and password, that would be enough information.

首先,我们将创建用户架构。 用户只应提供电子邮件和密码,这将是足够的信息。

model/model.js

const mongoose = require('mongoose')
const bcrypt = require('bcrypt');
const Schema = mongoose.Schema;

const UserSchema = new Schema({
  email : {
    type : String,
    required : true,
    unique : true
  },
  password : {
    type : String,
    required : true 
  }
});
  ...

Now we don’t want to store passwords in plain text because if an attacker manages to get access to the database, the password can be read so we want to avoid this. We’ll make use of a package called ‘bcrypt’ to hash user passwords and store them safely.

现在,我们不想以纯文本形式存储密码,因为如果攻击者设法访问数据库,则可以读取密码,因此我们希望避免这种情况。 我们将使用一个名为“ bcrypt”的包对用户密码进行哈希处理并安全地存储它们。

model/model.js
 ....
//This is called a pre-hook, before the user information is saved in the database
//this function will be called, we'll get the plain text password, hash it and store it.
UserSchema.pre('save', async function(next){
  //'this' refers to the current document about to be saved
  const user = this;
  //Hash the password with a salt round of 10, the higher the rounds the more secure, but the slower
  //your application becomes.
  const hash = await bcrypt.hash(this.password, 10);
  //Replace the plain text password with the hash and then store it
  this.password = hash;
  //Indicates we're done and moves on to the next middleware
  next();
});

//We'll use this later on to make sure that the user trying to log in has the correct credentials
UserSchema.methods.isValidPassword = async function(password){
  const user = this;
  //Hashes the password sent by the user for login and checks if the hashed password stored in the 
  //database matches the one sent. Returns true if it does else false.
  const compare = await bcrypt.compare(password, user.password);
  return compare;
}

const UserModel = mongoose.model('user',UserSchema);

module.exports = UserModel;

设置注册和登录中间件 (Setting Up Registration and Login Middleware)

We’ll use the passport local strategy to create middleware that will handle user registration and login. This will then be plugged into certain routes and be used for authentication.

我们将使用本地护照策略创建可处理用户注册和登录的中间件。 然后,将其插入某些路由并用于身份验证。

auth/auth.js

const passport = require('passport');
const localStrategy = require('passport-local').Strategy;
const UserModel = require('../model/model');

//Create a passport middleware to handle user registration
passport.use('signup', new localStrategy({
  usernameField : 'email',
  passwordField : 'password'
}, async (email, password, done) => {
    try {
      //Save the information provided by the user to the the database
      const user = await UserModel.create({ email, password });
      //Send the user information to the next middleware
      return done(null, user);
    } catch (error) {
      done(error);
    }
}));

//Create a passport middleware to handle User login
passport.use('login', new localStrategy({
  usernameField : 'email',
  passwordField : 'password'
}, async (email, password, done) => {
  try {
    //Find the user associated with the email provided by the user
    const user = await UserModel.findOne({ email });
    if( !user ){
      //If the user isn't found in the database, return a message
      return done(null, false, { message : 'User not found'});
    }
    //Validate password and make sure it matches with the corresponding hash stored in the database
    //If the passwords match, it returns a value of true.
    const validate = await user.isValidPassword(password);
    if( !validate ){
      return done(null, false, { message : 'Wrong Password'});
    }
    //Send the user information to the next middleware
    return done(null, user, { message : 'Logged in Successfully'});
  } catch (error) {
    return done(error);
  }
}));
    ....

创建路线 (Creating the Routes)

Now that we have middleware for handling registration and login, let’s create routes that’ll use this middleware.

现在,我们有了用于处理注册和登录的中间件,让我们创建将使用该中间件的路由。

routes/routes.js
const express = require('express');
const passport = require('passport');
const jwt = require('jsonwebtoken');

const router = express.Router();

//When the user sends a post request to this route, passport authenticates the user based on the
//middleware created previously
router.post('/signup', passport.authenticate('signup', { session : false }) , async (req, res, next) => {
  res.json({ 
    message : 'Signup successful',
    user : req.user 
  });
});

    ...

签署JWT (Signing the JWT)

When the user logs in, the user information is passed to our custom callback which in turn creates a secure token with the information. This token is then required to be passed along as a query parameter when accessing secure routes(which we’ll create later).

当用户登录时,用户信息将传递到我们的自定义回调中,后者又使用该信息创建一个安全令牌。 然后,在访问安全路由(我们稍后将创建)时,需要将该令牌作为查询参数传递。

routes/routes.js
    ....
router.post('/login', async (req, res, next) => {
  passport.authenticate('login', async (err, user, info) => {     try {
      if(err || !user){
        const error = new Error('An Error occurred')
        return next(error);
      }
      req.login(user, { session : false }, async (error) => {
        if( error ) return next(error)
        //We don't want to store the sensitive information such as the
        //user password in the token so we pick only the email and id
        const body = { _id : user._id, email : user.email };
        //Sign the JWT token and populate the payload with the user email and id
        const token = jwt.sign({ user : body },'top_secret');
        //Send back the token to the user
        return res.json({ token });
      });     } catch (error) {
      return next(error);
    }
  })(req, res, next);
});

module.exports = router;

Note: We set { session : false } because we don’t want to store the user details in a session. We expect the user to send the token on each request to the secure routes. This is especially useful for API’s, but it’s not a recommended approach for web application for performance reasons.

注意 :我们设置{ session : false }是因为我们不想在会话中存储用户详细信息。 我们希望用户将每个请求的令牌发送到安全路由。 这对于API尤其有用,但出于性能原因,不建议将其用于Web应用程序。

验证用户令牌 (Verifying the user token)

So now we’ve handled user signup and login, the next step is allowing users with tokens access certain secure routes, but how do we verify that the token sent by the user is valid and hasn’t been manipulated in some way or just outright invalid. Let’s do that next.

因此,现在我们已经处理了用户注册和登录,下一步是允许具有令牌的用户访问某些安全路由,但是我们如何验证用户发送的令牌是否有效并且没有以某种方式或完全地被操纵无效。 让我们接下来做。

auth/auth.js
  ....
const JWTstrategy = require('passport-jwt').Strategy;
//We use this to extract the JWT sent by the user
const ExtractJWT = require('passport-jwt').ExtractJwt;

//This verifies that the token sent by the user is valid
passport.use(new JWTstrategy({
  //secret we used to sign our JWT
  secretOrKey : 'top_secret',
  //we expect the user to send the token as a query parameter with the name 'secret_token'
  jwtFromRequest : ExtractJWT.fromUrlQueryParameter('secret_token')
}, async (token, done) => {
  try {
    //Pass the user details to the next middleware
    return done(null, token.user);
  } catch (error) {
    done(error);
  }
}));

Note : If you’ll need extra or sensitive details about the user that are not available in the token, you could use the _id available on the token to retrieve them from the database.

注意:如果您需要令牌中不可用的有关用户的其他或敏感的详细信息,则可以使用令牌中可用的_id从数据库中检索它们。

创建安全的路线 (Creating secure routes)

Now lets create some secure routes that only users with verified tokens can access.

现在,让我们创建一些只有经过验证的令牌的用户才能访问的安全路由。

routes/secure-routes.js
const express = require('express');

const router = express.Router();

//Let's say the route below is very sensitive and we want only authorized users to have access

//Displays information tailored according to the logged in user
router.get('/profile', (req, res, next) => {
  //We'll just send back the user details and the token
  res.json({
    message : 'You made it to the secure route',
    user : req.user,
    token : req.query.secret_token
  })
});

module.exports = router;

So now we’re all done with creating the routes and authentication middleware, let’s put everything together and then test it out.

现在,我们已经完成了创建路由和身份验证中间件的工作,让我们将所有内容放在一起然后进行测试。

app.js

const express = require('express');
const mongoose = require('mongoose');
const bodyParser = require('body-parser');
const passport = require('passport');
const app = express();
const UserModel = require('./model/model');

mongoose.connect('mongodb://127.0.0.1:27017/passport-jwt', { useMongoClient : true });
mongoose.connection.on('error', error => console.log(error) );
mongoose.Promise = global.Promise;

require('./auth/auth');

app.use( bodyParser.urlencoded({ extended : false }) );

const routes = require('./routes/routes');
const secureRoute = require('./routes/secure-route');

app.use('/', routes);
//We plugin our jwt strategy as a middleware so only verified users can access this route
app.use('/user', passport.authenticate('jwt', { session : false }), secureRoute );

//Handle errors
app.use(function(err, req, res, next) {
  res.status(err.status || 500);
  res.json({ error : err });
});

app.listen(3000, () => {
  console.log('Server started')
});

与邮递员测试 (Testing with Postman)

Now that we’ve put everything together, let’s use postman to test our API authentication. First of all we’ll have to sign up with an email and password. We can send over these details through the Body of our request. When that’s done, click the send button to initiate the POST request.

Testing with Postman We can see the password is encrypted, therefore anyone with access to the database will have access to only the hashed password, we added ten(10) salt rounds to increase the security. You can read more about this
here. Let’s now login with the credentials and get our token. Visit the /login route, passing the email and password you used previously and then initiate the request.

现在,我们已经将所有内容放在一起,让我们使用邮差来测试我们的API身份验证。 首先,我们必须使用电子邮件和密码进行注册。 我们可以通过我们的请求Body发送这些详细信息。 完成后,点击发送按钮以启动POST请求。 我们可以看到密码是加密的,因此有权访问数据库的任何人都只能访问哈希密码,我们增加了十(10)个盐回合以提高安全性。 您可以在此处了解更多信息。 现在,使用凭据登录并获取我们的令牌。 访问/login路径,并传递您之前使用的电子邮件和密码,然后启动请求。

Now we have our token, we’ll send over this token whenever we want to access a secure route. Let’s try this by accessing a secure route user/profile, we’ll pass our token in a query parameter called secret_token, The token will be collected, verified and we’ll be given access to the route if it’s valid.

现在我们有了令牌,只要我们要访问安全路由,我们都会通过该令牌发送。 让我们通过访问安全的路由user/profile尝试,我们将令牌传递到名为secret_token的查询参数中,令牌将被收集,验证,并且如果有效,我们将获得对路由的访问权限。

As you can see, the valid token enables us gain access to the secure route. You could go ahead and try accessing this route but with an invalid token, the request will return an Unauthorized error.

如您所见,有效令牌使我们能够访问安全路由。 您可以继续尝试访问此路由,但是使用无效的令牌,该请求将返回Unauthorized错误。

结论 (Conclusion)

JSON web tokens provide a secure way for creating authentication for APIs. An extra layer of security can be added by encrypting all the information within the token, thereby making it even more secure. Some resources for learning about JWT’s in depth include :

JSON Web令牌为创建API身份验证提供了一种安全的方法。 可以通过加密令牌中的所有信息来增加额外的安全性,从而使其更加安全。 一些深入了解JWT的资源包括:

翻译自: https://www.digitalocean.com/community/tutorials/api-authentication-with-json-web-tokensjwt-and-passport

web api json

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值