node.js+express的OAuth2认证流程
发布于 11 年前 作者 ym1623 27946 次浏览 最后一次编辑是 8 年前

我想大家都知道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表结构图

5 回复

实在是太复杂了…

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

这个是什么语法… 糖?

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

require('coffee-script')

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

untitled1.png

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

回到顶部