JWT

常见的前后端鉴权方式

  • Session-Cookie
  • Token 验证(包括 JWT,SSO)
  • OAuth2.0(开放授权)

JWT

JWT(JSON Web Token)是一种开放的标准,用于在网络应用间传递信息的一种方式。它是一种基于JSON的安全令牌,用于在客户端和服务端之间传输信息。

基于token的鉴权机制是无状态的,它不需要在服务端去保留用户的认证信息或者会话信息。每个请求都是独立的,所有的认证信息包含在每个请求的请求头参数中,这就为应用的扩展提供了便利。

JWT由三部分组成,它们通过点(.)进行分隔:

  • Header(头部):包含了令牌的类型和使用的加密算法等信息。通常采用Base64编码表示。
  • Payload(负载):包含了身份验证和授权等信息,如用户ID、角色、权限等。也可以自定义其他相关信息。同样采用Base64编码表示。
  • Signature(签名):使用指定的密钥对头部和负载进行签名,以确保令牌的完整性和真实性。

JWT的工作流程如下:

  1. 用户通过提供有效的凭证(例如用户名和密码)来请求服务端进行身份验证
  2. 服务端验证凭证,并生成一个JWT作为响应。JWT包含了用户的身份信息和其他必要的数据
  3. 服务端将JWT发送给客户端
  4. 客户端在后续的请求中,将JWT放入请求的头部或其他适当的位置
  5. 服务端在接收到请求时,验证JWT的签名以确保其完整性和真实性。如果验证通过,服务端使用JWT中的信息进行授权和身份验证。

Token 和 JWT

相同

  • 都是访问资源的令牌
  • 都可以记录用户的信息
  • 都是使服务端无状态化
  • 都是只有验证成功后,客户端才能访问服务端上受保护的资源

区别

  • Token:服务端验证客户端发送过来的 Token 时,还需要查询数据库获取用户信息,然后验证 Token 是否有效。
  • JWT: 将 Token 和 Payload 加密后存储于客户端,服务端只需要使用密钥解密进行校验(校验也是 JWT 自己实现的)即可,不需要查询或者减少查询数据库,因为 JWT 自包含了用户信息和加密的数据。

安装使用的依赖

  • passport:passport是一个流行的用于身份验证和授权的Node.js库,该依赖库中有很多身份验证的方式,JWT只是其中之一。

  • passport-jwt:Passport-JWT是Passport库的一个插件,用于支持使用JSON Web Token (JWT) 进行身份验证和授权

  • jsonwebtoken:生成token的库

js 复制代码
npm i passport
npm i passport-jwt
npm i jsonwebtoken

passport

passport.js是Nodejs中的一个做登录验证的中间件,极其灵活和模块化,并且可与Express、Sails等Web框架无缝集成。passport功能单一,只能做登录验证,但非常强大,支持本地账号验证和第三方账号登录验证(OAuth和OpenID等),支持大多数Web网站和服务。

策略Strategy

策略是passport中最重要的概念。passport模块本身不能做认证,所有的认证方法都以策略模式封装为插件,需要某种认证时将该认证的passport插件添加到项目中即可。

策略模式是一种设计模式,它将算法和对象分离开来,通过加载不同的算法来实现不同的行为,适用于相关类的成员相同但行为但不同的场景。比如在passport中,认证所需的字段都是用户名、邮箱、密码等,但认证方法是不同的。依据策略模式,passport支持了众多的验证方案,包括Basic、Digest、OAuth(1.0,和2.0的三种实现)、Bearer等。

基本用法

local本地验证

本地验证默认使用用户名和密码来进行验证。在做验证之前,首先需要对策略进行配置,官方的示例如下:

js 复制代码
let passport = require('passport');
let LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
   function(username, password, done) {
    User.findOne({ username: username }, function(err, user) {
      if (err) { return done(err); }
      if (!user) {
        return done(null, false, { message: '用户名不存在.' });
      }
      if (!user.validPassword(password)) {
        return done(null, false, { message: '密码不匹配.' });
      }
      return done(null, user);
    });
  }
));

其中的User.findOne()是MongoDB风格的语法,意思是从数据库的User集合中查询一条数据,第一个参数是查询条件,后面是callback回调函数,一般在callback回调函数中进行后续操作。这里的逻辑很简单,依次检查usernamepassword,如果出错执行done(err)则返回错误信息,如果通过执行done(null,user)则返回user信息。上面的代码user.validPassword(password)方法用于验证密码是否正确,这并不是passport添加的,而是需要用户自定义。

配置策略

passport本地验证默认使用用户名和密码来验证,但实际上也可以用邮箱来验证,通过配置策略对象实现。

passport在策略配置里提供了options参数,用来设置要验证的字段名称,使用方法如下:

js 复制代码
passport.use(new LocalStrategy({
    usernameField: 'email',
    passwordField: 'passwd'
  },
  function(username, password, done) {
    // ...
  }
));

注意,这里要验证的字段名称应该是客户端传入的参数,即req.body.xxx,而不是user数据库中的字段名称。

验证回调

passport本身不处理验证,验证方法在策略配置的回调函数里由用户自行设置,它又称为验证回调。验证回调需要返回验证结果,这是由done()来完成的。

在passport.use()里面,done()有三种用法:

  • 当发生系统级异常时,返回done(err),这里是数据库查询出错,一般用next(err),但这里用done(err),两者的效果相同,都是返回error信息;
  • 当验证不通过时,返回done(null, false, message),这里的message是可选的,可通过express-flash调用;
  • 当验证通过时,返回done(null, user)。

将策略配置对象options作为LocalStrategy第一个参数传入,第二个参数是验证回调。

session序列化与反序列化

验证用户提交的凭证是否正确,是与session中储存的对象进行对比,所以涉及到从session中存取数据,需要做session对象序列化与反序列化。实现代码如下:

js 复制代码
// 将user.id序列化到session中,即sessionID,同时它将作为凭证存储在用户cookie中
passport.serializeUser(function(user, done) {
  done(null, user.id);
});
// 从session反序列化,参数为用户提交的sessionID,若存在则从数据库中查询user并存储到req.user中
passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});

session序列化与反序列化的执行顺序可以放在passport.use()的前面或后面,但需要在app.use(passport.initialize())之前。

Authenticate验证

js 复制代码
app.post('/login',
  passport.authenticate('local',
    { successRedirect: '/',
     failureRedirect: '/login',
     failureFlash: true }),
  function(req, res) {
    // 验证成功则调用此回调函数
    res.redirect('/users/' + req.user.username);
  });

这里的passport.authenticate()就是中间件,若通过就进入后面的回调函数,并且给res加上res.user,若不通过则默认返回401错误。

authenticate()方法有3个参数,第一是name,即验证策略的名称,第二个是options,包括下列属性:

  • session:Boolean,设置是否需要session,默认为true
  • successRedirect:String,设置当验证成功时的跳转链接
  • failureRedirect:String,设置当验证失败时的跳转链接
  • failureFlash:Boolean或者String,设置为Boolean时,express-flash将调用use()里设置的message。设置为String时将直接调用这里的信息。
  • successFlash:Boolean或者String,使用方法同上。

第三个参数是callback。注意如果使用了callback,那么验证之后建立session和发出响应都应该由这个callback来做,passport中间件之后不应该再有其他中间件或callback。以下是代码:

js 复制代码
var express = require('express');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
//User模型需自己实现
var User = require('../models/User');
var bcrypt = require('bcrypt');

passport.serializeUser(function(user, done) {
  done(null, user.id);
});

passport.deserializeUser(function(id, done) {
  User.findById(id, function(err, user) {
    done(err, user);
  });
});


passport.use(new LocalStrategy(
   function(username, password, done) {
    User.findOne({ username: username }, function(err, user) {
      if (err) { return done(err); }
      if (!user) {
        return done(null, false, { message: '用户名不存在.' });
      }
      if (!user.validPassword(password)) {
        return done(null, false, { message: '密码不匹配.' });
      }
      return done(null, user);
    });
  }
));
app.use(session({secret: "need change"}));
app.use(passport.initialize());
app.use(passport.session());
app.use(flash());

app.post('/login', passport.authenticate('local', function(err, user, info) {
    if (err) return next(err);
    if (!user) {
      req.flash('errors', { msg: info.message });
      return res.redirect('/login');
    }
    req.logIn(user, function(err) {
      if (err) return next(err);
      req.flash('success', { msg: '登录成功!' });
      res.redirect('/');
    });
  })(req, res, next)
);

OAuth验证

OAuth标准分为两个版本,1.0版和2.0版,两者被使用的都很广泛,passport通过passport-oauth为两者提供支持,使用下面的命令可以安装。

js 复制代码
npm install passport-oauth

OAuth验证流程

OAuth1.0和2.0的使用流程都差不多,一般来说如下:

  1. 为自己的app去第三方服务商处申请标识和令牌appkey和secret;
  2. 在自己的app里添加按钮或链接,将用户引导至服务商的授权页,用户在这里选择授权给自己的app;
  3. 授权成功后跳转回自己的app,同时还传递回access_token和一些用户资料。

到这里首次验证流程就完成了,之后只要拿access_token去就可以做登录验证或者其他事了。

OAuth1.0

要使用passport OAuth1.0验证需要先引入然后进行配置

js 复制代码
let passport = require('passport');
let OAuthStrategy = require('passport-oauth').OAuthStrategy;
// 配置
passport.use('provider', new OAuthStrategy({
    requestTokenURL: 'https://www.provider.com/oauth/request_token',
    accessTokenURL: 'https://www.provider.com/oauth/access_token',
    userAuthorizationURL: 'https://www.provider.com/oauth/authorize',
    consumerKey: '123-456-789',
    consumerSecret: 'shhh-its-a-secret'
    callbackURL: 'https://www.example.com/auth/provider/callback'
  },
  function(token, tokenSecret, profile, done) {
    User.findOrCreate(..., function(err, user) {
      done(err, user);
    });
  }
));

这里比通用流程多的一点就是,自己的App需要先访问第三方服务,获取request token,这个request token是未授权的,等用户授权之后,可以拿这个request token去换取access token。在passport中不必管这些细节,找到第三服务的文档找到对应的URL添上即可。在此之前还得申请key和secret。

use方法的回调接受四个参数,token就是access token,和tokenSecret一起好好保存。profile则是用户在第三方服务上的一些公开资料,它的模型如下,不过返回的资料不一定全面,在使用前需要验证是否存在。

js 复制代码
class UserProfile{
    // 用户进行身份验证的提供者(facebook、twitter等)
    provider:string
    // 用户的唯一标识符,由服务提供者生成
    id:string
    // 该用户的名称,用于显示
    displayName:string
    name:object{
        familyName:string, // 用户的姓
        givenName:string, // 用户的名
        middleName:string // 用户的中间名
    }
    emails:srting[][
        value, // 实际的电子邮件地址
        type // 电子邮件地址的类型(家庭、工作等)]
    photos:string[][
        url // 图片的地址]
}

OAuth1.0的路由常见写法如下:

js 复制代码
app.get('/auth/provider', passport.authenticate('provider'));
app.get('/auth/provider/callback', passport.authenticate('provider', 
    { 
        successRedirect: '/',
        ailureRedirect: '/login'
    })
);

OAuth1.0主要是一些比较早提供第三方登录功能的网站使用,现在的网站大部分使用OAuth2.0

OAuth2.0

OAuth2.0的验证不需要request_token,但比1.0多了scope和refresh token,具体的配置方法:

js 复制代码
let passport = require('passport')
let OAuth2Strategy = require('passport-oauth').OAuth2Strategy;

passport.use('provider', new OAuth2Strategy({
    authorizationURL: 'https://www.provider.com/oauth2/authorize',
    tokenURL: 'https://www.provider.com/oauth2/token',
    clientID: '123-456-789',
    clientSecret: 'shhh-its-a-secret'
    callbackURL: 'https://www.example.com/auth/provider/callback'
  },
  function(accessToken, refreshToken, profile, done) {
    User.findOrCreate(..., function(err, user) {
      done(err, user);
    });
  }
));

refreshToken是重新获取access token的方法,因为access token是有使用期限的,到期了必须让用户重新授权才行,现在有了refresh token,可以让应用定期的用它去更新access token,这样第三方服务就可以一直绑定了。不过这个方法并不是每个服务商都提供,注意看服务商的文档。

下面是路由,OAuth2.0也有一点不同:

js 复制代码
app.get('/auth/provider',passport.authenticate('provider', { scope: 'email' }));
app.get('/auth/provider/callback', passport.authenticate('provider', 
    { 
        successRedirect: '/',
        failureRedirect: '/login'
     })
);

scope是权限范围,需要在服务商处事先申请,可参考微博的scope文档。它可以只有一项,也可以有多项,当为多项时以数组形式表示。

scope 是 OAuth2.0 授权机制中 authorize 接口的一个参数。通过 scope 参数,平台可以提供更多的能力给开发者,同时用户也可以有选择的授权或者不授权不同的能力给第三方,即保障了用户对于隐私保护的需求,也提升了用户使用第三方应用的体验。

passport-jwt用法

配置策略

js 复制代码
import { Strategy, ExtractJwt } from 'passport-jwt'
// new Strategy(options, verify)
let opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = 'secret';
opts.issuer = 'accounts.examplesoft.com';
opts.audience = 'yoursite.net';
new Strategy(opts, function(jwt_payload, done) {
    User.findOne({id: jwt_payload.sub}, function(err, user) {
        if (err) {
        // passport错误信息,false不通过验证
            return done(err, false);
        }
        if (user) {
            return done(null, user);
        } else {
            return done(null, false);
        }
    });
})
  • options:第一个参数是一个对象,包含用于控制如何从请求中提取令牌或者被验证的选项的对象文本
  • verify:第二个参数是一个验证token的回调函数

options配置对象属性如下:

  • secretOrKey:包含加密( 对称) 或者 public 编码密钥( 非对称)的字符串或者缓冲区,用于验证令牌的签名。除非提供 secretOrKeyProvider,否则需要
  • secretOrKeyProvider:是一个回调函数,格式为function secretOrKeyProvider(request, rawJwtToken, done) 应该为给定密钥和请求调用一个密码或者调用 done函数并传入public 密钥( 非对称)。 done 以 function done(err, secret) 格式接受参数。 除非提供 secretOrKey,否则需要
  • jwtFromRequest:字符串或者null,是请求中提取的JWT。从请求中提取 JWT的方法详情如下。
  • issuer: 如果定义了令牌颁发者,将根据这个值验证
  • audience: 如果定义了,将根据该值验证令牌
  • algorithms: 带允许算法名称的字符串列表, 例如:HS256和HS384
  • ignoreExpiration: 如果 true 不验证令牌的到期时间
  • passReqToCallback: 如果 true,请求将被传递到第二个参数即验证回调函数
  • jsonWebTokenOptions: passport-jwt将使用 jsonwebtoken 验证令牌

verify验证回调函数的参数如下:

  • jwt_payload:是包含解码的JWT负载的对象文字
  • done:用于控制鉴权的成功或者失败,回调函数接受两个参数,第一个参数是错误信息

从请求中提取 JWT 的方法

passport-jwt的ExtractJwt类中提供了许多提取器工厂函数。

  • ExtractJwt.fromHeader(header_name):创建一个新的提取器,它在给定的http头中查找 JWT
  • ExtractJwt.fromBodyField(field_name):创建一个新的提取器,它在给定的主体字段中查找 JWT,必须配置了主体解析器才能使用该方法
  • ExtractJwt.fromUrlQueryParameter(param_name):创建一个新的提取器,它在给定的URL查询参数中查找 JWT
  • ExtractJwt.fromAuthHeaderWithScheme(auth_scheme):为在授权标头中查找JWT创建一个新的提取器,期望该方案匹配 auth_scheme。
  • ExtractJwt.fromAuthHeaderAsBearerToken():创建一个新的提取器,该提取器在请求头在中查找授权字段的token
  • ExtractJwt.fromExtractors([array of extractor functions]):使用提供的提取器的array 创建一个新的提取器,每个提取器都按顺序尝试,直到返回一个标记。

jsonwebtoken

生成token方法

js 复制代码
// jwt.sign(保存的信息, 口令, 参数)
import jsonwebtoken from 'jsonwebtoken';
jsonwebtoken.sign(data, secret, { expiresIn: '7d' });
  • data:第一个参数是保存的信息
  • secret:第二个参数是加密口令,加密的时候混入信息使用,解密的时候还要这个口令
  • 参数对象:第三个参数是一个对象,expiresIn表示过期时间,单位为秒;可以使用7d表示7天,1h表示1小时

解码token方法

js 复制代码
// jwt.verify(要解析的 token, 口令, 回调函数)
import jsonwebtoken from 'jsonwebtoken';
jsonwebtoken.verify(token, secret, (err, data) => {
    if (err && err.message === 'invalid token') return res.send({ message: '无效 token', code:0})
    if (err && err.message === 'jwt expired') return res.send({ message: 'token 失效', code: 0})
    next()
})
  • token: 必须是一个指定的 token
  • 口令: 必须是加密时候的口令
  • 回调函数: 接收结果

项目架构

  • jwt目录用于封装token的初始化、生成和验证等方法
  • types目录的index.d.ts是全局声明

.d.ts文件是ts用来声明变量,模块,type,interface等的。

.d.ts后缀的ts文件声明这些东西和在纯ts文件声明这些东西的区别如下:

在.d.ts声明变量或者模块等东西之后,在其他地方可以不用import导入这些东西就可以直接用,用时且有语法提示。 但是也不是说创建了.d.ts文件,里面声明的东西就能生效了,毕竟归根到底也是.ts文件,需要预编译,所以需要在tsconfig.json文件里面的include数组里面添加这个文件。include数组里面可以不用写.d.ts文件的绝对路径,可以通过glob通配符,匹配这个文件所在的文件夹或者是"祖宗级别"文件夹。

支持的glob通配符有:

  • *:匹配0个或多个字符(不包括目录分隔符)
  • ?:匹配一个任意字符(不包括目录分隔符)
  • **:递归匹配任意子目录

使用files配置 files属性用来指定要编译的文件,可以配置一个数组列表,里面包含指定文件的相对或绝对路径,编译器在编译的时候只会编译包含在files中列出的文件,如果不指定,则取决于有没有设置include选项。如果没有include选项,则默认会编译根目录以及所有子目录中的文件。files列出的路径必须是指定文件,而不是某个文件夹。

include属性用来指定要编译要包含的路径列表,和files的区别在于,这里的路径可以是文件夹,也可以是文件,可以使用相对和绝对路径。

使用ts-node运行ts文件时,会默认忽略.d.ts的后缀文件编译,会报错找不到引用类型。解决方案是使用ts-node --files xx.ts

代码实现

js 复制代码
// src/jwt/index.ts
import { injectable } from 'inversify';
import passport from 'passport';
import jsonwebtoken from 'jsonwebtoken';
import { Strategy, ExtractJwt } from 'passport-jwt'
@injectable()
export class JWT {
    private secret = 'goldenstar&gloria-queens'; // 加密口令
    private jwtOptions = {
        jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // 从请求中提取JWT的token
        secretOrKey: this.secret // 口令
    };
    constructor() {
        this.strategy();
    }
    // 中间件,中间件用于jwt验证
    static middleware() {
        return passport.authenticate('jwt', { session: false })
    }
    // 初始化jwt
    public strategy() {
        // 使用passport-jwt,它是passport一个使用jsonwebtoken的一种插件
        const strategy = new Strategy(this.jwtOptions, (payload, done) => {
            // payload是负载,done用于控制鉴权的成功或者失败
            done(null, payload)
        });
        // 在passport中注册使用passport-jwt插件
        passport.use(strategy);
    }
    // 生成token的方法
    public createToken(data: object) {
        // 接受三个参数,第一个是Payload负载,是自定义的相关信息,可以包含身份验证和授权等信息,如用户ID、角色、权限等。
        // 第二个参数是私钥secret
        // 第三个参数是设置token的过期时间,1h表示1小时,7d表示7天
        return jsonwebtoken.sign(data, this.secret, { expiresIn: '7d' });
    }

    // 将passport和express进行关联,初始化passport
    public init() {
        return passport.initialize();
    }
}
js 复制代码
// main.ts
// 作为装饰器的基础,支持反射,放在最顶层
import 'reflect-metadata';
// 引入搭建服务端的express的类,InversifyExpressServer可以快速的创建express应用程序
import { InversifyExpressServer } from 'inversify-express-utils';
// 配置inversify容器,实现依赖注入
import { Container } from 'inversify';
// 引入express
import express from 'express';
// 引入prisma ORM框架
import { PrismaClient } from '@prisma/client';
// 引入User模块的controller
import { UserController } from './src/user/controller';
// 引入User模块的services
import { UserService } from './src/user/service';
// 引入封装好的数据库PrismaDatabase
import { PrismaDatabase } from './src/db';
// 引入封装好的JWT用于鉴权的类
import { JWT } from './src/jwt';
// 创建容器对象,这个对象会自动创建项目所需的对象
const container = new Container();
// 在容器对象中绑定封装的prisma工厂类,注入的实际是工厂创建器这个函数,容器对象会执行注入的函数,返回工厂函数
container.bind<PrismaClient>('PrismaClient').toFactory(() => {
    // 工厂创建器函数,不需要使用context上下文对象
    return () => {
        // 工厂函数返回PrismaClient的实例化对象
        return new PrismaClient(); // 创建prisma实例化对象
    }
});
// 具体类型绑定自身,将依赖类注入到容器中,在容器中会创建依赖类的实例对象并存储在容器的实例对象中
// 将User模块的controller和User模块的services添加到容器中,就可以实现services和controller之间的依赖注入,controller依赖于services
container.bind<UserController>(UserController).toSelf();
container.bind<UserService>(UserService).toSelf();
container.bind<PrismaDatabase>(PrismaDatabase).toSelf();
container.bind<JWT>(JWT).toSelf();
// 创建 Express 服务器,需要接受一个容器对象
const sever = new InversifyExpressServer(container);
// 在.setConfig方法中进行express的中间件注册
sever.setConfig(app => {
    // 注册express中间件,以便接受和解析前端请求时传入的json数据
    app.use(express.json());
    // 将passport注册为express的中间件,如此express和passport进行了关联
    // 从容器实例对象中读取JWT类的实例对象,并执行JWT类的init方法,初始化passport并集成到express中
    app.use(container.get(JWT).init())
});
// 所有注册的控制器和中间件添加到express应用程序并返回应用程序实例
const app = sever.build();
// 启动服务
app.listen(8089, () => {
    console.log('8089 is running');
})
js 复制代码
// src/user/controller.ts
// 引入装饰器将类设置注册为路由控制器
import { controller, httpGet, httpPost as Post } from 'inversify-express-utils'
// 引入inject装饰器,将controller层标注为依赖类,其参数是被依赖的services层
import { inject } from 'inversify'
// 引入services层
import { UserService } from './service';
// 引入类,用于变量的类型定义
import type { Request, Response } from 'express'
import { JWT } from '../jwt';
@controller('/user') //路由
export class UserController {
    // 属性的修饰符是private readonly,属性名是userService,属性类型是UserService
    private readonly userService: UserService;
    private readonly jwt: JWT;
    constructor(@inject(UserService) userService: UserService, @inject(JWT) jwt: JWT) {
        // User类的构造函数中实例化依赖的services层的UserService类
        this.userService = userService;
        // 依赖声明
        this.jwt = jwt;
    }

    // get请求接口,获取所有用户的信息,传入JWT.middleware()表示该接口使用传入的中间件
    // 该接口就会去验证是否携带token,没有携带token就返回401
    // 装饰器的第一个参数是路径,第二个参数是该路径需要用到的中间件
    @httpGet('/userInfo',JWT.middleware())
    public async getUserInfo(req: Request, res: Response) {
        let result = await this.userService.getUserList();
        res.send({
            code: 200,
            msg: '查询成功',
            data: result
        })
    }
    // post请求接口,添加一条用户信息
    @Post('/create')
    public async createUser(req: Request, res: Response) {
        // req用于接受前端出入的参数
        const info = req.body;
        let result = await this.userService.addUser(info)
        // res用于给前端返回数据
        res.send(result);
    }

    //  登录接口
    @Post('/login')
    public async userLogin(req: Request, res: Response){
        let result = await this.userService.loginHandle(req.body);
        if(result.code == 200){
            res.send({
                success:true,
                msg:'登陆成功',
                token:this.jwt.createToken(result.user) // 生成token
            });
        }else if(result.code == 400){
            res.send({
                success:true,
                msg:'用户不存在'
            });
        }else if(result.code == 500) {
            res.send({
                success:true,
                msg:'密码错误'
            });
        }
    }
}

哪个接口需要token验证就把jwt验证的中间件往那个接口加就可以了

js 复制代码
// src/user/service.ts
// 引入注入装饰器,作用是将修饰的类变成一个可以注入类的可依赖类
import { injectable, inject } from 'inversify'
import { UserDto } from './user.dto'
import { plainToClass } from 'class-transformer' //dto验证
import { validate } from 'class-validator' //dto验证
import { PrismaDatabase } from '../db'
@injectable()
export class UserService {

    private readonly prismaDataBase: PrismaDatabase;
    constructor(@inject(PrismaDatabase) prismaDataBase: PrismaDatabase ) {
        // 依赖声明
        this.prismaDataBase = prismaDataBase;
    }

    public async getUserList() {
        // 逻辑处理后的数据,将处理后的结果返回
        return await this.prismaDataBase.prisma.user.findMany()
    }

    public async addUser(userData: UserDto) {
        // userData接受前端传入的参数
        const user = plainToClass(UserDto, userData);
        const errors = await validate(user);
        if (errors.length != 0) {
            // 错误信息数组有值,验证未通过
            return errors
        } else {
            // 逻辑处理后的数据
            let result = await this.prismaDataBase.prisma.user.create({
                data: user
            });
            // 将处理后的结果返回
            return result
        }

    }

    public async loginHandle(userInfo:UserLogin){
        const passworld = 'admin123';
        if(userInfo.passworld == passworld){
            let result = await this.prismaDataBase.prisma.user.findMany({
                where:{
                    name:userInfo.name
                }
            });
            if(result.length != 0){
                return {
                    code:200,
                    user:result[0]
                };
            }else{
                return {
                    code:400
                };
            }
        }else {
            return {
                code:500
            };
        }    
    }
}
js 复制代码
// types/index.d.ts
type UserLogin = {
    name:string;
    passworld:string
}

request.http测试接口:

js 复制代码
GET http://localhost:8089/user/userInfo HTTP/1.1

token验证失败展示:(请求接口时未携带token)

js 复制代码
POST  http://localhost:8089/user/login HTTP/1.1
Content-Type: application/json

{
    "name":"gloria",
    "passworld":"admin123"
}

密码错误展示:

用户名错误展示:

获取token展示:

Bearer后面需要添加一个空格,然后是token

js 复制代码
GET   http://localhost:8089/user/userInfo HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwibmFtZSI6Imdsb3JpYSIsImVtYWlsIjoiaW5mb0BnbmF0aW9uLmhrIiwiaWF0IjoxNzE2MzQxNDgyLCJleHAiOjE3MTY5NDYyODJ9.r0Z3G9-qajx67T8pp97K68-vKX5rfQc73sSCjGofPfU

token验证成功展示:(请求接口时携带token)

从token中获取保存的信息

当接口需要token验证并添加了jwt验证的中间件时,就可以直接通过req.user获取用户信息。

需要先安装passport的ts声明模块,这样才会有类型提示

js 复制代码
yarn add @types/passport

User接口可进行类型扩展

扩充后有类型提示

js 复制代码
GET   http://localhost:8089/user/userInfo HTTP/1.1
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MSwibmFtZSI6Imdsb3JpYSIsImVtYWlsIjoiaW5mb0BnbmF0aW9uLmhrIiwiaWF0IjoxNzE2MzQxNDgyLCJleHAiOjE3MTY5NDYyODJ9.r0Z3G9-qajx67T8pp97K68-vKX5rfQc73sSCjGofPfU
相关推荐
儒雅的烤地瓜1 小时前
JS | JS中判断数组的6种方法,你知道几个?
javascript·instanceof·判断数组·数组方法·isarray·isprototypeof
道爷我悟了1 小时前
Vue入门-指令学习-v-on
javascript·vue.js·学习
27669582921 小时前
京东e卡滑块 分析
java·javascript·python·node.js·go·滑块·京东
PleaSure乐事1 小时前
【Node.js】内置模块FileSystem的保姆级入门讲解
javascript·node.js·es6·filesystem
雷特IT2 小时前
Uncaught TypeError: 0 is not a function的解决方法
前端·javascript
awonw2 小时前
[前端][easyui]easyui select 默认值
前端·javascript·easyui
老齐谈电商2 小时前
Electron桌面应用打包现有的vue项目
javascript·vue.js·electron
柏箱3 小时前
使用JavaScript写一个网页端的四则运算器
前端·javascript·css
一颗花生米。6 小时前
深入理解JavaScript 的原型继承
java·开发语言·javascript·原型模式
学习使我快乐016 小时前
JS进阶 3——深入面向对象、原型
开发语言·前端·javascript