Egg implements user registration and login and uses jwt for token authentication

Table of contents

What is user authentication

Register interface

login interface

jwt decryption token


In a background service, user authentication is a key point. Almost all requirements are designed around the user (token), so user authentication is also a course that we must pay attention to. In this chapter, we will use egg-jwt to achieve middleware for user authentication

What is user authentication

Quoting Baidu Encyclopedia's definition of "user authentication":

User Authentication, a method for authenticating a user attempting to access a service from a service provider in a communication network. When a user logs in to DSMP or uses data services, the service gateway or Portal sends this message to DSMP to check the legality and validity of the user's use of data services (whether the status is activated).

Simplify complex things. To put it simply, authentication means that Appwhen , through an agreed method, the webpage and the user establish a mutual trust mechanism, and then return the information required by the user.

There are four authentication mechanisms:

  • HTTP Basic Authentication

  • session-cookie

  • Token Token

  • OAuth (Open Authorization)

Generally speaking, most of our backgrounds use the token token model, and tokens can also be used in fields such as web pages, clients, applets, and browser plug-ins. If cookie authentication is used, this interface cannot be used under the client and applet. Because they have no concept of domain. The cookie is to exist in a certain domain.

Register interface

People who have developed background or small programs will know that the general process of registration interface is as follows:

Open the page - enter the user name and password - click register - the server will judge whether there is a duplicate after receiving it - save the user name and password into the user table if there is no duplicate - return the registration success message

First, we need to create a new egg project. If you don't remember, you can read the first chapter.

Then we need to configure the mysql plug-in. In config/config.default.js, change the database to our own database and the corresponding user information table.

My user table here is called user, and the content of the table is as follows:

 

Other project configurations are similar to the previous chapters, so remember to check them out if you forgot. I won't say much here.

Remember to download the egg-mtsql plugin before configuring mysql.

officially written

According to the same as at the beginning, we know that when registering, the front end will pass the user name and password to the background, so we need to get these two values ​​​​on the server

  1. We need to create a new user.js in the controller directory to write related codes.

// controller/user.js
​
const Controller = require('egg').Controller;
​
class UserController extends Controller {
  async register() {
    const { ctx } = this;
    const { username, password } = ctx.request.body; // 获取注册需要的参数
  }
}
​
module.exports = UserController;

Determine whether username and password are empty

if (!username || !password) {
  ctx.body = {
    code: 500,
    msg: '账号密码不能为空',
    data: null
  }
  return
}
  1. Judging whether the name is the same, the judgment of the same name is divided into two steps.

The first step is to write a service, because we need to query the interface to see if there is such a value.

The second part is about the controller, which is used for judgment.

// service/user.js
​
const Service = require('egg').Service;
​
class UserService extends Service {
  // 通过用户名获取用户信息
  async getUserByName(username) {
    const { app } = this;
      try {
        const result = await app.mysql.get('user', { username });
        return result;
      } catch (error) {
        console.log(error);
        return null;
      }
  }
}
module.exports = UserService;

// controller/user.js
async register() {
  ...
  // 验证数据库内是否已经有该账户名
  const userInfo = await ctx.service.user.getUserByName(username) // 获取用户信息
​
  // 判断是否已经存在
  if (userInfo && userInfo.id) {
    ctx.body = {
      code: 500,
      msg: '账户名已被注册,请重新输入',
      data: null
    }
    return
  }
}

After two layers of judgment, we can insert the username and password into the database.

// controller/user.js
​
const defaultAvatar = '默认图片地址'
// 调用 service 方法,将数据存入数据库。
const result = await ctx.service.user.register({
  username,
  password,
  signature: '个性签名',
  avatar: defaultAvatar,
  ctime: new Date(),
});
​
if (result) {
  ctx.body = {
    code: 200,
    msg: '注册成功',
    data: null
  }
} else {
  ctx.body = {
    code: 500,
    msg: '注册失败',
    data: null
  }
}
  1. Supplementary service insert database method

async register(params) {
  const { app } = this;
  try {
    const result = await app.mysql.insert('user', params);
    return result;
  } catch (error) {
    console.log(error);
    return null;
  }
}
  1. Remember to write the request interface address, router.

 router.post('/api/user/register', controller.user.register);

After completing the above, we can use postman to try

 

Call the interface, it shows success, let's look at the database again

 

also inserted.

At the same time, we can try the previous two judgments.

The first judgment is that any one of username and password is empty

After setting the password to empty, the return is correct.

 

Come down and judge the user name duplication problem.

Because the data with the same username has been inserted before, inserting it again will show that it already exists. Two judgments end.

The registration interface ends.

login interface

After completing the registration, the user needs to log in with the successful user name and password, which is our login process. After successful login, a token will be returned. For this token, we combine the egg-jwt plug-in and add our own Defined encrypted string generation.

Generally speaking, after the front-end obtains the token, it will be saved in the front-end (locally in the browser), but it must have an expiration time. Usually we set the expiration time to 24 minutes. If it is not some information-sensitive website or app, we It can be set for a longer time.

The token is that every time a request is made, no matter whether to obtain data or submit data, we need to bring the token to identify which user's behavior this request is for.

A feature of egg-jwt is that it can encrypt and generate tokens, and can also decrypt tokens to obtain user information. The token is probably the basic information of the logged-in user.

For example, your login user username and password are

{
    userName:'赵小左',
    password: '123',
}

Then, your token will contain the above user information, and the decryption will naturally correspond to the basic information that the username is equal to Zhao Xiaozuo.

Install egg-jwt

npm install egg-jwt -S

add plugin to egg

// config/plugin.js
​
jwt: {
  enable: true,
  package: 'egg-jwt'
}

Configure a custom encrypted string

// config/config.default.js

config.jwt = {
  secret: 'Nick',
};

secretThe encrypted string will be used later to combine user information to generate a string token. secretIt is placed in the server code, and ordinary users cannot find it through the browser, so it must not be leaked, otherwise it may be used by people with malicious intentions.

Add login method for request response

There are two conditions to be judged in login.

The first is whether the username exists

The second is whether the password is correct

// controller/user.js
​
async login() {
    // app 为全局属性,相当于所有的插件方法都植入到了 app 对象。
    const { ctx, app } = this;
    const { username, password } = ctx.request.body
    // 根据用户名,在数据库查找相对应的id操作
    const userInfo = await ctx.service.user.getUserByName(username)
    // 没找到说明没有该用户
    if (!userInfo || !userInfo.id) {
      ctx.body = {
        code: 500,
        msg: '账号不存在',
        data: null
      }
      return
    }
    // 找到用户,并且判断输入密码与数据库中用户密码。
    if (userInfo && password != userInfo.password) {
      ctx.body = {
        code: 500,
        msg: '账号密码错误',
        data: null
      }
      return
    }
}
After the judgment, you can start the formal login
async login () {
  ...
  // 生成 token 加盐
  // app.jwt.sign 方法接受两个参数,第一个为对象,对象内是需要加密的内容;第二个是加密字符串,上文已经提到过。
  const token = app.jwt.sign({
    id: userInfo.id,
    username: userInfo.username,
    exp: Math.floor(Date.now() / 1000) + (24 * 60 * 60) // token 有效期为 24 小时
  }, app.config.jwt.secret);
  
  ctx.body = {
    code: 200,
    message: '登录成功',
    data: {
      token
    },
  };
}

The reason why app.jwt.sign is used here is that app is familiar with the global context. We can get the plug-ins of config and the attributes in config.default.js through app.xxx and app.config.xx.

When we log in, we combine the id and username attributes of userinfo with the jwt.secret encrypted string through the sign method of jwt to generate a token, which will be a very long string.

After completing the above, we can throw the login interface api in the router.

 router.post('/api/user/login', controller.user.login);

We can open postman to call and try

The first step is the same as before, let’s verify two judgments first.

  • Judgment that the user name does not exist (input the wrong user name)

  •  

Verification passed

  • Password error judgment (wrong password entered)

Also verified. Then we will enter the official account password for testing

Enter the official user name and password, return token, the system login is successful, and the interface verification is passed.

jwt decryption token

Above we used login to complete the token generation. At this time, if we have an interface that requires a token to log in, how can we parse the token to obtain user information after logging in?

We add a new method in the controller you can call it test

// controller/user.js
​
async test() {
    const {ctx,app} = this;
    const token = ctx.request.header.token; // 获取header 的token
    const decode = app.config.jwt.verify(token, app.config.jwt.secret);
    ctx.body = {
        code: 200,
        msg: '成功',
        data:{
            ...decode
        }
    }
} 

Don't forget to add the router method

router.get('/api/user/getinfo', controller.user.text)

Finally, use postman to call

fully revealed.

Implement authentication middleware

In the above, we have successfully realized the token and parsed it.

Of course, one of the authentication we want is that every interface that requires a token needs authentication.

We can't use if else as rigidly as before. In this way, you have to judge as many interfaces as you write. In this way, the first is rigidity, as many judgment tokens as there are written in our interface. The second is that it is not easy to expand. What if our token changes later, do we have to change every interface?

then what should we do? Implement authentication middleware. Every time an interface is requested, it is first judged by the middleware

New middleware middleware

We first create a new folder middleware under the app of the project, and add the jwtErr.js file below

 

Secondly, we add the judgment method in jwtErr.js.

module.exports = (secret) => {
  return async function jwtErr(ctx, next) {
    const token = ctx.request.header.token; // 若是没有 token,返回的是 null 字符串
    let decode
    if(token != 'null' && token) {
      try {
        decode = ctx.app.jwt.verify(token, secret); // 验证token
        await next();
      } catch (error) {
        console.log('error', error)
        ctx.status = 200;
        ctx.body = {
          msg: 'token已过期,请重新登录',
          code: 401,
        }
        return;
      }
    } else {
      ctx.status = 200;
      ctx.body = {
        code: 401,
        msg: 'token不存在',
      };
      return;
    }
  }
}

First, the middleware throws a function by default, which returns an asynchronous method jwtErr. jewErrThe method has two parameters ctxwhich are the context, and ctxthe global object can be obtained in app.

First, pass ctx.request.header.token 获取到请求头中的token 属性,它便是我们请求接口是携带的token 值,如果没有携带null if token ctx.app.jwt.verify token await next() ,该值为字符串token` .。我们通过语句判断如果有的情况下,使用方法验证该是否存在并且有效,如果是存在且有效,则通过验证继续执行后续的接口逻辑。否则判断是失效还是不存在该

Then we configure it on the router

// app/router.js
module.exports = app => {
    const { router,controller,middleware } = app;
    const _jwt = middleware.jwtErr(app.config.jwt.secret); 传入加密字符串
    
    router.post('/api/user/getinfo', _jwt, controller.user.test ) // / 放入第二个参数,作为中间件过滤项。判断第二个参数的接口是否有jwt。可以在进入接口逻辑之前就进行判断
}

You can try to run it on postman

  • First imitate the interface with token

 

displayed correctly. Next, let's imitate the interface without token

 

The display is also correct. So authentication is what it is. We add the second parameter to the router and add _jwtthe method , so that user permissions can be judged before entering the interface logic. Subsequent interfaces that require jwt judgment are as shown above.

Guess you like

Origin blog.csdn.net/qq_31281245/article/details/127922062