Passport Passport-jwt 实现安全验证

news2024/10/6 0:35:21

目录

Passport

认证请求

策略

验证回调

会话

中间件

禁止 Sessions

自定义回调

passport-jwt

安装

使用

配置策略

认证请求

在请求中包含JWT


Passport

Passport 是 NodeJS 的认证中间件。他的唯一设计目的是:验证请求。书写模块化的、封装代码是一种美德,所以 Passport 将除了验证请求之外的功能都分发给应用程序来实现。关注点分离使代码能够更加整洁、可维护,同时也使 Passport 能够极易集成到应用中。

现在 Web 程序,认证有多种形式。传统的,用户通过用户名、密码登录。随着社交网络使用上升,使用 OAuth 的单点登录,例如 Facebook 或者 Twitter 已经成为了一种流行的认证方式。暴露一个 API 的服务通常需要基于 token 的证书来保护访问。

Passport 认识到每个应用有自己独特的认证需求。认证机制,也被成为策略,被打包成单独的模块。应用能够选择采用的策略,无需创建不需要的依赖。

不管认证的复杂性,代码能够不变的复杂。

安装

$ npm install passport
认证请求

Passport 提供了一个 authenticate() 函数,它作为路由中间件用于认证请求。

认证请求就像调用 passport.authenticate() 和指定采用哪种策略一样简单。authenticate() 的函数签名是标准的 Connect 中间件,所以能够方便的作为路由中间件在 Express 应用中使用。

app.post("/login", passport.authenticate("jwt"), function (req, res) {
  // 如果这个函数被调用,说明认证成功。
  // `req.user` 包含认证的 user
  res.redirect("/user", +req.user.username);
});

默认情况下,如果认证失败,Passport 将返回 401 Unauthorized,后续其他的路由处理器将不会执行。如果认证成功,下个处理器将调用,req.user 属性将设置为认证 user。

注意:在路由使用策略时,一定要预先配置。继续阅读详细配置 todo 章节。

策略

Passport 使用被称为策略的东西来认证请求。策略从验证用户名、密码,使用 OAuth 委托认证或者使用 OpenID 联合认证。

在让 Passport 认证请求前,应用使用的某个策略(或者某些策略)必须要先配置。

策略和他们的配置通过 use() 函数提供。例如,下面的例子使用passport-jwt 来进行tokne认证。

// 引入JWT策略和请求提取工具以及用户模型
const JwtStrategy = require("passport-jwt").Strategy,
  ExtractJwt = require("passport-jwt").ExtractJwt,
  UserService = require("../services/UserService");

// 初始化配置对象
opts = {};
// 配置从请求头中提取JWT的方法
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
// 配置JWT的密钥
opts.secretOrKey = "贾公子";

// 配置passport使用JWT策略
module.exports = (passport) => {
  passport.use(
    new JwtStrategy(opts, async (jwt_payload, done) => {
      // 尝试根据JWT载荷中的用户ID查找用户
      try {
        let user = await UserService.findById(jwt_payload._id);
        if (!user) return done(null, false);
        return done(null, user);
      } catch (error) {
        console.log(error);
      }
    })
  );
};

有480多个策略。你可以在 passportjs.org 找到你想要的策略。

验证回调

这个例子引入了一个重要的概念。策略需要一个称为回调的东西。验证回调的目的是找到拥有一套凭证的用户。

当 Passport 认证请求时,它解析请求中的凭证。然后将凭证作为调用回调函数的参数,这个例子中就是 token。如果凭证有效,回调函数将调用 done 将已认证的用户的信息传入 Passport

return done(null, user);

如果验证失败(本例中,比如token错误),done 函数应该传入 false 而不是用户信息来表明认证失败 

return done(null, false);

可以提供额外的消息来表明失败原因。这对于展示即时消息,来提示用户再次尝试很有用。 

return done(null, false, { message: '密码错误' });

最后,当验证凭证时发生异常(例如,数据库服务不可用),在常规的 Node 操作中 done 应该被调用来传入错误信息。

return done(err);

注意,对于区分两种能够发生失败的情况是很重要的。后者是服务端异常,这种情况下 err 被设置为非 null 的值。在服务器正常运行时,认证失败也是很自然的情况。确认 err 包含 null,使用最后一个参数传递详细信息。

验证回调通过委派的方式使 Passport 可以无需数据库支持。应用可以自己决定如何存储用户信息,没有验证层强加的任何假设。

 

会话

Passport 将维护持久的登录会话。为了使持久会话工作,认证用户必须序列化到会话,并且在后续请求时反序列化。

Passport 不会对你如何存储用户记录施加任何限制。相反,你提供给 Passport 的函数实现了必要的序列化和反序列化逻辑。在典型的应用中,这就像序列化用户 ID,以及在反序列化时通过 ID 查找用户一样简单。

在常规的 web 应用中,用于认证用户的凭证仅在登录请求时被发送。如果认证成功,一个session 将被建立和保持,通过设置在浏览器中的 cookie。

每个随后的请求将不再包含凭证,但是会有唯一的 cookie 来确认 session。为了支持登录 session,Passport 将序列化和反序列化 user 实例经由 session。

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

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

这个例子中,仅 user ID 被序列化到 session 中,从而保持存储在 session 中的数据量较小。当后续请求到达时,这个 ID 将用来找到存储在 req.user 中的 user。

序列化和反序列化逻辑由应用来提供,允许应用选择一个合适的数据库和(或者)对象mapper,无需认证层强加。

 

中间件

要在 Express 或 Connect 基础的应用中使用 Passport,需要配置必需的 passport.initialize() 中间件。如果你的应用使用持久登录会话(推荐,但不必需),则还必须使用 passport.session() 中间件。

var app = express();
app.use(require('serve-static')(__dirname + '/../../public'));
app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: 'keyboard cat', resave: true, saveUninitialized: true }));
app.use(passport.initialize());
app.use(passport.session());

注意,开启 session 支持完全是可选的,尽管建议将其用于大多数应用中。如果开启,确认在 passport.session() 前使用 session(),从而确保登录 session 能够按正确的顺序存储。

禁止 Sessions

成功授权后,Passport 将建立一个持久的登录 session。对于用户通过浏览器访问 web 应用的场景这是有用的。然后,其他情况下,不需要 session 支持。例如,API 服务器通常需要每个请求携带凭证。这种情况下,session 支持能够通过设置 session 选项为 false 来安全的禁用。

app.get('/api/users/me',
    passport.authenticate('basic', { session: false }),
    function(req, res) {
        res.json({id: req.user.id, username: req.user.username});
    }
)
自定义回调

如果内部选项不足够处理认证请求,自定义回调能够让应用处理成功和失败的情况。

app.get("/login", function (req, res, next) {
  passport.authenticate("local", function (err, user, info) {
    if (err) {
      return next(err);
    }
    if (!user) {
      return res.redirect("/login");
    }
    req.logIn(user, function (err) {
      if (err) {
        return next(err);
      }
      return res.redirect("/users" + user.username);
    });
  })(req, res, next);
});

这个例子中,注意 authenticate() 是在路由处理器中被调用,而不是作为路由中间件。这通过闭包给了 req和 res 对象回调权限。

如果认证失败,user 将被设置为 false。如果发生异常,err 将被设置。一个可选的 info 参数将传入,包括策略验证回调所提供的附加的详细信息。

这个回调能够使用提供的参数处理预期的认证结果。注意,当使用自定义回调时,需要应用来建立 session(通过调用 req.login()) 和发送响应。

passport-jwt

一个用于通过JSON Web Token进行身份验证的Passport策略。

这个模块允许您使用JSON Web Token对端点进行身份验证。它旨在用于保护没有会话的RESTful端点。

安装

npm install passport-jwt

使用

配置策略

JWT身份验证策略的构建如下:

new JwtStrategy(options, verify)

options是一个包含控制如何从请求中提取或验证令牌的选项的对象。

  • secretOrKey是一个包含用于验证令牌签名的密钥(对称)或PEM编码的公钥(非对称)的字符串或缓冲区。除非提供了secretOrKeyProvider,否则必须提供。
  • secretOrKeyProvider是一个回调函数,格式为function secretOrKeyProvider(request, rawJwtToken, done),应该调用done并传入给定密钥和请求组合的密钥或PEM编码的公钥(非对称)。done接受参数格式为function done(err, secret)。注意,实现者需要解码rawJwtToken。除非提供了secretOrKey,否则必须提供。
  • jwtFromRequest(必须)函数,接受请求作为唯一参数,并返回JWT字符串或_null_。有关更多详细信息,请参阅从请求中提取JWT。
  • issuer:如果定义了,将验证令牌的发行者(iss)是否与此值匹配。
  • audience:如果定义了,将验证令牌的受众(aud)是否与此值匹配。
  • algorithms:允许的算法名称列表。例如,"HS256","HS384""HS256","HS384"。
  • ignoreExpiration:如果为true,则不验证令牌的过期时间。
  • passReqToCallback:如果为true,则将请求传递给验证回调。即verify(request, jwt_payload, done_callback)。
  • jsonWebTokenOptions:passport-jwt使用jsonwebtoken验证令牌。在这里传入任何其他选项的对象。(例如maxAge)

verify是一个函数,参数为verify(jwt_payload, done)

  • jwt_payload是一个包含解码JWT有效负载的对象。
  • done是passport错误优先回调,接受参数done(error, user, info)

一个示例配置,它从带有'bearer'方案的http授权头读取JWT:

 
var JwtStrategy = require('passport-jwt').Strategy,
    ExtractJwt = require('passport-jwt').ExtractJwt;
var opts = {}
opts.jwtFromRequest = ExtractJwt.fromAuthHeaderAsBearerToken();
opts.secretOrKey = 'secret';
opts.issuer = 'accounts.examplesoft.com';
opts.audience = 'yoursite.net';
passport.use(new JwtStrategy(opts, function(jwt_payload, done) {
    User.findOne({id: jwt_payload.sub}, function(err, user) {
        if (err) {
            return done(err, false);
        }
        if (user) {
            return done(null, user);
        } else {
            return done(null, false);
            // 或者您可以创建一个新账户
        }
    });
}));

JWT可能包含在请求中的多种方式。为了尽可能灵活,JWT通过用户提供的回调函数从请求中解析,该回调函数作为jwtFromRequest参数传递。从现在起,这个回调函数被称为提取器,接受请求对象作为参数,并返回编码的JWT字符串或_null_。

passport-jwt.ExtractJwt提供了许多提取器工厂函数。这些工厂函数返回一个配置有给定参数的新提取器。

  • fromHeader(header_name)创建一个新的提取器,它在给定的http头中查找JWT。
  • fromBodyField(field_name)创建一个新的提取器,它在给定的正文字段中查找JWT。您必须配置了正文解析器才能使用此方法。
  • fromUrlQueryParameter(param_name)创建一个新的提取器,它在给定的URL查询参数中查找JWT。
  • fromAuthHeaderWithScheme(auth_scheme)创建一个新的提取器,它在授权头中查找JWT,期望方案与auth_scheme匹配。
  • fromAuthHeaderAsBearerToken()创建一个新的提取器,它在带有'bearer'方案的授权头中查找JWT。
  • fromExtractors([array of extractor functions])使用提供的提取器数组创建一个新的提取器。按顺序尝试每个提取器,直到一个返回令牌。

如果提供的提取器不符合您的需求,您可以轻松地提供自己的回调。例如,如果您使用cookie-parser中间件并希望从cookie中提取JWT,可以使用以下函数作为jwtFromRequest选项的参数:

var cookieExtractor = function(req) {
    var token = null;
    if (req && req.cookies) {
        token = req.cookies['jwt'];
    }
    return token;
};
// ...
opts.jwtFromRequest = cookieExtractor;

认证请求

使用passport.authenticate()指定'JWT'作为策略。

app.post('/profile', passport.authenticate('jwt', { session: false }),
    function(req, res) {
        res.send(req.user.profile);
    }
);

在请求中包含JWT

在请求中包含JWT的方法完全取决于您选择的提取器函数。例如,如果您使用fromAuthHeaderAsBearerToken提取器,则会在请求中包含一个Authorization头,并将方案设置为bearer。例如:

Authorization: bearer JSON_WEB_TOKEN_STRING.....

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2190818.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Python】Dejavu:Python 音频指纹识别库详解

Dejavu 是一个基于 Python 实现的开源音频指纹识别库,主要用于音频文件的识别和匹配。它通过生成音频文件的唯一“指纹”并将其存储在数据库中,来实现音频的快速匹配。Dejavu 的主要应用场景包括识别音乐、歌曲匹配、版权管理等。 ⭕️宇宙起点 &#x1…

【AI知识点】泊松分布(Poisson Distribution)

泊松分布(Poisson Distribution) 是统计学和概率论中的一种离散概率分布,通常用于描述在固定时间或空间内,某个事件发生的次数。该分布适用于稀有事件的建模,特别是当事件发生是独立的、随机的,且发生的平均…

[Go语言快速上手]初识Go语言

目录 一、什么是Go语言 二、第一段Go程序 1、Go语言结构 注意 2、Go基础语法 关键字 运算符优先级 三、Go语言数据类型 示例 小结 一、什么是Go语言 Go语言,通常被称为Golang,是一种静态类型、编译型的计算机编程语言。它由Google的Robert Gr…

关闭IDM自动更新

关闭IDM自动更新 1 打开注册表2 找到IDM注册表路径 1 打开注册表 winR regedit 2 找到IDM注册表路径 计算机\HKEY_CURRENT_USER\Software\DownloadManager 双击LstCheck,把数值数据改为0 完成 感谢阅读

存储电话号码的数据类型,用 int 还是用 string?

在 Java 编程中,存储电话号码的选择可以通过两种常见方式进行:使用 int 类型或 String 类型。这种选择看似简单,但实际上涉及到 JVM 内部的字节码实现、内存优化、数据表示、以及潜在的可扩展性问题。 Java 基本数据类型与引用数据类型的差异…

Windows安全加固详解

一、补丁管理 使用适当的命令或工具&#xff0c;检查系统中是否有未安装的更新补丁。 Systeminfo 尝试手动安装一个系统更新补丁。 • 下载适当的补丁文件。 • 打开命令提示符或PowerShell&#xff0c;并运行 wusa.exe <patch_file_name>.msu。 二、账号管…

使用seata管理分布式事务

做应用开发时&#xff0c;要保证数据的一致性我们要对方法添加事务管理&#xff0c;最简单的处理方案是在方法上添加 Transactional 注解或者通过编程方式管理事务。但这种方案只适用于单数据源的关系型数据库&#xff0c;如果项目配置了多个数据源或者多个微服务的rpc调用&…

thinkphp 学习记录

1、PHP配置 &#xff08;点开链接后&#xff0c;往下拉&#xff0c;找到PHP8.2.2版本&#xff0c;下载的是ZIP格式&#xff0c;解压即用&#xff09; PHP For Windows: Binaries and sources Releases &#xff08;这里是下载地址&#xff09; 我解压的地址是&#xff1a;D:\…

Spring中Bean创建过程中各个阶段的作用

文章目录 Instantiate&#xff08;实例化&#xff09;Populate properties&#xff08;填充属性&#xff09;BeanNameAwares setBeanName()BeanFactoryAwares setBeanFactory()ApplicationContextAwares setApplicationContext()Pre-initialization BeanPostProcessorsInitiali…

【Python篇】从零到精通:全面分析Scikit-Learn在机器学习中的绝妙应用

文章目录 从零到精通&#xff1a;全面揭秘Scikit-Learn在机器学习中的绝妙应用前言第一部分&#xff1a;深入了解Scikit-Learn的基础知识1. 什么是Scikit-Learn&#xff1f;2. 安装Scikit-Learn3. Scikit-Learn中的基本构件4. 数据集的加载与探索5. 数据预处理标准化数据 6. 构…

【Kubernetes】常见面试题汇总(五十五)

目录 121. POD 创建失败&#xff1f; 122. POD 的 ready 状态未进入&#xff1f; 特别说明&#xff1a; 题目 1-68 属于【Kubernetes】的常规概念题&#xff0c;即 “ 汇总&#xff08;一&#xff09;~&#xff08;二十二&#xff09;” 。 题目 69-113 属于【Kube…

C# 数组和集合

本课要点&#xff1a; 1、数组概述 2、一维数组的使用 3、二维数组的使用 4、数组的基本操作 5、数组排序算法 6、ArrayList集合 7、Hashtable类 8、常见错误 一 数组 1 数组引入1 问题&#xff1a; 简单问题&#xff1a;求4个整数的最大值&#xff1f; int a 40,…

C语言自定义类型联合和枚举(25)

文章目录 前言一、联合体联合体的声明联合体的特点联合体和结构体内存布局对比联合体的大小计算联合体的实际使用样例礼品兑换单判断当前机器是大端还是小端 二、枚举枚举的定义枚举类型的声明枚举类型的优点枚举类型的使用 总结 前言 关于自定义类型除了我们常用的结构体&…

Kubernetes-Operator篇-04-operator部署验证

1、部署命令 这个是很多博客教程都在使用的部署命令&#xff1a; make manifests make install export ENABLE_WEBHOOKSfalse make run我们使用之前的demo来进行部署验证&#xff1a;Kubernetes-Operator篇-02-脚手架熟悉 这里面涉及到的makefile的配置可以参考&#xff1a;…

10.5二分专练,二分边界情况,+1不加1的判断,方向判断,各种DEBUG

5 https://leetcode.cn/problems/minimum-speed-to-arrive-on-time/submissions/570242512/ 就是说总时间是 前n-1量汽车的运行时间&#xff0c;向上取整&#xff0c;然后再加上最后一辆列车的运行时间 最快的话是需要n-1个小时 搜索空间就是时速&#xff0c;左边界是1&#x…

数学建模 第三讲 - 简单的优化模型

在数学建模的学习过程中&#xff0c;第三章介绍了几种简单的优化模型&#xff0c;这些模型在实际生活中有广泛的应用。以下是对这些模型的整理和总结。 3.1 存贮模型 问题描述 配件厂为装配线生产产品&#xff0c;更换设备需要支付生产准备费&#xff0c;产量大于需求时需要…

Llama 3.2 微调指南

让我们通过微调 Llama 3.2 来找到一些精神上的平静。 我们需要安装 unsloth&#xff0c;以更小的尺寸实现 2 倍的快速训练 !pip install unsloth!pip uninstall unsloth -y && pip install --upgrade --no-cache-dir "unsloth[colab-new] githttps://github.co…

OpenCV马赛克

#马赛克 import cv2 import numpy as np import matplotlib.pyplot as pltimg cv2.imread(coins.jpg,1) imgInfo img.shape height imgInfo[0] width imgInfo[1]for m in range(200,400): #m,n表示打马赛克区域for n in range(200,400):# pixel ->10*10if m%10 0 and …

初识Linux · 文件(1)

目录 前言&#xff1a; 回顾语言层面的文件 理解文件的预备知识 文件和磁盘 使用和认识系统调用函数 前言&#xff1a; 本文以及下篇文章&#xff0c;揭露的都是Linux中文件的奥秘&#xff0c;对于文件来说&#xff0c;初学Linux第一节课接触的就是文件&#xff0c;对于C…

Windows删除service服务

Windows删除service服务 找到命令提示符&#xff1a; 右键&#xff0c;以管理员身份运行 输入&#xff1a; sc delete 服务名 Windows根据TCP端口号查找进程PID再kill进程_windows tcpkill-CSDN博客文章浏览阅读5.3k次&#xff0c;点赞42次&#xff0c;收藏104次。Windows根据…