Nodejs express的OAuth2认证流程

Nodejs express的OAuth2认证流程

我想大家都知道oauth2是通过access_token去限制访问api的接口,通过时间过期规则要重新登录才能访问接口,但是oauth2里面有提供一个叫refresh access_token的方法,这样就保证能自动获取最新的access_token以免过期,具体原理我也不多讲了,下面是要做的步骤: 首先大家先要在index.js中定义一些配置:

global.__basename = __dirname; #定义文件夹的目录
require('coffee-script'); #加载coffeescript的语法,如果不需要可以不用写
process.env.TZ = 'PRC'; #设置时间

app.coffee要添加以下代码:

cors = require './middlewares/cors'
oauth = require './middlewares/oauth'
app.use cors
app.use oauth.oauth()
app.use oauth.login()

我们然后我们要新建一个oauth.coffee放在middlewares中,里面要用第三方的oauth2-provider npm install oauth2-provider,然后还要依次加载一些必要的包

OAuth2Provider = require('oauth2-provider').OAuth2Provider
config = require __basename + '/config/config'
mysql = require __basename + '/connections/mysql/test' #此处为你的mysql链接句柄
mohair = require 'mohair'
async = require 'async'
_ = require 'underscore'
makeExtraData = require __basename + '/helpers/oauth/extra_data'

makeExtraData require的实际上是oauth的用户验证.

在’/helpers/oauth/中新建立一个extra_data.coffee在里面写下以下代码

mhoair = require 'mohair'
mysql = require __basename + '/connections/mysql/test' #此处为你的mysql链接句柄
#链接users表,拿到该users所属的群主
module.exports = (userId, fn) ->
  mohair
    .connect(mysql)
    .table('users')
    .select('group_id')
    .where(id: userId)
    .findOne (err, result) ->
      fn err, groupId: result.group_id

然后回到middlewares/oauth.coffee中开始认证

module.exports = oauth = new OAuth2Provider
  crypt_key: config.oauth.cryptKey
  sign_key: config.oauth.signKey

在此需要检查用户登录状态,并且实现页面跳转

oauth.on 'enforce_login', (req, res, authorizeUrl, next) ->
  return next req.session.userId if req.session.userId
  res.redirect '/oauth/login?next=' + encodeURIComponent authorizeUrl

现在应该实现验证了.

oauth.on 'authorize_form', (req, res, clientId, authorizeUrl) ->
  async.auto
    permission: (fn) ->
      mohair
        .connect(mysql)
        .table('user_role_relations urr')
        .join('left join roles r on urr.role_id = r.id')
        .where('urr.user_id': req.session.userId)
        .where('r.product_id': clientId)
        .exists fn
  ,
    (err, results) ->
      res.locals.authorizeUrl = authorizeUrl
      if results.permission
        #res.render 至成功页面
      else
        #res.render 至失败页面

在创建access_token的时候检查用户是否存在

oauth.on 'create_access_token', (userId, clientId, next) ->
  makeExtraData userId, (err, result) ->
    next result

oauth.on 'save_access_token', (userId, clientId, accessToken) ->
#然后保存access_token,在此你可以进行其他操作

最后,开始创建access_token啦.config.oauth.accessTokenTTL 为access_token的过期时间

oauth.on 'access_token', (req, info, next) ->
  #在此判断access_token是否过期
  if info.grant_date.getTime() + config.oauth.accessTokenTTL > Date.now()
    req.session.userId = info.user_id
    req.session.groupId = info.extra_data?.groupId
    async.auto
      role: (callback) ->
        mohair
          .connect(mysql)
          .table('user_role_relations urr')
          .join('left join roles r on urr.role_id = r.id')
          .select('urr.role_id as role_id')
          .where('urr.user_id': info.user_id)
          .findOne callback
      permission: ['role', (callback, results) ->
        return callback null, [] unless results.role
        mohair
          .connect(mysql)
          .table('role_permission_relations rpr')
          .join('left join permissions p on rpr.permission_id = p.id')
          .select('p.constant')
          .where('rpr.role_id': results.role.role_id)
          .exec callback
      ]
    , 
      (err, results) ->
        next err if err
        req.session.permissions = _.pluck results.permission, 'constant'
        next()
  else
    req.session.error = 'access_token timeout'
    next()

我想上面的代码大家应该都能看懂吧,先判断access_token是否过期,然后将【用户、群主、及用户权限存入至session中,如果权限不存在,返回就是空,也就是该用户没有权限】

接下来,我们应该开始添加url接口,实现oauth2认证了就应该在router.coffee写下如下代码:

app = require './app'
# OAuth
app.use '/oauth', require './lib/oauth'

新建/lib/oauth/index.coffee,在这里可以实现登录获取access_token及登出等操作.

express = require 'express'
mysql = require __basename + '/connections/mysql/test'
mohair = require 'mohair'
config = require __basename + '/config/config'
querystring = require 'querystring'
oauth = require __basename + '/middlewares/oauth'
restrict = require __basename + '/middlewares/restrict'
makeExtraData = require __basename + '/helpers/oauth/extra_data'

module.exports = app = express()
#在此处进行登录验证,成功后进行页面跳转,并设置session
app.post '/login', (req, res) ->
  {email, password} = req.body
  mohair
    .connect(mysql)
    .table('users')
    .select('id')
    .where(email: email)
    .where(password: password)
    .findOne (err, result) ->
      if result
        req.session.userId = result.id
      else
        req.session.error = 'Invalid User'
      res.redirect req.body.next

登出方法

app.get '/logout', (req, res) ->
  req.session = null
  res.redirect '/oauth/authorize?' + querystring.stringify req.query

刷新access_token

app.get '/refresh_token', restrict(), (req, res) ->
  makeExtraData req.session.userId, (err, extraData) ->
    result = oauth.generateAccessToken req.session.userId, null, extraData
    res.json access_token: result.access_token 

登出和刷新所提供的两个方法都是oauth2本身提供的方法

middlewares/restrict.coffee代码如下:

_ = require 'underscore'

module.exports = (permission) ->
  (req, res, next) ->
    unless req.query.access_token
      return res.json 400, error: "require access_token"
    if req.session.error
      return res.json 400, error: req.session.error
    if permission and ! _.contains req.session.permissions, permission
      return res.json 400, error: 'You dont have the permission'
    next()

如果有权限就会调用next()实现跳转了,在调用login的时候要传一个next的参数,这个是登录成功后跳转的页面

在routes里面,大家可以通过restrict去判断该用户是否具备某routes权限

Users = require './user'
app.get '/users', restrict('list'), Users.index

里面的list是该route的权限,是跟数据库test中的permissions表中的constant对应,该表结构如下:permissions表结构图


7 回复

Nodejs Express 的 OAuth2 认证流程

OAuth2 是一种授权框架,用于授予客户端(如Web应用)对HTTP服务(资源服务器)上的受保护资源进行有限访问的权限。在 Node.js 中使用 Express 框架实现 OAuth2 认证流程通常涉及以下几个步骤:

1. 安装依赖

首先,你需要安装 oauth2-server 库来处理 OAuth2 流程。你也可以使用其他库,比如 passport-oauth2,但这里我们使用 oauth2-server 作为示例。

npm install oauth2-server

2. 配置 OAuth2 Server

在你的项目中定义全局变量并加载必要的模块。

// index.js
global.__basename = __dirname;
require('coffee-script');
process.env.TZ = 'PRC';

3. 设置中间件

app.jsapp.coffee 中设置 CORS 和 OAuth 中间件。

// app.js
const cors = require('./middlewares/cors');
const oauth = require('./middlewares/oauth');

app.use(cors);
app.use(oauth.oauth());
app.use(oauth.login());

4. 创建 OAuth2 Provider

创建一个 OAuth2Provider 实例,并定义必要的回调函数。

// middlewares/oauth.js
const OAuth2Provider = require('oauth2-server').OAuth2Server;
const config = require(__dirname + '/../config/config');
const mysql = require(__dirname + '/../connections/mysql/test');

module.exports = new OAuth2Provider({
  model: {
    // 实现必要的模型方法
  },
  grants: ['password'],
  debug: true,
});

5. 用户认证逻辑

实现用户认证逻辑,包括登录、登出和刷新 token。

// lib/oauth/index.js
const express = require('express');
const mysql = require(__dirname + '/../connections/mysql/test');
const oauth = require(__dirname + '/../middlewares/oauth');

const app = express();

app.post('/login', (req, res) => {
  const { email, password } = req.body;

  // 在这里查询数据库以验证用户
  mysql.query(
    'SELECT * FROM users WHERE email = ? AND password = ?',
    [email, password],
    (err, result) => {
      if (result.length > 0) {
        req.session.userId = result[0].id;
        res.redirect(req.body.next);
      } else {
        req.session.error = 'Invalid User';
        res.redirect('/');
      }
    }
  );
});

app.get('/logout', (req, res) => {
  req.session = null;
  res.redirect('/oauth/authorize?' + querystring.stringify(req.query));
});

app.get('/refresh_token', (req, res) => {
  const { user_id } = req.session;
  const extraData = {}; // 根据需求生成额外数据
  const result = oauth.generateAccessToken(user_id, null, extraData);
  res.json({ access_token: result.access_token });
});

module.exports = app;

6. 权限控制

实现权限控制中间件,确保只有具有特定权限的用户才能访问某些路由。

// middlewares/restrict.js
module.exports = (permission) => {
  return (req, res, next) => {
    if (!req.query.access_token) {
      return res.status(400).json({ error: 'require access_token' });
    }

    if (req.session.error) {
      return res.status(400).json({ error: req.session.error });
    }

    if (permission && !req.session.permissions.includes(permission)) {
      return res.status(400).json({ error: 'You don\'t have the permission' });
    }

    next();
  };
};

7. 路由配置

根据权限配置路由。

// routes.js
const Users = require('./user');
const restrict = require('./middlewares/restrict');

app.get('/users', restrict('list'), Users.index);

通过以上步骤,你可以在 Node.js 中使用 Express 实现 OAuth2 认证流程。每个步骤都提供了详细的代码示例和解释,帮助你理解整个流程。


实在是太复杂了…

app.post ‘/login’, (req, res) ->

这个是什么语法… 糖?

这个是coffeescript的语法,就是ruby on rails语言的语法,呵呵. 因为整个过程包括登录,认证以及验证,所以可能有点点复杂. 你在index.js中加入这个代码就可以用coffeescript的语法了.

require('coffee-script')

大量语法糖的混杂外加各种框架,逻辑根本看不清,这代码想要维护有点难吧

长文帮顶~

untitled1.png

coffee 也挺不错的, 但是总是充当人肉编译器, 想编译完是啥样子, 还是写 js 吧

OAuth2认证是一种常用的授权机制,允许客户端安全地访问服务器资源,而无需暴露用户的凭证。在Node.js中使用Express框架结合OAuth2-Provider库来实现OAuth2认证流程包括几个关键步骤:

  1. 安装依赖:首先确保安装了oauth2-provider和其它必需的依赖。

    npm install oauth2-provider express mohair mysql2
    
  2. 初始化OAuth2 Provider:在中间件文件中初始化OAuth2Provider并配置加密密钥。

    const OAuth2Provider = require('oauth2-provider').OAuth2Provider;
    const config = require(__dirname + '/config/config');
    module.exports = oauth = new OAuth2Provider({
        crypt_key: config.oauth.cryptKey,
        sign_key: config.oauth.signKey
    });
    
  3. 处理登录请求:创建登录接口,验证用户凭据并生成会话。

    app.post('/login', (req, res) => {
        const { email, password } = req.body;
        // 假设的MySQL查询验证用户
        db.query("SELECT * FROM users WHERE email=? AND password=?", [email, password], (err, result) => {
            if (result.length) {
                req.session.userId = result[0].id;
                res.redirect(req.body.next);
            } else {
                req.session.error = 'Invalid credentials';
                res.redirect('/login');
            }
        });
    });
    
  4. 处理OAuth请求:创建OAuth端点用于获取令牌。

    app.post('/oauth/token', oauth.token());
    
  5. 保护API路由:使用中间件验证访问令牌。

    const restrict = require('./middlewares/restrict');
    app.get('/protected', restrict(['read']), (req, res) => {
        res.send('This is a protected route!');
    });
    
  6. 实现令牌刷新:创建刷新令牌的端点。

    app.get('/refresh_token', restrict(), (req, res) => {
        const refreshToken = req.query.refresh_token;
        oauth.refreshToken(refreshToken).then((response) => {
            res.json({ access_token: response.accessToken });
        }).catch((error) => {
            res.status(401).json({ error: 'Failed to refresh token' });
        });
    });
    

以上步骤演示了一个基本的OAuth2认证流程。实际应用中需要考虑更多的细节,比如错误处理、安全性加固、以及与特定数据库交互时的性能优化。

回到顶部